/*
* JFugue - API for Music Programming
* Copyright (C) 2003-2008 David Koelle
*
* http://www.jfugue.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.jfugue;
import java.util.HashMap;
import java.util.Map;
/**
* Parses music strings, and fires events for <code>ParserListener</code>
* interfaces when tokens are interpreted. The <code>ParserListener</code> does
* intelligent things with the resulting events, such as create music, draw
* sheet music, or transform the data.
*
* As of Version 3.0, the Parser supports turning MIDI Sequences into JFugue
* Patterns with the parse(Sequence) method. In this case, the ParserListeners
* established by a ParserBuilder use the parsed events to construct the Pattern
* string.
*
* @author David Koelle
* @version 3.0
* @version 4.0 - Note parsing split up into many separate methods; verification
* added for testing purposes
*/
public final class MusicStringParser extends Parser {
private Map<String, Object> dictionaryMap;
private byte keySig = 0;
/**
* Creates a new Parser object, and populates the dictionary with initial
* entries.
*
* @see JFugueDefinitions
*/
public MusicStringParser() {
dictionaryMap = new HashMap<String, Object>();
JFugueDefinitions.populateDictionary(dictionaryMap);
}
/**
* Parses a <code>Pattern</code> and fires events to subscribed
* <code>ParserListener</code> interfaces. As the Pattern is parsed, events
* are sent to <code>ParserLisener</code> interfaces, which are responsible
* for doing something interesting with the music data, such as playing the
* music, displaying it as sheet music, or transforming the pattern.
*
* <p>
* The parser breaks a music string into tokens, which are separated by
* spaces. It then determines the type of command based on the first
* character of the token. If the parser does not recognize the first
* character of the token, which is limited to the command letters (K, V, T,
* I, L, X, #, $, @, &, +, *, |), the notes (A, B, C, D, E, F, G, R), and
* the open-bracket character ( [ ), then the token will be ignored.
* </p>
*
* @param pattern
* the <code>Pattern</code> to parse
* @throws Exception
* if there is an error parsing the pattern
*/
public void parse(Pattern pattern) throws JFugueException {
String[] tokens = pattern.getTokens();
// If the user hasn't specified a tempo as the first token, use the
// default of 120
if (tokens.length > 0) {
if (tokens[0].toUpperCase().charAt(0) != 'T') {
parseTempoElement("T120");
}
}
int counter = 0;
for (int t = 0; t < tokens.length; t++) {
parseToken(tokens[t]);
counter++;
fireProgressReported("Parsing music string...", counter,
tokens.length);
}
}
/**
* This method takes a single token, and distributes it to a specific
* element parser based on the first character in the string. If the parser
* does not recognize the first character of the string, the token will be
* ignored.
*
* @param s
* the single token to parse
* @throws JFugueException
* if there is a problem parsing the string
*/
private void parseToken(String s) throws JFugueException {
// If there are any spaces, get out
if (s.indexOf(" ") != -1) {
throw new JFugueException(JFugueException.PARSER_SPACES_EXC, s, s);
}
s = s.toUpperCase();
trace("--------Processing Token: ", s);
switch (s.charAt(0)) {
case 'V':
parseVoiceElement(s);
break;
case 'T':
parseTempoElement(s);
break;
case 'I':
parseInstrumentElement(s);
break;
case 'L':
parseLayerElement(s);
break; // New in 3.0
case 'K':
parseKeySignatureElement(s);
break; // New in 3.0
case 'X':
parseControllerElement(s);
break; // New in 2.0
case '@':
parseTimeElement(s);
break; // New in 3.0
case '*':
parsePolyPressureElement(s);
break; // New in 3.0, also known as Key Pressure
case '+':
parseChannelPressureElement(s);
break; // New in 3.0
case '&':
parsePitchBendElement(s);
break; // New in 3.0
case '|':
parseMeasureElement(s);
break; // New in 3.0
case '$':
parseDictionaryElement(s);
break; // New in 2.0
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'R':
case '[':
parseNoteElement(s);
break;
default:
break; // Unknown characters are okay
}
}
/**
* Parses a voice element.
*
* @param s
* the token that contains a voice element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseVoiceElement(String s) throws JFugueException {
String voiceNumberString = s.substring(1, s.length());
byte voiceNumber = getByteFromDictionary(voiceNumberString);
if (voiceNumber > 15) {
// throw new
// JFugueException(JFugueException.VOICE_EXC,voiceNumberString,s);
return;
}
trace("Voice element: voice = ", voiceNumber);
fireVoiceEvent(new Voice(voiceNumber));
}
/**
* Parses a tempo element. As of JFugue 4.0, Tempo can be specified in Beats
* Per Minute, which is much more intuitive than the original Milliseconds
* Per Quarter Note. To maintain compatibility with existing JFugue Music
* Strings, those wishing to specify Tempo using BPM need to use the full
* word "Tempo" in their music string, instead of just the initial "T". To
* summarize: "Tempo120" (or "Tempo[Allegro]") --> Tempo will be is 120
* beats per minute "T120" --> Tempo will be 120 milliseconds per beat.
* Divide into 60000000 to get BPM.
*
* @param s
* the token that contains a tempo element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseTempoElement(String s) throws JFugueException {
String tempoNumberString = s.substring(1, s.length());
int tempoNumber = getIntFromDictionary(tempoNumberString);
trace("Tempo element: tempo = ", tempoNumber);
fireTempoEvent(new Tempo(tempoNumber));
}
/**
* Parses an instrument element.
*
* @param s
* the token that contains an instrument element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseInstrumentElement(String s) throws JFugueException {
String instrumentNumberString = s.substring(1, s.length());
byte instrumentNumber = getByteFromDictionary(instrumentNumberString);
trace("Instrument element: instrument = ", instrumentNumber);
fireInstrumentEvent(new Instrument(instrumentNumber));
}
/**
* Parses a layer element.
*
* @param s
* the token that contains a layer element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseLayerElement(String s) throws JFugueException {
String layerNumberString = s.substring(1, s.length());
byte layerNumber = getByteFromDictionary(layerNumberString);
trace("Layer element: layer = ", layerNumber);
fireLayerEvent(new Layer(layerNumber));
}
/**
* Parses a time element.
*
* @param s
* the token that contains a time element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseTimeElement(String s) throws JFugueException {
String timeNumberString = s.substring(1, s.length());
long timeNumber = getLongFromDictionary(timeNumberString);
trace("Time element: time = ", timeNumber);
fireTimeEvent(new Time(timeNumber));
}
/**
* Parses a key signature element.
*
* @param s
* the token that contains a key signature
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseKeySignatureElement(String s) throws JFugueException {
String rootNote = null;
String majOrMin = null;
if (s.length() == 5) {
rootNote = s.substring(1, 5);
majOrMin = s.substring(2, 5);
} else {
rootNote = s.substring(1, 6);
majOrMin = s.substring(3, 6);
}
trace("Key signature element: root=", rootNote, " majOrMin=", majOrMin);
if (!(majOrMin.equalsIgnoreCase("MAJ")
|| (majOrMin.equalsIgnoreCase("MIN")))) {
throw new JFugueException(JFugueException.KEYSIG_EXC, majOrMin, s);
}
int scale = (majOrMin.equalsIgnoreCase("MAJ") ? 0 : 1);
int keySig = 0;
if (rootNote.equalsIgnoreCase("CBMAJ")
|| rootNote.equalsIgnoreCase("ABMIN")) {
keySig = -7;
} else if (rootNote.equalsIgnoreCase("GBMAJ")
|| rootNote.equalsIgnoreCase("EBMIN")) {
keySig = -6;
} else if (rootNote.equalsIgnoreCase("DBMAJ")
|| rootNote.equalsIgnoreCase("BBMIN")) {
keySig = -5;
} else if (rootNote.equalsIgnoreCase("ABMAJ")
|| rootNote.equalsIgnoreCase("FMIN")) {
keySig = -4;
} else if (rootNote.equalsIgnoreCase("EBMAJ")
|| rootNote.equalsIgnoreCase("CMIN")) {
keySig = -3;
} else if (rootNote.equalsIgnoreCase("BBMAJ")
|| rootNote.equalsIgnoreCase("GMIN")) {
keySig = -2;
} else if (rootNote.equalsIgnoreCase("FMAJ")
|| rootNote.equalsIgnoreCase("DMIN")) {
keySig = -1;
} else if (rootNote.equalsIgnoreCase("CMAJ")
|| rootNote.equalsIgnoreCase("AMIN")) {
keySig = 0;
} else if (rootNote.equalsIgnoreCase("GMAJ")
|| rootNote.equalsIgnoreCase("EMIN")) {
keySig = +1;
} else if (rootNote.equalsIgnoreCase("DMAJ")
|| rootNote.equalsIgnoreCase("BMIN")) {
keySig = +2;
} else if (rootNote.equalsIgnoreCase("AMAJ")
|| rootNote.equalsIgnoreCase("F#MIN")) {
keySig = +3;
} else if (rootNote.equalsIgnoreCase("EMAJ")
|| rootNote.equalsIgnoreCase("C#MIN")) {
keySig = +4;
} else if (rootNote.equalsIgnoreCase("BMAJ")
|| rootNote.equalsIgnoreCase("G#MIN")) {
keySig = +5;
} else if (rootNote.equalsIgnoreCase("F#MAJ")
|| rootNote.equalsIgnoreCase("D#MIN")) {
keySig = +6;
} else if (rootNote.equalsIgnoreCase("C#MAJ")
|| rootNote.equalsIgnoreCase("A#MIN")) {
keySig = +7;
} else {
throw new JFugueException(JFugueException.KEYSIG_EXC, s);
}
trace("Key signature: sig=", keySig, " scale=", scale);
fireKeySignatureEvent(new KeySignature((byte) keySig, (byte) scale));
this.keySig = (byte) keySig;
}
/**
* Parses a measure element.
*
* @param s
* the token that contains a measure element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseMeasureElement(String s) throws JFugueException {
trace("Measure element.");
fireMeasureEvent(new Measure());
}
/**
* Parses a controller element.
*
* @param s
* the token that contains a controller element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseControllerElement(String s) throws JFugueException {
int indexOfEquals = s.indexOf("=");
if (-1 == indexOfEquals) {
throw new JFugueException(JFugueException.CONTROL_FORMAT_EXC, s, s);
}
//
// Get the Control Index from this token. The Control Index can be one
// of two things:
// 1. A byte. In this case, simply use the controller event referred to
// by that byte.
// 2. An int. In this case, the coarse adjuster is the high bits (div),
// and the fine adjuster is the low bits (mod).
//
String controlIndexString = s.substring(1, indexOfEquals);
byte controlIndex = 0;
int controlIndexInt = -1;
try {
controlIndex = getByteFromDictionary(controlIndexString);
} catch (JFugueException e) {
controlIndexInt = getIntFromDictionary(controlIndexString);
}
String controlValueString = s.substring(indexOfEquals + 1, s.length());
// An int was found as the Contoller Index number. Therefore, assume
// that the value passed to this Index is also an int, and should be
// divided among multiple controllers
if (-1 != controlIndexInt) {
int controlValue = getIntFromDictionary(controlValueString);
byte coarseIndex = (byte) (controlIndexInt / 128);
byte fineIndex = (byte) (controlIndexInt % 128);
// Special case for BANK_SELECT, which has a high byte of 0
if (16383 == controlValue) {
coarseIndex = 0;
fineIndex = 32;
}
byte coarseValue = (byte) (controlValue / 128);
byte fineValue = (byte) (controlValue % 128);
trace("Combined controller element: coarse-index = ", coarseIndex,
", coarse-value = ", coarseValue, "; fine-index = ",
fineIndex, ", fine-value = ", fineValue);
fireControllerEvent(new Controller(coarseIndex, coarseValue));
fireControllerEvent(new Controller(fineIndex, fineValue));
} else {
byte controlValue = getByteFromDictionary(controlValueString);
trace("Controller element: index = ", controlIndex, ", value = ",
controlValue);
fireControllerEvent(new Controller(controlIndex, controlValue));
}
}
/**
* Parses a channel pressure element.
*
* @param s
* the token that contains a channel pressure element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseChannelPressureElement(String s) throws JFugueException {
// A ChannelPressure token looks like this:
// +pressure
//
// where "pressure" can each be bytes or dictionary items
String pressureString = s.substring(1, s.length());
byte pressureNumber = getByteFromDictionary(pressureString);
trace("ChannelPressure element: pressure = ", pressureNumber);
fireChannelPressureEvent(new ChannelPressure(pressureNumber));
}
/**
* Parses a polyphonic pressure element.
*
* @param s
* the token that contains a polyphonic pressure element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parsePolyPressureElement(String s) throws JFugueException {
// A PolyphonicPressure token looks like this:
// *key,pressure
//
// where "key" and "pressure" can each be bytes or dictionary items
String keyString = s.substring(1, s.indexOf(','));
byte keyNumber = getByteFromDictionary(keyString);
String pressureString = s.substring(s.indexOf(',') + 1, s.length());
byte pressureNumber = getByteFromDictionary(pressureString);
trace("PolyphonicPressure element: key = ", keyNumber, ", pressure = ",
pressureNumber);
firePolyphonicPressureEvent(
new PolyphonicPressure(keyNumber, pressureNumber));
}
/**
* Parses a pitch bend element.
*
* @param s
* the token that contains a pitch bend pressure element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parsePitchBendElement(String s) throws JFugueException {
// A PitchBend token looks like one of the following:
// &lsb,msb
// &int
//
// where "byte1" and "byte2" or "int" can be bytes/ints or dictionary
// items
byte lsb = 0;
byte msb = 0;
if (s.indexOf(',') > -1) {
// We're dealing with two bytes
String b1String = s.substring(1, s.indexOf(','));
lsb = getByteFromDictionary(b1String);
String b2String = s.substring(s.indexOf(',') + 1, s.length());
msb = getByteFromDictionary(b2String);
} else {
// We're dealing with a single integer, which we will break into
// bytes
String valueString = s.substring(1, s.length());
int value = getIntFromDictionary(valueString);
lsb = (byte) (value % 128);
msb = (byte) (value / 128);
}
trace("PitchBend element: byte1 = ", lsb, ", byte2 = ", msb);
firePitchBendEvent(new PitchBend(lsb, msb));
}
/**
* Parses a dictionary element.
*
* @param s
* the token that contains a dictionary element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseDictionaryElement(String s) throws JFugueException {
int indexOfEquals = s.indexOf("=");
String word = s.substring(1, indexOfEquals);
String definition = s.substring(indexOfEquals + 1, s.length());
// Replace tilde's with spaces. I don't think this will work, though,
// since the
// MusicString has already been tokenized.
// definition.replace('~', ' ');
word = word.toUpperCase();
trace("Dictionary Definition element: word = ", word, ", value = ",
definition);
dictionaryMap.put(word, definition);
}
class NoteContext {
boolean isRest = false;
boolean isNumericNote = false;
boolean isChord = false;
boolean isFirstNote = true;
boolean isSequentialNote = false;
boolean isParallelNote = false;
boolean isNatural = false;
boolean existAnotherNote = true;
boolean anotherNoteIsSequential = false;
boolean anotherNoteIsParallel = false;
boolean isStartOfTie = false;
boolean isEndOfTie = false;
byte[] halfsteps = new byte[5];
byte numHalfsteps = 0;
byte noteNumber = 0;
int octaveNumber = 0;
double decimalDuration = 0.0;
long duration = 0L;
byte attackVelocity = Note.DEFAULT_VELOCITY;
byte decayVelocity = Note.DEFAULT_VELOCITY;
public NoteContext() {
for (int i = 0; i < 5; i++) {
halfsteps[i] = 0;
}
}
}
/**
* Parses a note element.
*
* @param s
* the token that contains a note element
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseNoteElement(String s) throws JFugueException {
NoteContext context = new NoteContext();
while (context.existAnotherNote) {
trace("--Parsing note from token " + s);
decideSequentialOrParallel(context);
int index = 0;
int slen = s.length(); // We pass the length of the string because
// it is an invariant value that is used
// often
index = parseNoteRoot(s, slen, index, context);
index = parseNoteOctave(s, slen, index, context);
index = parseNoteChord(s, slen, index, context);
computeNoteValue(context);
index = parseNoteChordInversion(s, slen, index, context);
index = parseNoteDuration(s, slen, index, context);
index = parseNoteVelocity(s, slen, index, context);
s = parseNoteConnector(s, slen, index, context);
fireNoteEvents(context);
}
}
private void decideSequentialOrParallel(NoteContext context) {
// Test whether this note is already known to be sequential (was
// connected with _) or parallel (was connected with +)
context.isSequentialNote = false;
if (context.anotherNoteIsSequential) {
context.isSequentialNote = true;
context.anotherNoteIsSequential = false;
trace("This note is sequential");
}
context.isParallelNote = false;
if (context.anotherNoteIsParallel) {
context.isParallelNote = true;
context.anotherNoteIsParallel = false;
trace("This note is parallel");
}
}
/**
* Returns the index with which to start parsing the next part of the
* string, once this method is done with its part
*/
private int parseNoteRoot(String s, int slen, int index,
NoteContext context) {
switch (s.charAt(index)) {
case '[':
return parseNumericNote(s, slen, index, context);
case 'R':
return parseRest(s, slen, index, context);
default:
return parseLetterNote(s, slen, index, context);
}
}
/**
* 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 slen, int index,
NoteContext context) {
int indexOfEndBracket = s.indexOf(']', index);
String stringInBrackets = s.substring(1, indexOfEndBracket);
context.noteNumber = getByteFromDictionary(stringInBrackets);
context.isNumericNote = true;
trace("This note is a numeric note with value ", context.noteNumber);
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 parseRest(String s, int slen, int index, NoteContext context) {
context.isRest = true;
trace("This note is a Rest");
return index + 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 parseLetterNote(String s, int slen, int index,
NoteContext context) {
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:
throw new JFugueException(JFugueException.NOTE_EXC, s);
}
index++;
// Check for #, b, or n (sharp, flat, or natural) modifier
boolean checkForModifiers = true;
while (checkForModifiers) {
if (index < slen) {
switch (s.charAt(index)) {
case '#':
index++;
context.noteNumber++;
/* if (context.noteNumber == 12) context.noteNumber = 0; */ break;
case 'B':
index++;
context.noteNumber--;
/* if (context.noteNumber == -1) context.noteNumber = 11; */ break;
case 'N':
index++;
context.isNatural = true;
checkForModifiers = false;
break;
default:
checkForModifiers = false;
break;
}
} else {
checkForModifiers = false;
}
}
trace("Note number within an octave (C=0, B=11): ", context.noteNumber);
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 static int parseNoteOctave(String s, int slen, 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.
char possibleOctave1 = '.';
char possibleOctave2 = '.';
if (index < slen) {
possibleOctave1 = s.charAt(index);
}
if (index + 1 < slen) {
possibleOctave2 = s.charAt(index + 1);
}
byte definiteOctaveLength = 0;
if ((possibleOctave1 >= '0') && (possibleOctave1 <= '9')) {
definiteOctaveLength = 1;
if ((possibleOctave2 >= '0') && (possibleOctave2 <= '9')) {
definiteOctaveLength = 2;
}
String octaveNumberString = s.substring(index,
index + definiteOctaveLength);
try {
context.octaveNumber = Byte.parseByte(octaveNumberString);
} catch (NumberFormatException e) {
throw new JFugueException(JFugueException.OCTAVE_EXC,
octaveNumberString, s);
}
if (context.octaveNumber > 10) {
throw new JFugueException(JFugueException.OCTAVE_EXC,
octaveNumberString, s);
}
}
return index + definiteOctaveLength;
}
/**
* Returns the index with which to start parsing the next part of the
* string, once this method is done with its part
*/
private int parseNoteChord(String s, int slen, int index,
NoteContext context) {
// Don't parse chord for a rest
if (context.isRest) {
return index;
}
String possibleChord3 = null;
String possibleChord4 = null;
String possibleChord5 = null;
String possibleChord6 = null;
String possibleChord7 = null;
String possibleChord8 = null;
try {
possibleChord3 = s.substring(index, index + 3);
possibleChord4 = s.substring(index, index + 4);
possibleChord5 = s.substring(index, index + 5);
possibleChord6 = s.substring(index, index + 6);
possibleChord7 = s.substring(index, index + 7);
possibleChord8 = s.substring(index, index + 8);
} catch (IndexOutOfBoundsException e) {
// Nothing to do... just needed to catch
}
int lengthOfChordString = 0; // This represents the length of the
// string, not the number of halfsteps
// Below, 'chordLength' refers to the size of the text for the chord
// (for example, "min"=3, "dim7"=4),
// and 'numHalfsteps' refers to the number of elements in the halfsteps
// array.
// This must be done in order from smaller to larger strings, so the
// longer string names
// take effect. This means 'min' can be overwritten by 'minmaj7', or
// 'maj' by 'maj7', for example.
if (possibleChord3 != null) {
if (possibleChord3.equals("MAJ")) {
lengthOfChordString = 3;
context.numHalfsteps = 2;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
} else if (possibleChord3.equals("MIN")) {
lengthOfChordString = 3;
context.numHalfsteps = 2;
context.halfsteps[0] = 3;
context.halfsteps[1] = 7;
} else if (possibleChord3.equals("AUG")) {
lengthOfChordString = 3;
context.numHalfsteps = 2;
context.halfsteps[0] = 4;
context.halfsteps[1] = 8;
} else if (possibleChord3.equals("DIM")) {
lengthOfChordString = 3;
context.numHalfsteps = 2;
context.halfsteps[0] = 3;
context.halfsteps[1] = 6;
}
}
if (possibleChord4 != null) {
if (possibleChord4.equalsIgnoreCase("DOM7")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
context.halfsteps[2] = 10;
} else if (possibleChord4.equalsIgnoreCase("MAJ7")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
context.halfsteps[2] = 11;
} else if (possibleChord4.equalsIgnoreCase("MIN7")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 3;
context.halfsteps[1] = 7;
context.halfsteps[2] = 10;
} else if (possibleChord4.equalsIgnoreCase("SUS4")) {
lengthOfChordString = 4;
context.numHalfsteps = 2;
context.halfsteps[0] = 5;
context.halfsteps[1] = 7;
} else if (possibleChord4.equalsIgnoreCase("SUS2")) {
lengthOfChordString = 4;
context.numHalfsteps = 2;
context.halfsteps[0] = 2;
context.halfsteps[1] = 7;
} else if (possibleChord4.equalsIgnoreCase("MAJ6")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
context.halfsteps[2] = 9;
} else if (possibleChord4.equalsIgnoreCase("MIN6")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 3;
context.halfsteps[1] = 7;
context.halfsteps[2] = 9;
} else if (possibleChord4.equalsIgnoreCase("DOM9")) {
lengthOfChordString = 4;
context.numHalfsteps = 4;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
context.halfsteps[2] = 10;
context.halfsteps[3] = 14;
} else if (possibleChord4.equalsIgnoreCase("MAJ9")) {
lengthOfChordString = 4;
context.numHalfsteps = 4;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
context.halfsteps[2] = 11;
context.halfsteps[3] = 14;
} else if (possibleChord4.equalsIgnoreCase("MIN9")) {
lengthOfChordString = 4;
context.numHalfsteps = 4;
context.halfsteps[0] = 3;
context.halfsteps[1] = 7;
context.halfsteps[2] = 10;
context.halfsteps[3] = 14;
} else if (possibleChord4.equalsIgnoreCase("DIM7")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 3;
context.halfsteps[1] = 6;
context.halfsteps[2] = 9;
} else if (possibleChord4.equalsIgnoreCase("ADD9")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 7;
context.halfsteps[2] = 14;
} else if (possibleChord4.equalsIgnoreCase("DAVE")) {
lengthOfChordString = 4;
context.numHalfsteps = 3;
context.halfsteps[0] = 7;
context.halfsteps[1] = 14;
context.halfsteps[2] = 21;
}
}
if (possibleChord5 != null) {
if (possibleChord5.equalsIgnoreCase("MIN11")) {
lengthOfChordString = 5;
context.numHalfsteps = 5;
context.halfsteps[0] = 7;
context.halfsteps[1] = 10;
context.halfsteps[2] = 14;
context.halfsteps[3] = 15;
context.halfsteps[4] = 17;
} else if (possibleChord5.equalsIgnoreCase("DOM11")) {
lengthOfChordString = 5;
context.numHalfsteps = 4;
context.halfsteps[0] = 7;
context.halfsteps[1] = 10;
context.halfsteps[2] = 14;
context.halfsteps[3] = 17;
} else if (possibleChord5.equalsIgnoreCase("DOM13")) {
lengthOfChordString = 5;
context.numHalfsteps = 5;
context.halfsteps[0] = 7;
context.halfsteps[1] = 10;
context.halfsteps[2] = 14;
context.halfsteps[3] = 16;
context.halfsteps[4] = 21;
} else if (possibleChord5.equalsIgnoreCase("MIN13")) {
lengthOfChordString = 5;
context.numHalfsteps = 5;
context.halfsteps[0] = 7;
context.halfsteps[1] = 10;
context.halfsteps[2] = 14;
context.halfsteps[3] = 15;
context.halfsteps[4] = 21;
} else if (possibleChord5.equalsIgnoreCase("MAJ13")) {
lengthOfChordString = 5;
context.numHalfsteps = 5;
context.halfsteps[0] = 7;
context.halfsteps[1] = 11;
context.halfsteps[2] = 14;
context.halfsteps[3] = 16;
context.halfsteps[4] = 21;
}
}
if (possibleChord6 != null) {
if (possibleChord6.equalsIgnoreCase("DOM7<5")) {
lengthOfChordString = 6;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 6;
context.halfsteps[2] = 10;
} else if (possibleChord6.equalsIgnoreCase("DOM7>5")) {
lengthOfChordString = 6;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 8;
context.halfsteps[2] = 10;
} else if (possibleChord6.equalsIgnoreCase("MAJ7<5")) {
lengthOfChordString = 6;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 6;
context.halfsteps[2] = 11;
} else if (possibleChord6.equalsIgnoreCase("MAJ7>5")) {
lengthOfChordString = 6;
context.numHalfsteps = 3;
context.halfsteps[0] = 4;
context.halfsteps[1] = 8;
context.halfsteps[2] = 11;
}
}
if (possibleChord7 != null) {
if (possibleChord7.equalsIgnoreCase("minmaj7")) {
lengthOfChordString = 7;
context.numHalfsteps = 3;
context.halfsteps[0] = 3;
context.halfsteps[1] = 7;
context.halfsteps[2] = 11;
}
}
if (possibleChord8 != null) {
if (possibleChord8.equalsIgnoreCase("DOM7<5<9")) {
lengthOfChordString = 8;
context.numHalfsteps = 4;
context.halfsteps[0] = 4;
context.halfsteps[1] = 6;
context.halfsteps[2] = 10;
context.halfsteps[3] = 13;
} else if (possibleChord8.equalsIgnoreCase("DOM7<5>9")) {
lengthOfChordString = 8;
context.numHalfsteps = 4;
context.halfsteps[0] = 4;
context.halfsteps[1] = 6;
context.halfsteps[2] = 10;
context.halfsteps[3] = 15;
} else if (possibleChord8.equalsIgnoreCase("DOM7>5<9")) {
lengthOfChordString = 8;
context.numHalfsteps = 4;
context.halfsteps[0] = 4;
context.halfsteps[1] = 8;
context.halfsteps[2] = 10;
context.halfsteps[3] = 13;
} else if (possibleChord8.equalsIgnoreCase("DOM7>5>9")) {
lengthOfChordString = 8;
context.numHalfsteps = 4;
context.halfsteps[0] = 4;
context.halfsteps[1] = 8;
context.halfsteps[2] = 10;
context.halfsteps[3] = 15;
}
}
if (lengthOfChordString > 0) {
context.isChord = true;
trace("Chord: chordLength=", lengthOfChordString,
", so chord is one of the following: [ 3=", possibleChord3,
" 4=", possibleChord4, " 5=", possibleChord5, " 6=",
possibleChord6, " 7=", possibleChord7, " 8=",
possibleChord8, " ]");
}
return index + lengthOfChordString;
}
/**
* This method does a variety of calculations to get the actual value of the
* note.
*/
private void computeNoteValue(NoteContext context) {
// Don't compute note value for a rest
if (context.isRest) {
return;
}
// If we happen not to have an octave yet, set it to a default value.
// Default octave: 5 for notes, 3 for chords
if ((context.octaveNumber == 0) && (!context.isNumericNote)) {
if (context.isChord) {
context.octaveNumber = 3;
} else {
context.octaveNumber = 5;
}
}
trace("Octave: ", context.octaveNumber);
// Adjust for Key Signature
if ((keySig != 0) && (!context.isNatural)) {
if ((keySig <= -1) && (context.noteNumber == 11)) {
context.noteNumber = 10;
}
if ((keySig <= -2) && (context.noteNumber == 4)) {
context.noteNumber = 3;
}
if ((keySig <= -3) && (context.noteNumber == 9)) {
context.noteNumber = 8;
}
if ((keySig <= -4) && (context.noteNumber == 2)) {
context.noteNumber = 1;
}
if ((keySig <= -5) && (context.noteNumber == 7)) {
context.noteNumber = 6;
}
if ((keySig <= -6) && (context.noteNumber == 0)) {
context.noteNumber = 11;
context.octaveNumber--;
}
if ((keySig <= -7) && (context.noteNumber == 5)) {
context.noteNumber = 4;
}
if ((keySig >= +1) && (context.noteNumber == 5)) {
context.noteNumber = 6;
}
if ((keySig >= +2) && (context.noteNumber == 0)) {
context.noteNumber = 1;
}
if ((keySig >= +3) && (context.noteNumber == 7)) {
context.noteNumber = 8;
}
if ((keySig >= +4) && (context.noteNumber == 2)) {
context.noteNumber = 3;
}
if ((keySig >= +5) && (context.noteNumber == 9)) {
context.noteNumber = 10;
}
if ((keySig >= +6) && (context.noteNumber == 4)) {
context.noteNumber = 5;
}
if ((keySig >= +7) && (context.noteNumber == 11)) {
context.noteNumber = 0;
context.octaveNumber++;
}
trace("After adjusting for Key Signature, noteNumber=",
context.noteNumber, " octave=", context.octaveNumber);
}
// Compute the actual note number, based on octave and note
if (!context.isNumericNote) {
int intNoteNumber = (context.octaveNumber * 12)
+ context.noteNumber;
if (intNoteNumber > 127) {
throw new JFugueException(JFugueException.NOTE_OCTAVE_EXC,
Integer.toString(intNoteNumber), "");
}
context.noteNumber = (byte) intNoteNumber;
trace("Computed note number: ", context.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 parseNoteChordInversion(String s, int slen, int index,
NoteContext context) {
if (!context.isChord) {
return index;
}
int inversionCount = 0;
int inversionRootNote = -1;
int inversionOctave = -1;
boolean checkForInversion = true;
while (checkForInversion) {
if (index < slen) {
switch (s.charAt(index)) {
case '^':
index++;
inversionCount++;
break;
case 'C':
index++;
inversionRootNote = 0;
break;
case 'D':
index++;
inversionRootNote = 2;
break;
case 'E':
index++;
inversionRootNote = 4;
break;
case 'F':
index++;
inversionRootNote = 5;
break;
case 'G':
index++;
inversionRootNote = 7;
break;
case 'A':
index++;
inversionRootNote = 9;
break;
// For 'B', need to differentiate between B note and 'b' flat
case 'B':
index++;
if (inversionRootNote == -1) {
inversionRootNote = 11;
} else {
inversionRootNote--;
}
break;
case '#':
index++;
inversionRootNote++;
break;
// For '0', need to differentiate between initial 0 and 0 as a
// second digit (i.e., 10)
case '0':
index++;
if (inversionOctave == -1) {
inversionOctave = 0;
} else {
inversionOctave = inversionOctave * 10;
}
break;
case '1':
index++;
inversionOctave = 1;
break;
case '2':
index++;
inversionOctave = 2;
break;
case '3':
index++;
inversionOctave = 3;
break;
case '4':
index++;
inversionOctave = 4;
break;
case '5':
index++;
inversionOctave = 5;
break;
case '6':
index++;
inversionOctave = 6;
break;
case '7':
index++;
inversionOctave = 7;
break;
case '8':
index++;
inversionOctave = 8;
break;
case '9':
index++;
inversionOctave = 9;
break;
// If [, whoo boy, we're checking for a note number
case '[':
int indexEndBracket = s.indexOf(']', index);
inversionRootNote = Integer.parseInt(
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 (inversionCount > 0) {
if (inversionRootNote == -1) {
// The root is determined by a number of carets. Increase each
// half-step
// before the inversion by 12, the number of notes in an octave.
trace("Inversion is base on count: " + inversionCount);
trace("Inverting " + context.noteNumber + " to be "
+ (context.noteNumber + 12));
context.noteNumber += 12;
for (int i = inversionCount
- 1; i < context.numHalfsteps; i++) {
trace("Inverting " + context.halfsteps[i] + " to be "
+ (context.halfsteps[i] - 12));
context.halfsteps[i] -= 12;
}
} else {
// The root is determined by an inversionRoot. This is much
// trickier, but we can
// still figure it out.
if (inversionOctave != -1) {
inversionRootNote += inversionOctave * 12;
} else if (inversionRootNote < 12) {
int currentOctave = context.noteNumber / 12;
inversionRootNote += currentOctave * 12;
}
// Otherwise, inversionRootNote is a numeric note value, like
// [60]
trace("Inversion is base on note: " + inversionRootNote);
if ((inversionRootNote > context.noteNumber
+ context.halfsteps[context.numHalfsteps - 1])
|| (inversionRootNote < context.noteNumber)) {
throw new JFugueException(JFugueException.INVERSION_EXC);
}
trace("Inverting " + context.noteNumber + " to be "
+ (context.noteNumber + 12));
context.noteNumber += 12;
for (int i = 0; i < context.numHalfsteps; i++) {
if (context.noteNumber
+ context.halfsteps[i] >= inversionRootNote + 12) {
trace("Inverting " + context.halfsteps[i] + " to be "
+ (context.halfsteps[i] - 12));
context.halfsteps[i] -= 12;
}
}
}
}
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 parseNoteDuration(String s, int slen, int index,
NoteContext context) {
context.decimalDuration = 0.0;
if (index < slen) {
switch (s.charAt(index)) {
case '/':
index = parseNumericDuration(s, slen, index, context);
break;
case 'W':
case 'H':
case 'Q':
case 'I':
case 'S':
case 'T':
case 'X':
case 'O':
case '-':
index = parseLetterDuration(s, slen, index, context);
break;
default:
break;
}
index = parseTuplet(s, slen, index, context);
} else {
context.decimalDuration = 1.0 / 4.0; // Default duration is a
// quarter note
}
// context.duration = (long) (120.0 * 4.0 * context.decimalDuration); //
// javax.sound.midi.Sequence resolution is 120
context.duration = (long) (120.0 * context.decimalDuration); // DMK
// 9/27/08:
// The
// *4.0
// makes
// quarter
// notes
// 4
// times
// as
// long
// as
// they
// should
// be
// // Below is incorrect, as identified by M. Ahluwalia
// // Tempo is now in Beats Per Minute. Convert this to Pulses Per
// Quarter (PPQ), then to
// // Pulses Per Whole (PPW), then multiply that by durationNumber for
// WHQITXN notes
// double ppq = 60000000.0D / (double)this.getTempo();
// double ppw = ppq * 4.0; // 4 quarter notes in a whole note
// context.duration = (long)(ppw * context.decimalDuration) / 4000;
trace("Decimal duration is ", context.decimalDuration);
trace("Actual duration is ", context.duration);
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 parseLetterDuration(String s, int slen, int index,
NoteContext context) {
// Check duration
boolean durationExists = true;
boolean isDotted = false;
while (durationExists == true) {
int durationNumber = 0;
// See if the note has a duration
// Duration is optional; default is Q (4)
if (index < slen) {
char durationChar = s.charAt(index);
switch (durationChar) {
case '-':
if ((context.decimalDuration == 0)
&& (!context.isEndOfTie)) {
context.isEndOfTie = true;
trace("Note is end of tie");
} else {
context.isStartOfTie = true;
trace("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--;
durationExists = false;
break;
}
index++;
if ((index < slen) && (s.charAt(index) == '.')) {
isDotted = true;
index++;
}
if (durationNumber > 0) {
double d = 1.0 / durationNumber;
if (isDotted) {
context.decimalDuration += d + (d / 2.0);
} else {
context.decimalDuration += d;
}
}
} else {
durationExists = 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 parseNumericDuration(String s, int slen, 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++;
// Decimal duration is not required to be enclosed by brackets,
// but since most of the other numerical input to a MusicString
// is required to be in brackets, we should support it.
if ('[' == s.charAt(index)) {
int indexOfEndingBracket = s.indexOf(']', index);
context.decimalDuration += getDoubleFromDictionary(
s.substring(index + 1, indexOfEndingBracket));
index = indexOfEndingBracket + 1;
} else {
int endingIndex = index;
boolean keepAdvancingPointer = true;
while (keepAdvancingPointer) {
try {
char numericDurationChar = s.charAt(endingIndex);
if ((numericDurationChar >= '0')
&& (numericDurationChar <= '9')
|| (numericDurationChar == '.')) // Decimal dot, not
// dotted
// duration
{
endingIndex++;
} else {
keepAdvancingPointer = false;
}
} catch (IndexOutOfBoundsException e) {
keepAdvancingPointer = false;
}
}
String durationNumberString = s.substring(index, endingIndex);
context.decimalDuration += Double.parseDouble(durationNumberString);
index = endingIndex;
}
trace("Decimal duration 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 parseTuplet(String s, int slen, int index,
NoteContext context) {
if (index < slen) {
if (s.charAt(index) == '*') {
trace("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 (slen > 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.0;
double denominator = 3.0;
if ((indexOfUnitsToMatch > 0) && (indexOfNumNotes > 0)) {
numerator = Double.parseDouble(s.substring(
indexOfUnitsToMatch, indexOfNumNotes - 1));
denominator = Double
.parseDouble(s.substring(indexOfNumNotes, index));
}
trace("Tuplet ratio is " + numerator + ":" + denominator);
double tupletRatio = numerator / denominator;
context.decimalDuration = context.decimalDuration * tupletRatio;
trace("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 parseNoteVelocity(String s, int slen, 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 < slen) {
int startPoint = index + 1;
int endPoint = startPoint;
char velocityChar = s.charAt(index);
int lengthOfByte = 0;
if ((velocityChar == '+') || (velocityChar == '_')) {
break;
}
trace("Identified Velocity character ", velocityChar);
boolean byteDone = false;
while (!byteDone && (index + lengthOfByte + 1 < slen)) {
char possibleByteChar = s.charAt(index + lengthOfByte + 1);
if ((possibleByteChar >= '0') && (possibleByteChar <= '9')) {
lengthOfByte++;
} else {
byteDone = true;
}
}
endPoint = index + lengthOfByte + 1;
// Or maybe a bracketed string was passed in, instead of a byte
if ((index + 1 < slen) && (s.charAt(index + 1) == '[')) {
endPoint = s.indexOf(']', startPoint) + 1;
}
byte velocityNumber = getByteFromDictionary(
s.substring(startPoint, endPoint));
switch (velocityChar) {
case 'A':
context.attackVelocity = velocityNumber;
break;
case 'D':
context.decayVelocity = velocityNumber;
break;
default:
throw new JFugueException(JFugueException.NOTE_VELOCITY_EXC,
s.substring(startPoint, endPoint), s);
}
index = endPoint;
}
trace("Attack velocity = ", context.attackVelocity,
"; Decay velocity = ", context.decayVelocity);
return index;
}
/**
* Returns the String of the next sub-token (the parts after + or _), if one
* exists; otherwise, returns null
*/
private String parseNoteConnector(String s, int slen, int index,
NoteContext context) {
context.existAnotherNote = false;
// See if there's another note to process
if ((index < slen)
&& ((s.charAt(index) == '+') || (s.charAt(index) == '_'))) {
trace("Another note: string = ",
s.substring(index, s.length() - 1));
if (s.charAt(index) == '_') {
context.anotherNoteIsSequential = true;
trace("Next note will be sequential");
} else {
context.anotherNoteIsParallel = true;
trace("Next note will be parallel");
}
index++;
context.existAnotherNote = true;
return s.substring(index, slen);
}
return null;
}
private void fireNoteEvents(NoteContext context) {
// Set up the note
Note note = new Note();
if (context.isRest) {
note.setRest(true);
note.setDuration(context.duration);
note.setDecimalDuration(context.decimalDuration);
note.setAttackVelocity((byte) 0); // turn off sound for rest notes
note.setDecayVelocity((byte) 0);
} else {
note.setValue(context.noteNumber);
note.setDuration(context.duration);
note.setStartOfTie(context.isStartOfTie);
note.setEndOfTie(context.isEndOfTie);
note.setDecimalDuration(context.decimalDuration);
note.setAttackVelocity(context.attackVelocity);
note.setDecayVelocity(context.decayVelocity);
}
note.setHasAccompanyingNotes(
context.existAnotherNote || context.isChord);
// Fire note events
if (context.isFirstNote) {
note.setType(Note.FIRST);
trace("Firing first note event");
fireNoteEvent(note);
} else if (context.isSequentialNote) {
note.setType(Note.SEQUENTIAL);
trace("Firing sequential note event");
fireSequentialNoteEvent(note);
} else if (context.isParallelNote) {
note.setType(Note.PARALLEL);
trace("Firing parallel note event");
fireParallelNoteEvent(note);
}
if (context.isChord) {
for (int i = 0; i < context.numHalfsteps; i++) {
Note chordNote = new Note(
(byte) (context.noteNumber + context.halfsteps[i]),
context.duration);
chordNote.setDecimalDuration(context.decimalDuration); // This
// won't
// have
// any
// effect
// on
// the
// note,
// but
// it's
// good
// bookkeeping
// to
// have
// it
// around.
chordNote.setType(Note.PARALLEL);
trace("Chord note number: ",
(context.noteNumber + context.halfsteps[i]));
if (i == context.numHalfsteps - 1) {
chordNote.setHasAccompanyingNotes(context.existAnotherNote);
} else {
chordNote.setHasAccompanyingNotes(
context.existAnotherNote || context.isChord);
}
fireParallelNoteEvent(chordNote);
}
}
context.isFirstNote = false;
}
/**
* Looks up a string's value in the dictionary. The dictionary is used to
* keep memorable names of obscure numbers - for example, the string FLUTE
* is set to a value of 73, so when users want to play music with a flute,
* they can say "I[Flute]" instead of "I[73]".
*
* <p>
* The Dictionary feature also lets users define constants so that if the
* value of something were to change, it only needs to be changed in one
* place. For example, MY_FAVORITE_INSTRUMENT could be set to 73, then you
* can say "I[My_Favorite_Instrument]" when you want to play with that
* instrument. If your favorite instrument were ever to change, you only
* have to make the change in one place, instead of every place where you
* give the Instrument command.
* </p>
*
* @param bracketedString
* the string to look up in the dictionary
* @returns the definition of the string
* @throws JFugueException
* if there is a problem looking up bracketedString
*/
private String dictionaryLookup(String bracketedString)
throws JFugueException {
int indexOfOpeningBracket = bracketedString.indexOf("[");
int indexOfClosingBracket = bracketedString.indexOf("]");
String word = null;
if ((indexOfOpeningBracket != -1) && (indexOfClosingBracket != -1)) {
word = bracketedString.substring(indexOfOpeningBracket + 1,
indexOfClosingBracket);
} else {
// It appears that "bracketedString" wasn't bracketed.
word = bracketedString;
}
word = word.toUpperCase();
String definition = (String) dictionaryMap.get(word);
while ((definition != null)
&& (dictionaryMap.containsKey(definition.toUpperCase()))) {
definition = (String) dictionaryMap.get(definition.toUpperCase());
}
// If there is no definition for this word, see if the word is actually
// a number.
if (null == definition) {
char ch = 0;
boolean isNumber = true;
for (int i = 0; i < word.length(); i++) {
ch = word.charAt(i);
if ((!Character.isDigit(ch) && (ch != '.'))) {
isNumber = false;
}
}
if (isNumber) {
trace("Dictionary lookup returning the number ", word);
return word;
}
throw new JFugueException(JFugueException.WORD_NOT_DEFINED_EXC,
word, bracketedString);
}
trace("Word ", word, " is defined as ", definition);
return definition;
}
/**
* Look up a byte from the dictionary
*
* @param bracketedString
* the string to look up
* @returns the byte value of the definition
* @throws JFugueException
* if there is a problem getting a byte from the dictionary
* look-up
*/
private byte getByteFromDictionary(String bracketedString)
throws JFugueException {
String definition = dictionaryLookup(bracketedString);
Byte newbyte = null;
try {
newbyte = new Byte(definition);
} catch (NumberFormatException e) {
throw new JFugueException(JFugueException.EXPECTED_BYTE, definition,
bracketedString);
}
return newbyte.byteValue();
}
/**
* Look up a long from the dictionary
*
* @param bracketedString
* the string to look up
* @returns the long value of the definition
* @throws JFugueException
* if there is a problem getting a long from the dictionary
* look-up
*/
private long getLongFromDictionary(String bracketedString)
throws JFugueException {
String definition = dictionaryLookup(bracketedString);
Long newlong = null;
try {
newlong = new Long(definition);
} catch (NumberFormatException e) {
throw new JFugueException(JFugueException.EXPECTED_LONG, definition,
bracketedString);
}
return newlong.longValue();
}
/**
* Look up an int from the dictionary
*
* @param bracketedString
* the string to look up
* @returns the int value of the definition
* @throws JFugueException
* if there is a problem getting a int from the dictionary
* look-up
*/
private int getIntFromDictionary(String bracketedString)
throws JFugueException {
String definition = dictionaryLookup(bracketedString);
Integer newint = null;
try {
newint = Integer.valueOf(definition);
} catch (NumberFormatException e) {
throw new JFugueException(JFugueException.EXPECTED_INT, definition,
bracketedString);
}
return newint.intValue();
}
/**
* Look up a double from the dictionary
*
* @param bracketedString
* the string to look up
* @returns the double value of the definition
* @throws JFugueException
* if there is a problem getting a double from the dictionary
* look-up
*/
private double getDoubleFromDictionary(String bracketedString)
throws JFugueException {
String definition = dictionaryLookup(bracketedString);
Double newdouble = null;
try {
newdouble = new Double(definition);
} catch (NumberFormatException e) {
throw new JFugueException(JFugueException.EXPECTED_DOUBLE,
definition, bracketedString);
}
return newdouble.doubleValue();
}
/**
* Checks whether a token is valid. This method is provided for testing
* purposes, and is not used during normal operation.
*
* @param token
* the token to test for validity
* @return <code>true</code> is the token is valid; <code>false</code>
* otherwise.
*/
public boolean isValidToken(String token) {
boolean valid = true;
try {
parseToken(token);
} catch (Exception e) {
valid = false;
}
return valid;
}
public void verifyToken(String token, final String verifyString) {
ParserListener listener = new CollatedParserListener() {
private StringBuilder results = new StringBuilder();
@Override
public void jfugueEvent(JFugueElement e) {
results.append(e.getVerifyString());
if (!verifyString.startsWith(results.toString())) {
throw new JFugueException(
JFugueException.VERIFICATION_EXCEPTION,
results.toString(), verifyString);
}
results.append("; ");
}
};
addParserListener(listener);
parseToken(token);
removeParserListener(listener);
}
/**
* Parses a string which presumably contains one token, which is a note.
*
* @param string
* The String that contains one token with a note, like "C5"
* @return a Note object representing the note parsed from the string
*/
public static Note getNote(String string) {
return getNote(new Pattern(string));
}
/**
* Parses a pattern which presumably contains one token, which is a note.
*
* @param pattern
* The Pattern that contains one token with a note, like "C5"
* @return a Note object representing the note parsed from the pattern
*/
public static Note getNote(Pattern pattern) {
final Note rootNote = new Note();
MusicStringParser parser = new MusicStringParser();
ParserListener renderer = new ParserListenerAdapter() {
@Override
public void noteEvent(Note note) {
rootNote.setValue(note.getValue());
rootNote.setDuration(note.getDuration());
rootNote.setDecimalDuration(note.getDecimalDuration());
rootNote.setStartOfTie(note.isStartOfTie());
rootNote.setEndOfTie(note.isEndOfTie());
rootNote.setAttackVelocity(note.getAttackVelocity());
rootNote.setDecayVelocity(note.getDecayVelocity());
rootNote.setRest(note.isRest());
}
};
parser.addParserListener(renderer);
parser.parse(pattern);
return rootNote;
}
/**
* Used for diagnostic purposes. main() makes calls to test the
* Pattern-to-MIDI parser. If you make any changes to this parser, run this
* method ("java org.jfugue.MusicStringParser"), and make sure everything
* works correctly.
*
* @param args
* not used
*/
public static void main(String[] args) {
verifyTokenParsing();
}
/**
* Used for diagnostic purposes. Contains an assortment of tokens that are
* known to parse correctly.
*/
private static void verifyTokenParsing() {
MusicStringParser parser = new MusicStringParser();
parser.setTracing(MusicStringParser.TRACING_ON);
try {
long startTime = System.currentTimeMillis();
// Don't forget -- individual tokens ONLY! No strings with spaces!
parser.verifyToken("C", Note.createVerifyString(60, 0.25));
parser.verifyToken("C3", Note.createVerifyString(36, 0.25));
parser.verifyToken("Cb3", Note.createVerifyString(35, 0.25));
parser.verifyToken("B#3", Note.createVerifyString(48, 0.25));
parser.verifyToken("C#3q", Note.createVerifyString(37, 0.25));
parser.verifyToken("C3i", Note.createVerifyString(36, 0.125));
parser.verifyToken("C3qh", Note.createVerifyString(36, 0.75));
parser.verifyToken("C5minw", Note.createCompoundVerifyString(
Note.createVerifyString(60, 1.0),
Note.createVerifyString(63, 1.0, false, true, false),
Note.createVerifyString(67, 1.0, false, true, false)));
parser.verifyToken("Cmaj", Note.createCompoundVerifyString(
Note.createVerifyString(36, 0.25),
Note.createVerifyString(40, 0.25, false, true, false),
Note.createVerifyString(43, 0.25, false, true, false)));
parser.parseToken("Cdom9");
parser.parseToken("Cmin11");
parser.parseToken("Cdom7<5");
parser.parseToken("Cminmaj7");
parser.parseToken("Cdom7<5<9");
parser.parseToken("Cwhqistxo");
parser.parseToken("C10");
parser.parseToken("V0");
parser.parseToken("V15");
parser.parseToken("I0");
parser.parseToken("I[13]");
parser.parseToken("I[Acoustic_Grand]");
parser.parseToken("IFlute");
parser.parseToken("Cmaj7W");
parser.parseToken("C#5Q");
parser.parseToken("eb3Q.");
parser.parseToken("[Cowbell]O");
parser.parseToken("P50"); // An unknown token should just pass
// through
parser.parseToken("A");
parser.parseToken("A+B+C");
parser.parseToken("A_B_C");
parser.parseToken("RW");
parser.parseToken("[105]X");
parser.parseToken("[105]Xa20+[98]X+[78]X");
parser.parseToken("AW+[18]X+[cabasa]Q+Dmin");
parser.parseToken("A/0.25");
parser.parseToken("[70]o");
// 2.0 Dictionary Definition and Controller Events
parser.parseToken("$UKELE=72");
parser.parseToken("IUKELE");
parser.parseToken("$Volume=43");
parser.parseToken("X[Volume]=10");
parser.parseToken("X[PORTAMENTO_TIME]=777");
// 2.0 Dictionary Definition in odd situations that should work
parser.parseToken("XVolume=ON");
parser.parseToken("[Ukele]q");
// 2.0 Dictionary Definition and non-bytes
parser.parseToken("$number1=1");
parser.parseToken("$quarter=0.25");
parser.parseToken("C4/[quarter]");
parser.parseToken("C4q");
parser.parseToken("[Number1]/[Quarter]");
// 2.0 Note velocity
parser.parseToken("Cb4qa45");
parser.parseToken("Gb4qd67");
parser.parseToken("F#4qa55d77");
parser.parseToken("B4qa[Volume]d[Number1]");
// 3.0 Layers
parser.parseToken("L8");
parser.parseToken("$number1=1");
parser.parseToken("L[Number1]");
// 3.0 Times
parser.parseToken("@100002");
// 3.0 Measures
parser.parseToken("|");
// 3.0 Tied notes
parser.parseToken("Cq-");
parser.parseToken("C5q-");
parser.parseToken("C5q.-");
parser.parseToken("C5qh-");
parser.parseToken("C-q");
parser.parseToken("C5-q");
parser.parseToken("C5-q.");
parser.parseToken("C5-qh");
parser.parseToken("C-q-");
parser.parseToken("C--");
// 3.0 Pitch Bend
parser.parseToken("&100,50");
parser.parseToken("&512");
parser.parseToken("$number110=110");
parser.parseToken("&[number110],50");
parser.parseToken("&[number110],[number110]");
parser.parseToken("$number1010=1010");
parser.parseToken("&[number1010]");
// 3.0 Channel Pressure
parser.parseToken("+100");
parser.parseToken("$number110=110");
parser.parseToken("+[number110]");
parser.parseToken("+[number110]");
// 3.0 Polyphonic Pressure
parser.parseToken("*100,20");
parser.parseToken("$number110=110");
parser.parseToken("*[number110],50");
parser.parseToken("*[number110],[number110]");
// 4.0 Chord Inversions
parser.parseToken("Cmaj");
parser.parseToken("C7maj");
parser.parseToken("C7maj^");
parser.parseToken("C7maj^^");
parser.parseToken("C7maj^^^");
parser.parseToken("C7maj^E");
parser.parseToken("C7maj^G");
parser.parseToken("C7maj^E7");
parser.parseToken("C7maj^G7");
parser.parseToken("[60]maj^");
parser.parseToken("[60]maj^^");
parser.parseToken("[60]maj^^^");
parser.parseToken("[60]maj^E");
parser.parseToken("[60]maj^[67]");
parser.parseToken("Bb6min13^^^^^^");
// 4.0 Tuplets
parser.parseToken("Cq*");
parser.parseToken("Ci*5:4");
parser.parseToken("Cs.*7:8");
parser.parseToken("Chx.*10:11");
// 3.0 Key Signatures
parser.parseToken("KC#maj");
parser.parseToken("KAbmin");
parser.parseToken("Cn");
parser.parseToken("Cn6");
// 4.0 New parser
parser.parseToken("D3");
parser.parseToken("C##3"); // Should be like D3
long endTime = System.currentTimeMillis();
System.out.println("Time taken: " + (endTime - startTime) + "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
}