/* * JFugue, an Application Programming Interface (API) for Music Programming * http://www.jfugue.org * * Copyright (C) 2003-2014 David Koelle * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.staccato; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.jfugue.parser.ParserException; import org.jfugue.provider.ChordProvider; import org.jfugue.provider.KeyProviderFactory; import org.jfugue.provider.NoteProvider; import org.jfugue.theory.Chord; import org.jfugue.theory.Intervals; import org.jfugue.theory.Note; public class NoteSubparser implements Subparser, NoteProvider, ChordProvider { private static NoteSubparser instance; public static NoteSubparser getInstance() { if (instance == null) { instance = new NoteSubparser(); } return instance; } private List<Character> charArray = new ArrayList<Character>(); private Logger logger = Logger.getLogger("org.jfugue"); private NoteSubparser() { charArray.add('C'); // Do charArray.add('D'); // Re charArray.add('E'); // Mi charArray.add('F'); // Fa charArray.add('G'); // So charArray.add('A'); // La charArray.add('B'); // Ti charArray.add('R'); // Rest charArray.add('['); // Note expressed as a value (e.g., "[SNARE_DRUM]q") charArray.add('0'); charArray.add('1'); charArray.add('2'); charArray.add('3'); charArray.add('4'); charArray.add('5'); charArray.add('6'); charArray.add('7'); charArray.add('8'); charArray.add('9'); logger.setLevel(Level.OFF); } @Override public boolean matches(String music) { return charArray.contains(music.charAt(0)); } @Override public int parse(String s, StaccatoParserContext context) { return parseNoteElement(s, 0, context); } private int parseNoteElement(String s, int index, StaccatoParserContext parserContext) { boolean repeat = false; NoteContext noteContext = new NoteContext(); do { // Begin the voyage of creating a note by populating the NoteContext index = parseNoteElement(s, index, noteContext, parserContext); if (noteContext.isChord) { Chord chord = noteContext.createChord(parserContext); parserContext.getParser().fireChordParsed(chord); } else { Note note = noteContext.createNote(parserContext); parserContext.getParser().fireNoteParsed(note); } // // If the note is a chord, fire all the note events for each note in the chord // if (noteContext.isChord) { // Note[] notes = new Chord(note, noteContext.intervals).getNotes(); // for (int i=1; i < notes.length; i++) { // NoteContext context2 = noteContext.createChordNoteContext(); // context2.noteNumber = notes[i].getValue(); // logger.info("Next note in chord is "+context2.noteNumber); // parserContext.fireNoteParsed(context2.createNote(parserContext)); // } // } // Determine if there is another note to parse, and set up the next NoteContext repeat = noteContext.thereIsAnother; noteContext = noteContext.createNextNoteContext(); } while (repeat); return index; } /** * Parses a note element. * @param s the token that contains a note element * @throws JFugueException if there is a problem parsing the element */ public int parseNoteElement(String s, int index, NoteContext noteContext, StaccatoParserContext parserContext) { logger.info("--Parsing note from string "+s); s = s.toUpperCase(); // Ensure s is uppercase - which might not be the case if this is coming in via NoteProvider or ChordProvider index = parseRoot(s, index, noteContext); int startInternalInterval = parseOctave(s, index, noteContext); int startChord = parseInternalInterval(s, startInternalInterval, noteContext); int startChordInversion = parseChord(s, startChord, noteContext); if (index == startInternalInterval) { setDefaultOctave(noteContext); } logger.info("Octave: " + noteContext.octaveNumber); computeNoteValue(noteContext, parserContext); index = parseChordInversion(s, startChordInversion, noteContext); index = parseDuration(s, index, noteContext, parserContext); index = parseVelocity(s, index, noteContext); index = parseConnector(s, index, noteContext); return index; } /** * Returns the index with which to start parsing the next part of the * string, once this method is done with its part */ private int parseRoot(String s, int index, NoteContext context) { if ((s.charAt(index) >= 'A') && (s.charAt(index) <= 'G')) { return parseLetterNote(s, index, context); } else if (s.charAt(index) == 'R') { return parseRest(s, index, context); } else if (s.charAt(index) == '[') { return parseBracketedNote(s, index, context); } else if (s.charAt(index) >= '0' && (s.charAt(index) <= '9')) { return parseNumericNote(s, index, context); } else { // We should never get here; if we do, then there's something wrong with matches() return 0; } } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseLetterNote(String s, int index, NoteContext context) { context.isNumericNote = false; int originalIndex = index; switch(s.charAt(index)) { case 'C' : context.noteNumber = 0; break; case 'D' : context.noteNumber = 2; break; case 'E' : context.noteNumber = 4; break; case 'F' : context.noteNumber = 5; break; case 'G' : context.noteNumber = 7; break; case 'A' : context.noteNumber = 9; break; case 'B' : context.noteNumber = 11; break; default : break; } index++; // Check for #, b, or n (sharp, flat, or natural) modifier boolean checkForModifiers = true; while (checkForModifiers) { if (index < s.length()) { switch(s.charAt(index)) { case '#' : index++; context.noteNumber++; if (context.noteNumber == 12) { context.noteNumber = 0; context.octaveBias++; } break; case 'B' : index++; context.noteNumber--; if (context.noteNumber == -1) { context.noteNumber = 11; context.octaveBias--; } break; case 'N' : index++; context.isNatural = true; checkForModifiers = false; break; default : checkForModifiers = false; break; } } else { checkForModifiers = false; } } context.originalString = s.substring(originalIndex, index); logger.info("Note number within an octave (C=0, B=11): " + context.noteNumber+" (with octaveBias = "+context.octaveBias+")"); return index; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseRest(String s, int index, NoteContext context) { context.isRest = true; logger.info("This note is a Rest"); return index+1; } private int parseBracketedNote(String s, int index, NoteContext context) { int indexOfEndBracket = s.indexOf(']', index); String stringInBrackets = s.substring(index+1,indexOfEndBracket); context.noteValueAsString = stringInBrackets; context.isNumericNote = true; logger.info("This note is a note represented by the dictionary value "+context.noteValueAsString); return indexOfEndBracket+1; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseNumericNote(String s, int index, NoteContext context) { int numCharsInNumber = 0; while (numCharsInNumber < s.length() && (s.charAt(index+numCharsInNumber) >= '0') && (s.charAt(index+numCharsInNumber) <= '9')) { numCharsInNumber++; } String numericNoteString = s.substring(index, index+numCharsInNumber); context.noteNumber = Byte.parseByte(numericNoteString); context.isNumericNote = true; logger.info("This note is a numeric note with value " + context.noteNumber); return index+numCharsInNumber; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseOctave(String s, int index, NoteContext context) { // Don't parse an octave for a rest or a numeric note if (context.isRest || context.isNumericNote) { return index; } // Check for octave. Remember that octaves are optional. // Octaves can be two digits, which is what this next bit is testing for. // But, there could be no octave here as well. char possibleOctave1 = '.'; char possibleOctave2 = '.'; if (index < s.length()) { possibleOctave1 = s.charAt(index); } if (index+1 < s.length()) { possibleOctave2 = s.charAt(index+1); } byte definiteOctaveLength = 0; if ((possibleOctave1 >= '0') && (possibleOctave1 <= '9')) { definiteOctaveLength = 1; if (possibleOctave2 == '0') { definiteOctaveLength = 2; } // if ((possibleOctave1 == '-') && (definiteOctaveLength == 1)) { // // That '-' isn't representing a -1 octave, it's probably representing the end of a tie! // // It's a duration character, not an octave character! // return index; // } logger.info("Octave is " + definiteOctaveLength + " digits long"); String octaveNumberString = s.substring(index, index+definiteOctaveLength); logger.info("Octave string value is " + octaveNumberString); try { context.octaveNumber = Integer.parseInt(octaveNumberString) + context.octaveBias; } catch (NumberFormatException e) { throw new ParserException(StaccatoMessages.OCTAVE_OUT_OF_RANGE, s); } if (context.octaveNumber > Note.MAX_OCTAVE) { throw new ParserException(StaccatoMessages.OCTAVE_OUT_OF_RANGE, s); } if (context.octaveNumber < Note.MIN_OCTAVE) { throw new ParserException(StaccatoMessages.OCTAVE_OUT_OF_RANGE, s); } context.originalString = context.originalString + octaveNumberString; } return index+definiteOctaveLength; } private void setDefaultOctave(NoteContext context) { logger.info("No octave string found, setting default octave"); if (context.isChord) { context.octaveNumber = DefaultNoteSettingsManager.getInstance().getDefaultBassOctave() + context.octaveBias; } else { context.octaveNumber = DefaultNoteSettingsManager.getInstance().getDefaultOctave() + context.octaveBias; } } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseInternalInterval(String s, int index, NoteContext context) { if (context.isRest) { return index; } // An internal interval is indicated by a single quote if ((index < s.length()) && (s.charAt(index) == '\'')) { int intervalLength = 0; // Verify that index+1 is a number representing the interval. if (index+1 < s.length() && ((s.charAt(index+1) >= '0') && (s.charAt(index+1) <= '9'))) { intervalLength = 1; } // We'll allow for the possibility of double-sharps and double-flats. if ((intervalLength == 1) && (index+2 < s.length()) && ((s.charAt(index+2) == '#') || (s.charAt(index+2) == 'B'))) { intervalLength = 2; } if ((intervalLength == 2) && (index+3 < s.length()) && ((s.charAt(index+3) == '#') || (s.charAt(index+3) == 'B'))) { intervalLength = 3; } context.internalInterval = Intervals.getHalfsteps(s.substring(index+1, index+intervalLength+1)); return index + intervalLength + 1; } else { return index; } } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseChord(String s, int index, NoteContext context) { // Don't parse chord for a rest if (context.isRest) { return index; } int lengthOfChordString = 0; boolean chordFound = false; String[] chordNames = Chord.getChordNames(); for (String chordName : chordNames) { if (!chordFound && (s.length() >= index + chordName.length()) && chordName.equals(s.substring(index, index+chordName.length()))) { chordFound = true; lengthOfChordString = chordName.length(); context.isChord = true; context.intervals = Chord.getIntervals(chordName); context.chordName = chordName; logger.info("Chord: "+chordName+" Interval Pattern: "+Chord.getIntervals(chordName)); break; } } return index + lengthOfChordString; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseChordInversion(String s, int index, NoteContext context) { if (!context.isChord) { return index; } int inversionCount = 0; boolean bassNote = false; int startIndex = index; boolean checkForInversion = true; while (checkForInversion) { if (index < s.length()) { switch(s.charAt(index)) { case '^': index++; inversionCount++; break; case 'C': index++; bassNote = true; break; case 'D': index++; bassNote = true; break; case 'E': index++; bassNote = true; break; case 'F': index++; bassNote = true; break; case 'G': index++; bassNote = true; break; case 'A': index++; bassNote = true; break; case 'B': index++; bassNote = true; break; case '#': index++; break; // presumably the sharp mark followed a note // For '0', need to differentiate between initial 0 and 0 as a second digit (i.e., 10) case '0': index++; inversionCount = (inversionCount == -1) ? 0 : inversionCount + 10; break; case '1': index++; inversionCount = 1; break; case '2': index++; inversionCount = 2; break; case '3': index++; inversionCount = 3; break; case '4': index++; inversionCount = 4; break; case '5': index++; inversionCount = 5; break; case '6': index++; inversionCount = 6; break; case '7': index++; inversionCount = 7; break; case '8': index++; inversionCount = 8; break; case '9': index++; inversionCount = 9; break; // For '[', we're checking for a note number after the inversion marker case '[': int indexEndBracket = s.indexOf(']', index); context.inversionBassNote = Note.getToneString(Byte.parseByte(s.substring(index + 1, indexEndBracket - 1))); index = indexEndBracket + 1; break; default: checkForInversion = false; break; } } else { checkForInversion = false; } } // Modify the note values based on the inversion if (bassNote) { context.inversionBassNote = context.inversionBassNote = s.substring(startIndex+1, index); } else if (inversionCount > 0) { context.inversionCount = inversionCount; } return index; } /** This method does a variety of calculations to get the actual value of the note. */ private void computeNoteValue(NoteContext noteContext, StaccatoParserContext parserContext) { // Don't compute note value for a rest if (noteContext.isRest) { return; } // Adjust for Key Signature if (parserContext.getKey() != null) { int keySig = KeyProviderFactory.getKeyProvider().convertKeyToByte(parserContext.getKey()); if ((keySig != 0) && (!noteContext.isNatural)) { if ((keySig <= -1) && (noteContext.noteNumber == 11)) noteContext.noteNumber = 10; if ((keySig <= -2) && (noteContext.noteNumber == 4)) noteContext.noteNumber = 3; if ((keySig <= -3) && (noteContext.noteNumber == 9)) noteContext.noteNumber = 8; if ((keySig <= -4) && (noteContext.noteNumber == 2)) noteContext.noteNumber = 1; if ((keySig <= -5) && (noteContext.noteNumber == 7)) noteContext.noteNumber = 6; if ((keySig <= -6) && (noteContext.noteNumber == 0)) { noteContext.noteNumber = 11; noteContext.octaveNumber--; } if ((keySig <= -7) && (noteContext.noteNumber == 5)) noteContext.noteNumber = 4; if ((keySig >= +1) && (noteContext.noteNumber == 5)) noteContext.noteNumber = 6; if ((keySig >= +2) && (noteContext.noteNumber == 0)) noteContext.noteNumber = 1; if ((keySig >= +3) && (noteContext.noteNumber == 7)) noteContext.noteNumber = 8; if ((keySig >= +4) && (noteContext.noteNumber == 2)) noteContext.noteNumber = 3; if ((keySig >= +5) && (noteContext.noteNumber == 9)) noteContext.noteNumber = 10; if ((keySig >= +6) && (noteContext.noteNumber == 4)) noteContext.noteNumber = 5; if ((keySig >= +7) && (noteContext.noteNumber == 11)) { noteContext.noteNumber = 0; noteContext.octaveNumber++; } logger.info("After adjusting for Key Signature, noteNumber=" + noteContext.noteNumber +" octave=" + noteContext.octaveNumber); } } // Compute the actual note number, based on octave and note if (!noteContext.isNumericNote) { int intNoteNumber = ((noteContext.octaveNumber) * 12) + noteContext.noteNumber + noteContext.internalInterval; if ( intNoteNumber > 127) { throw new ParserException(StaccatoMessages.CALCULATED_NOTE_OUT_OF_RANGE, Integer.toString(intNoteNumber)); } noteContext.noteNumber = (byte)intNoteNumber; logger.info("Computed note number: " + noteContext.noteNumber); } } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseDuration(String s, int index, NoteContext noteContext, StaccatoParserContext parserContext) { if (index < s.length()) { switch (s.charAt(index)) { case '/' : index = parseNumericDuration(s, index, noteContext); break; case 'W' : case 'H' : case 'Q' : case 'I' : case 'S' : case 'T' : case 'X' : case 'O' : case '-' : index = parseLetterDuration(s, index, noteContext, parserContext); break; default : noteContext.decimalDuration = DefaultNoteSettingsManager.getInstance().getDefaultDuration(); noteContext.durationExplicitlySet = false; break; // Could get here if the next character is a velocity char ("a" or "d") } index = parseTuplet(s, index, noteContext); } else { noteContext.decimalDuration = DefaultNoteSettingsManager.getInstance().getDefaultDuration(); noteContext.durationExplicitlySet = false; } logger.info("Decimal duration is " + noteContext.decimalDuration); return index; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseNumericDuration(String s, int index, NoteContext context) { // The duration has come in as a number, like 0.25 for a quarter note. // Advance pointer past the initial slash (/) index++; // If first character before the numeric value is a dash, we're ending a tie if (s.charAt(index) == '-') { context.isEndOfTie = true; index++; } context.durationExplicitlySet = true; // Get the duration value int endingIndex = seekToEndOfDecimal(s,index); String durationNumberString = s.substring(index, endingIndex); context.decimalDuration += Double.parseDouble(durationNumberString); logger.info("Decimal duration is " + context.decimalDuration); index = endingIndex; // If the character after all of the value parsing is a dash, we're starting a tie if ((index < s.length()) && (s.charAt(index) == '-')) { context.isStartOfTie = true; index++; } return index; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseQuantityDuration(String s, int index, NoteContext context) { // A quantity is associated with the duration, like the '24' in "w24" int endingIndex = seekToEndOfDecimal(s,index); String quantityNumberString = s.substring(index, endingIndex); context.decimalDuration += (1.0f/context.mostRecentDuration) * (Double.parseDouble(quantityNumberString) - 1.0D); // Subtract 1, because mostRecentDuration has already been added to the total duration logger.info("Quantity duration calculation: Duration of 1/"+context.mostRecentDuration+" * "+quantityNumberString+" = "+(1.0/context.mostRecentDuration) * Double.parseDouble(quantityNumberString)); return endingIndex; } private int seekToEndOfDecimal(String s, int startingIndex) { int cursor = startingIndex; while (cursor < s.length() && (s.charAt(cursor) == '.' || ((s.charAt(cursor) >= '0') && (s.charAt(cursor) <= '9')))) { cursor++; } return cursor; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseLetterDuration(String s, int index, NoteContext context, StaccatoParserContext parserContext) { boolean moreDurationCharsToParse = true; boolean isDotted = false; while (moreDurationCharsToParse == true) { int durationNumber = 0; // See if the note has a duration. Duration is optional for a note. if (index < s.length()) { char durationChar = s.charAt(index); switch (durationChar) { case '-' : if ((context.decimalDuration == 0.0D) && (!context.isEndOfTie)) { context.isEndOfTie = true; logger.info("Note is end of tie"); } else { context.isStartOfTie = true; logger.info("Note is start of tie"); } break; case 'W' : durationNumber = 1; break; case 'H' : durationNumber = 2; break; case 'Q' : durationNumber = 4; break; case 'I' : durationNumber = 8; break; case 'S' : durationNumber = 16; break; case 'T' : durationNumber = 32; break; case 'X' : durationNumber = 64; break; case 'O' : durationNumber = 128; break; default : index--; moreDurationCharsToParse = false; break; } index++; if ((index < s.length()) && (s.charAt(index) == '.')) { isDotted = true; index++; } if (durationNumber > 0) { context.durationExplicitlySet = true; double d = 1.0/durationNumber; if (isDotted) { context.decimalDuration += d + (d/2.0); } else { context.decimalDuration += d; } } context.mostRecentDuration = durationNumber; if ((index < s.length()) && (s.charAt(index) >= '0') && (s.charAt(index) <= '9')) { index = parseQuantityDuration(s, index, context); } } else { moreDurationCharsToParse = false; } } return index; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseTuplet(String s, int index, NoteContext context) { if (index < s.length()) { if (s.charAt(index) == '*') { logger.info("Note is a tuplet"); index++; // Figure out tuplet ratio, or figure out when to stop looking for tuplet info boolean stopTupletParsing = false; int indexOfUnitsToMatch = 0; int indexOfNumNotes = 0; int counter = -1; while (!stopTupletParsing) { counter++; if (s.length() > index+counter) { if (s.charAt(index+counter) == ':') { indexOfNumNotes = index+counter+1; } else if ((s.charAt(index+counter) >= '0') && (s.charAt(index+counter) <= '9')) { if (indexOfUnitsToMatch == 0) { indexOfUnitsToMatch = index+counter; } } else if ((s.charAt(index+counter) == '*')) { // no op... artifact of parsing } else { stopTupletParsing = true; } } else { stopTupletParsing = true; } } index += counter; double numerator = 2.0d; double denominator = 3.0d; if ((indexOfUnitsToMatch > 0) && (indexOfNumNotes > 0)) { numerator = Double.parseDouble(s.substring(indexOfUnitsToMatch, indexOfNumNotes-1)); denominator = Double.parseDouble(s.substring(indexOfNumNotes, index)); } logger.info("Tuplet ratio is "+numerator+":"+denominator); double tupletRatio = numerator / denominator; context.decimalDuration = context.decimalDuration * (1.0d / tupletRatio); logger.info("Decimal duration after tuplet is " + context.decimalDuration); } } return index; } /** Returns the index with which to start parsing the next part of the string, once this method is done with its part */ private int parseVelocity(String s, int index, NoteContext context) { // Don't compute note velocity for a rest if (context.isRest) { return index; } // Process velocity attributes, if they exist while (index < s.length()) { int startPoint = index+1; int endPoint = startPoint; char velocityChar = s.charAt(index); int lengthOfByte = 0; if ((velocityChar == '+') || (velocityChar == '_') || (velocityChar == ' ')) break; logger.info("Identified Velocity character " + velocityChar); boolean byteDone = false; while (!byteDone && (index + lengthOfByte+1 < s.length())) { char possibleByteChar = s.charAt(index + lengthOfByte+1); if ((possibleByteChar >= '0') && (possibleByteChar <= '9')) { lengthOfByte++; } else { byteDone = true; } } endPoint = index + lengthOfByte+1; if (startPoint == endPoint) { return endPoint; } byte velocityNumber = Byte.parseByte(s.substring(startPoint,endPoint)); // Or maybe a bracketed string was passed in, instead of a byte String velocityString = null; if ((index+1 < s.length()) && (s.charAt(index+1) == '[')) { endPoint = s.indexOf(']',startPoint)+1; velocityString = s.substring(startPoint,endPoint); } switch (velocityChar) { case 'A' : if (velocityString == null) { context.noteOnVelocity = velocityNumber; } else { context.noteOnVelocityValueAsString = velocityString; } context.hasNoteOnVelocity = true; break; case 'D' : if (velocityString == null) { context.noteOffVelocity = velocityNumber; } else { context.noteOffVelocityValueAsString = velocityString; } context.hasNoteOffVelocity = true; break; default : throw new ParserException(StaccatoMessages.VELOCITY_CHARACTER_NOT_RECOGNIZED, s.substring(startPoint,endPoint)); } index = endPoint; } if (context.hasNoteOnVelocity) { logger.info("Attack velocity = " + context.noteOnVelocity); } if (context.hasNoteOffVelocity) { logger.info("Decay velocity = " + context.noteOffVelocity); } return index; } /** Returns the String of the next sub-token (the parts after + or _), if one exists; otherwise, returns null */ private int parseConnector(String s, int index, NoteContext context) { context.thereIsAnother = false; // See if there's another note to process if ((index < s.length()) && ((s.charAt(index) == '+') || (s.charAt(index) == '_'))) { logger.info("Another note: string = " + s.substring(index, s.length()-1)); if (s.charAt(index) == '_') { context.anotherNoteIsMelodic = true; logger.info("Next note will be melodic"); } else { context.anotherNoteIsHarmonic = true; logger.info("Next note will be harmonic"); } index++; context.thereIsAnother = true; } return index; } class NoteContext { public String originalString; public byte noteNumber; public String noteValueAsString; public boolean isNumericNote; public boolean isChord; public String chordName; public Intervals intervals; public int inversionCount; public String inversionBassNote; public boolean isRest; public boolean isNatural; public int octaveBias; public int octaveNumber; public int internalInterval; public double decimalDuration; public String durationValueAsString; public boolean durationExplicitlySet; public int mostRecentDuration; public boolean hasIndeterminateDuration; public boolean isEndOfTie; public boolean isStartOfTie; public boolean hasNoteOnVelocity; public byte noteOnVelocity; public String noteOnVelocityValueAsString; public boolean hasNoteOffVelocity; public byte noteOffVelocity; public String noteOffVelocityValueAsString; public boolean isFirstNote; public boolean isMelodicNote; public boolean anotherNoteIsMelodic; public boolean isHarmonicNote; public boolean anotherNoteIsHarmonic; public boolean thereIsAnother; /** * NoteContext should only be constructed when a note token is first being parsed. * Subsequent parsings of notes within the same token must create a NoteContext using * createNextNoteContext() */ public NoteContext() { this.isFirstNote = true; } /** * Must be called (instead of the constructor) for notes other than the first note * being parsed * @return */ public NoteContext createNextNoteContext() { NoteContext noteContext = new NoteContext(); noteContext.isFirstNote = false; noteContext.isMelodicNote = anotherNoteIsMelodic; noteContext.isHarmonicNote = anotherNoteIsHarmonic; return noteContext; } public NoteContext createChordNoteContext() { NoteContext noteContext = new NoteContext(); noteContext.isFirstNote = false; noteContext.isMelodicNote = false; noteContext.isHarmonicNote = true; noteContext.decimalDuration = decimalDuration; return noteContext; } /** * Creates a Note based on the settings in this NoteContext * @return Note */ public Note createNote(StaccatoParserContext parserContext) { try { if (noteValueAsString != null) { noteNumber = (Byte)parserContext.getDictionary().get(noteValueAsString); } } catch (NullPointerException e) { throw new RuntimeException("JFugue NoteSubparser: Could not find '"+noteValueAsString+"' in dictionary."); } try { if (durationValueAsString != null) { decimalDuration = (Byte)parserContext.getDictionary().get(durationValueAsString); } } catch (NullPointerException e) { throw new RuntimeException("JFugue NoteSubparser: Could not find '"+durationValueAsString+"' in dictionary."); } Note note = new Note(noteNumber); if (durationExplicitlySet) { note.setDuration(decimalDuration); } note.setOriginalString(originalString); note.setRest(isRest); if (hasNoteOnVelocity) { if (noteOnVelocityValueAsString != null) { noteOnVelocity = (Byte)parserContext.getDictionary().get(noteOnVelocityValueAsString); } note.setOnVelocity(noteOnVelocity); } if (hasNoteOffVelocity) { if (noteOffVelocityValueAsString != null) { noteOffVelocity = (Byte)parserContext.getDictionary().get(noteOffVelocityValueAsString); } note.setOffVelocity(noteOffVelocity); } note.setEndOfTie(isEndOfTie); note.setStartOfTie(isStartOfTie); note.setFirstNote(isFirstNote); note.setHarmonicNote(isHarmonicNote); note.setMelodicNote(isMelodicNote); return note; } /** * Creates a Note based on the settings in this NoteContext * @return Note */ public Chord createChord(StaccatoParserContext parserContext) { if (noteValueAsString != null) { noteNumber = (Byte)parserContext.getDictionary().get(noteValueAsString); } if (durationValueAsString != null) { decimalDuration = (Byte)parserContext.getDictionary().get(durationValueAsString); } Note rootNote = createNote(parserContext); if (isChord) { Chord chord = new Chord(rootNote, intervals); if (!(inversionBassNote == null)) { chord.setBassNote(inversionBassNote); } else if (inversionCount > 0) { chord.setInversion(inversionCount); } return chord; } return null; } } // // Methods from NoteProvider // @Override public Note createNote(String noteString) { StaccatoParserContext parserContext = new StaccatoParserContext(new StaccatoParser()); NoteContext noteContext = new NoteContext(); parseNoteElement(noteString, 0, noteContext, parserContext); return noteContext.createNote(parserContext); } @Override public Note getMiddleC() { return createNote("C"); } @Override public double getDurationForString(String s) { NoteContext noteContext = new NoteContext(); StaccatoParserContext parserContext = new StaccatoParserContext(new StaccatoParser()); this.parseDuration(s, 0, noteContext, parserContext); return noteContext.decimalDuration; } public static void populateContext(StaccatoParserContext context) { for (int i=0; i < Note.PERCUSSION_NAMES.length; i++) { context.getDictionary().put(Note.PERCUSSION_NAMES[i], (byte)(i+35)); } // Also give a hand to Chord! for (String key : Chord.chordMap.keySet()) { context.getDictionary().put(key, Chord.chordMap.get(key)); } } // // Method from ChordProvider // public Chord createChord(String chordString) { // If the user requested a chord like "C" or "Ab", assume it's MAJOR if (chordString.length() <= 2) { chordString = chordString + "MAJ"; } StaccatoParserContext parserContext = new StaccatoParserContext(new StaccatoParser()); NoteContext noteContext = new NoteContext(); parseNoteElement(chordString, 0, noteContext, parserContext); return noteContext.createChord(parserContext); } }