/*
* 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.jfugue.integration;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Reader; //Abstract class for reading character streams.
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger; //the Java Logger
import javax.xml.parsers.ParserConfigurationException;
import nu.xom.Attribute;
import nu.xom.Builder;
import nu.xom.DocType;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.Node;
import nu.xom.ParsingException;
import nu.xom.ValidityException;
import org.jfugue.midi.MidiDefaults;
import org.jfugue.midi.MidiDictionary;
import org.jfugue.parser.Parser;
import org.jfugue.theory.Chord;
import org.jfugue.theory.Note;
import org.staccato.DefaultNoteSettingsManager;
// helper class
class XMLpart extends Object { //should be called XMLPartHeader - holds a MusicXML part-list entry
public String ID;
public String part_name;
public String score_instruments;
public String [][] midi_instruments; // id|channel1|name1~id|channel2|name2
public XMLpart() {
ID = "";
part_name = "";
score_instruments = "";
midi_instruments = new String[16][6];
}
};
/**
* voiceDef MusicString voice can be a combination of part and voice
*/
class voiceDef {
int part;
int voice;
}
/**
* Parses a MusicXML file, 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.
*
* MusicXmlParser.parse can be called with a file name, File, InputStream, or
* Reader
*
* @author E.Philip Sobolik
* @author David Koelle (updates for JFugue 5)
*
*/
public final class MusicXmlParser_J extends Parser {
// private HashMap<String, String> dictionaryMap;
private Builder xomBuilder;
private Document xomDoc;
private String[] volumes = { "pppppp", "ppppp", "pppp", "ppp", "pp", "p",
"mp", "mf", "f", "ff", "fff", "ffff", "fffff", "ffffff" };
// note difference between maxVolume and minVolume should be divisible by 13
private byte minVelocity = 10;
private byte maxVelocity = 127;
private byte curVelocity = DefaultNoteSettingsManager.getInstance().getDefaultOnVelocity();
private byte beats; // beats per measure
private byte divisions; // divisions per beat
private int curVoice; // current voice
private byte curLayer;
private byte[] curKey = {0, 0};
private byte nextVoice; // next available voice # for a new voice
private voiceDef[] voices;
private Logger logger = Logger.getLogger("org.jfugue");
private double totalMeasurePct;
// private double lastNoteInMeasureDuration; // adjusted duration of the last note in the measure
public static Map<String, String> XMLtoJFchordMap;
static {
// @formatter:off
XMLtoJFchordMap = new TreeMap<String, String>(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
int result = compareLength(s1, s2);
if (result == 0) { result = s1.compareTo(s2); }
return result;
}
/** Compare two strings and the bigger of the two is deemed to come first in order */
private int compareLength(String s1, String s2) {
if (s1.length() < s2.length()) {
return 1;
} else if (s1.length() > s2.length()) {
return -1;
} else {
return 0;
}
}
});
// Major Chords
XMLtoJFchordMap.put("major","MAJ");
XMLtoJFchordMap.put("major-sixth","MAJ6");
XMLtoJFchordMap.put("major-seventh","MAJ7");
XMLtoJFchordMap.put("major-ninth","MAJ9");
XMLtoJFchordMap.put("major-13th","MAJ13");
// Minor Chords
XMLtoJFchordMap.put("minor","MIN");
XMLtoJFchordMap.put("minor-sixth","MIN6");
XMLtoJFchordMap.put("minor-seventh","MIN7");
XMLtoJFchordMap.put("minor-ninth","MIN9");
XMLtoJFchordMap.put("minor-11th","MIN11");
XMLtoJFchordMap.put("major-minor","MINMAJ7");
// Dominant Chords
XMLtoJFchordMap.put("dominant","DOM7");
XMLtoJFchordMap.put("dominant-11th","DOM7%11");
XMLtoJFchordMap.put("dominant-ninth","DOM9");
XMLtoJFchordMap.put("dominant-13th","DOM13");
// Augmented Chords
XMLtoJFchordMap.put("augmented","AUG");
XMLtoJFchordMap.put("augmented-seventh","AUG7");
// Diminished Chords
XMLtoJFchordMap.put("diminished","DIM");
XMLtoJFchordMap.put("diminished-seventh","DIM7");
// Suspended Chords
XMLtoJFchordMap.put("suspended-fourth","SUS4");
XMLtoJFchordMap.put("suspended-second","SUS2");
// @formatter:on
}
//CONSTRUCTOR
public MusicXmlParser_J() throws ParserConfigurationException {
xomBuilder = new Builder();
// Set up MusicXML default values
beats = 1;
divisions = 1;
curVoice = -1;
nextVoice = 0;
voices = new voiceDef[15];
}
//Overloadings of parse method
public void parse(String musicXmlString) throws ValidityException, ParsingException, IOException {
xomDoc = xomBuilder.build(musicXmlString, (String)null); // URI is null
parse();
}
public void parse(File fileXMLin) throws ValidityException, ParsingException, IOException {
System.out.println("attempting to build file");
xomDoc = xomBuilder.build(fileXMLin);
parse();
}
public void parse(FileInputStream fisXMLin) throws ValidityException, ParsingException, IOException {
xomDoc = xomBuilder.build(fisXMLin);
parse();
}
public void parse(Reader rXMLin) throws ValidityException, ParsingException, IOException {
xomDoc = xomBuilder.build(rXMLin);
parse();
}
// ///////////////////////////////////////////////////////////////////////
// Tempo methods
//
private int tempo = MidiDefaults.DEFAULT_TEMPO_BEATS_PER_MINUTE;
protected void setTempo(int tempo) {
this.tempo = tempo;
}
protected int getTempo() {
return this.tempo;
}
//
// End Tempo methods
// ///////////////////////////////////////////////////////////////////////
/**
* Parses a MusicXML file and fires events to subscribed
* <code>ParserListener</code> interfaces. As the file is parsed, events are
* sent to <code>ParserListener</code> interfaces, which are responsible for
* doing something interesting with the music data.
*
* the input is a XOM Document, which has been built previously
*
* @throws Exception
* if there is an error parsing the pattern
*/
public void parse() {
DocType docType = xomDoc.getDocType();
//System.out.print(xomDoc.getDocType().getValue().toString());
logger.log(Level.INFO, "DocType = {0}", xomDoc.getDocType().toXML());
logger.log(Level.INFO, "Score Partwise Check");
Element root = xomDoc.getRootElement();
logger.log(Level.INFO, "Root Element = {0}", xomDoc.getDocType().getRootElementName());
if (docType.getRootElementName().compareToIgnoreCase("score-timewise") == 0){
//RUN XSLT CONVERT TO SCORE-PARTWISE
}
//compareToIgnoreCase returns zero if the strings are equal, neg or positive if one is greater than the other
if (docType.getRootElementName().compareToIgnoreCase("score-partwise") == 0) { // check if RootElementName eq "score-partwise"
Element partlist = root.getFirstChildElement("part-list"); //Then check for the first element named "part-list"
Elements parts = partlist.getChildElements(); // gets the part headers in the part-list and puts them into "parts", an ArrayList of Elements (Elements is an XOM defined ArrayList)
Integer part_list_size = parts.size();
if (parts.size() == 1) System.out.println("part list size = 1");
XMLpart[] partHeaders = new XMLpart[parts.size()]; //declare an array of XMLPart helper classes. These will be entries from the MusicXML doc's part-list
//uses class XMLPart which should be called XMLPartHeader - holds select subset of data from a MusicXML part-list entry
for (int p = 0; p < parts.size(); ++p) {
System.out.println("score part " + p);
partHeaders[p] = new XMLpart();//declares an XMLpart
logger.log(Level.INFO, "Parsing a Part Header");
parsePartHeader(parts.get(p), partHeaders[p]); //this method - see below- fills in the XMLpart
}
parts = root.getChildElements("part"); // now we re-use the parts XML Element array to hold the parts themselves
for (int p = 0; p < parts.size(); ++p) {
logger.log(Level.INFO, "Parsing a Part");
parsePart(p, parts.get(p), partHeaders); //parses each part - takes as input an integer p, the corresponding part, and the whole array of XMLParts
}
}
//else Error document could not be parsed.
}
/**
* Parses a <code>part</code> element in the <code>part-list</code> section
*
* @param part
* is the <code>part</code> element
* @param partHeader
* is the array of <code>XMLpart</code> classes that stores the
* <code>part-list</code> elements
*/
private void parsePartHeader(Element part, XMLpart partHeader) { //Parse part header
// I added the following check to satisfy a MusicXML file that contained a part-group,
// but I am not convinced that this is the proper way to handle such an
// element.
// - dmkoelle, 2 MAR 2011
// part-group is a notational convention and can be ignored - JWitzgall
if (part.getLocalName().equals("part-group")) {
return;
}
// ID //ID
Attribute ID = part.getAttribute("id"); //gets the part-list entry's id attribute
partHeader.ID = ID.getValue(); //puts it into the XMLpart
// part-name // part-name
Element partName = part.getFirstChildElement("part-name");
partHeader.part_name = partName.getValue();
logger.log(Level.INFO, "ID = {0}",partHeader.ID );
logger.log(Level.INFO, "part_name = {0} ",partHeader.part_name );
// score-instruments //score-instruments
int x;
Elements scoreInsts = part.getChildElements("score-instrument");
for (x = 0; x < scoreInsts.size(); ++x) {
logger.log(Level.INFO, "Processing Score-instrument String" );
partHeader.score_instruments += scoreInsts.get(x).getValue(); //builds a string of instrument names
if (x < scoreInsts.size() - 1)
partHeader.score_instruments += "~"; //puts these into the XMLpart score_instruments string. there is a '..' before the last substring
}
logger.log(Level.INFO, "Score Instruments String = {0} ", partHeader.score_instruments );
// midi-instruments
Elements midiInsts = part.getChildElements("midi-instrument");
for (x = 0; x < midiInsts.size(); ++x) { //For each of the part's midi-instruments
Element midi_instrument = midiInsts.get(x);
// Get instrument id
Attribute midi_instID = midi_instrument.getAttribute("id");
String midi_instID_str = midi_instID.getValue();
partHeader.midi_instruments[x][0] = midi_instID_str;
logger.log(Level.INFO, "Midi Instrument ID = {0}",partHeader.midi_instruments[x][0] );
//Get Instrument Channel
Element midi_channel = midi_instrument.getFirstChildElement("midi-channel");
String midiChannel_str = (midi_channel == null) ? "" : midi_channel.getValue();
if (midiChannel_str.length() > 0) //ASK DAVID IF THESE CHECKS ARE NECESSARY?
partHeader.midi_instruments[x][1] = midiChannel_str;
logger.log(Level.INFO, "Midi Instrument Channel = {0}",partHeader.midi_instruments[x][1] );
//Get Midi Name
Element midi_inst = midi_instrument.getFirstChildElement("midi-name");
String midiInstName_str = (midi_inst == null) ? "" : midi_inst.getValue();
if (midiInstName_str.length() > 0)
partHeader.midi_instruments[x][2] = midiInstName_str;
//Get Midi Bank
Element midi_bank = midi_instrument.getFirstChildElement("midi-bank");
String midi_bank_str = (midi_bank == null) ? "" : midi_bank.getValue();
if (midi_bank_str.length() > 0)
partHeader.midi_instruments[x][3] = midi_bank_str;
//Get Midi Program
Element midi_program = midi_instrument.getFirstChildElement("midi-program");
String midi_program_str = (midi_program == null) ? "" : midi_program.getValue();
partHeader.midi_instruments[x][4] = midi_program_str;
//Get Midi Unpitched
Element midi_unpitched = midi_instrument.getFirstChildElement("midi-unpitched");
String midi_unpitched_str = (midi_unpitched == null) ? "" : midi_unpitched.getValue();
partHeader.midi_instruments[x][5] = midi_unpitched_str;
}
}
/**
* Parses a <code>part</code> and fires all the appropriate note events
*
* @param part
* is the entire <code>part</part>
* @param partHeaders
* is the array of XMLpart classes that contains instrument info
* for the <code>part</code>s
*/
private void parsePart(int p, Element part, XMLpart[] partHeaders) { //Parse Part
for (int x = 0; x < partHeaders.length; ++x) { //Go thru partHeaders and get initial instrument for this part.
if (part.getAttribute("id").getValue().equals(partHeaders[x].ID)) {
// assigns a jfugue voice to the part - inputs are part index p and part header index x
logger.log(Level.INFO, "part number: {0}", x);
logger.log(Level.INFO, "Midi Instruments in part = {0}", partHeaders[x].midi_instruments.length );
if (partHeaders[x].midi_instruments[0] == null) { //if there are no midi instruments for the part ie the midi-instruments string length is 0
parseVoice(p, x);
parseInstrument(partHeaders[x].part_name); //then pass the name of the part to the Instrument parser
curLayer = 0;
fireLayerChanged(curLayer);
} else {
if (partHeaders[x].midi_instruments[0][1] != null) parseVoice(p, Integer.parseInt(partHeaders[x].midi_instruments[0][1]));
logger.log(Level.INFO, "Passing {0} to parseInstrument", partHeaders[x].midi_instruments[0][4] );
parseInstrument(partHeaders[x].midi_instruments[0][4]); // else parse the midi_instrument midi-name
curLayer = 0;
fireLayerChanged(curLayer);
}
} //If part id doesn't match the corresponding part headers ID, move on to next part header.
}
Elements measures = part.getChildElements("measure"); //measures - load all measures in the part into Element array "measures"
for (int m = 0; m < measures.size(); ++m) {
Element measure = measures.get(m);
Element attributes = measure.getFirstChildElement("attributes");
if (attributes != null) { //key signature...
byte key = 0, scale = 0; // scale 0 = minor, 1 = major
Element attr = attributes.getFirstChildElement("key"); //shouldn't this be key_signature??
if (attr != null) {
Element eKey = attr.getFirstChildElement("fifths");
if (eKey != null) {
key = Byte.parseByte(eKey.getValue());
}
Element eMode = attr.getFirstChildElement("mode");
if (eMode != null) {
String mode = eMode.getValue();
if (mode.compareToIgnoreCase("major") == 0) {
scale = 0;
}
else if (mode.compareToIgnoreCase("minor") == 0) {
scale = 1;
}
else {
throw new RuntimeException("Error in key signature: "+mode);
}
}
} else {
scale = 0; // default = major
}
logger.log(Level.INFO, "Key Signature = " + key + scale );
if (key != curKey[0] || scale != curKey[1]) {
fireKeySignatureParsed(key, scale);
curKey[0] = key;
curKey[1] = scale;
}
// divisions and beats used to calculate duration when note type not present //Time-Signature
Element element_divisions = attributes.getFirstChildElement("divisions");
if (element_divisions != null) {
this.divisions = Byte.valueOf(element_divisions.getValue());
}
Element element_time = attributes.getFirstChildElement("time");
if (element_time != null) {
Element element_beats = element_time.getFirstChildElement("beats");
if (element_beats != null) {
this.beats = Byte.valueOf(element_beats.getValue());
}
}
} logger.log(Level.INFO, "Time Signature = " + divisions + "/"+ beats );
// Tempo
Element direction = measure.getFirstChildElement("direction");
if (direction != null) {
Element directionType = direction.getFirstChildElement("direction-type");
if (directionType != null) {
Element metronome = directionType.getFirstChildElement("metronome");
if (metronome != null) {
Element beatUnit = metronome.getFirstChildElement("beat-unit");
String sBeatUnit = beatUnit.getValue();
if (sBeatUnit.compareToIgnoreCase("quarter") != 0) {
throw new RuntimeException("Beat unit must be quarter: "+sBeatUnit);
}
Element bpm = metronome.getFirstChildElement("per-minute");
if (bpm != null) {
this.setTempo(BPMtoPPM(Float.parseFloat(bpm.getValue())));
fireTempoChanged(this.getTempo());
}
}
}
}
Elements chords = measure.getChildElements("harmony"); //load all notes into Element array 'notes'
for (int n = 0; n < chords.size(); ++n) {
parseChord(p, chords.get(n));
}
// Notes
Elements notes = measure.getChildElements("note"); //load all notes into Element array 'notes'
for (int n = 0; n < notes.size(); ++n) {
parseNote(p, notes.get(n), part, partHeaders);
}
//Add a pad rest at the end of the bar if needed
// attempt to adjust for rounding errors with un-supported durations
// if the total length of all the notes doesn't equal a full measure, add a pad rest
//float minDif = (1.f / (beats * divisions));
//double padDur = (1. - totalMeasurePct);
//if (padDur > minDif) {
// Note pad = new Note();
// pad.setDuration(padDur);
// pad.setRest(true);
// fireNoteParsed(pad); //this note event is just a padding rest
//}
//Bar Line
fireBarLineParsed(0);
} // end of measure
}
private void parseChord(int p, Element harmony) {
String chord_string = " ";
Element chord_root = harmony.getFirstChildElement("root");
if (chord_root != null) {
Element chord_root_step = chord_root.getFirstChildElement("root-step");
if (chord_root_step != null) chord_string = chord_root_step.getValue();
Element chord_root_alter = chord_root.getFirstChildElement("root-alter");
if (chord_root_alter != null) {
if (chord_root_alter.getValue() == "-1")chord_string = chord_string + "b";
if (chord_root_alter.getValue() == "+1")chord_string = chord_string + "#";
}
}
Element chord_kind = harmony.getFirstChildElement("kind");
if (chord_kind != null) {
String chord_kind_str = XMLtoJFchordMap.get(chord_kind.getValue());
chord_string += (chord_kind_str == null) ? "" : chord_kind_str;
}
Element chord_inv = harmony.getFirstChildElement("inversion");
if (chord_inv != null) {
Integer inv_value = Integer.parseInt(chord_inv.getValue());
for (Integer i = 0; i < inv_value; i++) {
chord_string = chord_string + "^";
}
}
Element chord_bass = harmony.getFirstChildElement("bass");
if (chord_bass != null) {
Element chord_bass_step = chord_bass.getFirstChildElement("bass-step");
if (chord_bass_step != null) chord_string = chord_bass_step.getValue();
Element chord_bass_alter = chord_bass.getFirstChildElement("bass-alter");
if (chord_bass_alter != null) {
if (chord_bass_alter.getValue() == "-1")chord_string = chord_string + "b";
if (chord_bass_alter.getValue() == "+1")chord_string = chord_string + "#";
}
}
logger.log(Level.INFO, "Chord = {0}", chord_string );
Chord newChord = new Chord(chord_string);
fireChordParsed(newChord);
}
/**
* parses MusicXML note Element
*
* @param note
* is the note Element to parse
*/
private void parseNote(int p, Element note, Element part, XMLpart[] partHeaders) {
Note newNote = new Note();
boolean isRest = false;
boolean isStartOfTie = false;
boolean isEndOfTie = false;
byte noteNumber = 0;
byte octaveNumber = 0;
double decimalDuration;
// skip grace notes
if (note.getFirstChildElement("grace") != null) {
return;
}
logger.log(Level.INFO, "Parsing Note");
Elements note_elements = note.getChildElements();
//See if note is part of a chord
for (int i = 0; i < note_elements.size(); i++){
//System.out.println (note_elements.get(i).getQualifiedName());
if (note_elements.get(i).getQualifiedName().equals("chord")){
// System.out.println("setting harmonic to true");
newNote.setHarmonicNote(true);
//if (newNote.isHarmonicNote()) System.out.println("harmonic is set");
}
//else newNote.setFirstNote(true);
}
// Element element_my_chord = note.getFirstChildElement("chord");
//String test = element_my_chord.getQualifiedName();
// logger.log(Level.INFO, " go back {0}", test );
//if (test.equalsIgnoreCase("chord")){
//String test = element_chord.getLocalName();
//logger.log(Level.INFO, " go back {0}", test );
// newNote.setHarmonicNote(true);
// }
// else newNote.setFirstNote(true);
Element note_instrument = note.getFirstChildElement("instrument");
if (note_instrument != null) {
for (int x = 0; x < partHeaders.length; ++x) {
if (part.getAttribute("ID").equals(partHeaders[x].ID)) {
for (int y = 0; y < partHeaders[x].midi_instruments.length; ++y) {
if (partHeaders[x].midi_instruments[y][0] == note_instrument.getAttributeValue("ID").toString() ) {
logger.log(Level.INFO, "Part Headers entry: {0}", partHeaders[x].midi_instruments[y][0] );
logger.log(Level.INFO, "Instrument ID {0}", note_instrument.getAttributeValue("ID").toString() );
logger.log(Level.INFO, "Part Headers Name: {0}", partHeaders[x].midi_instruments[y][1] );
parseVoice(p, Integer.parseInt(partHeaders[x].midi_instruments[y][0]));
parseInstrument(partHeaders[x].midi_instruments[y][1]);
}
}
}
}
}
//To Determine if Note is Percussive
Element unpitched = note.getFirstChildElement("unpitched");
if (unpitched != null) {
newNote.setPercussionNote(true);
Element display_note = unpitched.getFirstChildElement("display-step");
Element display_octave = unpitched.getFirstChildElement("display-octave");
if (display_note != null ){
String sdisplay_note = display_note.getValue();
switch (sdisplay_note.charAt(0)) {
case 'C': noteNumber = 0; break;
case 'D': noteNumber = 2; break;
case 'E': noteNumber = 4; break;
case 'F': noteNumber = 5; break;
case 'G': noteNumber = 7; break;
case 'A': noteNumber = 9; break;
case 'B': noteNumber = 11; break;
}
}
if (display_octave != null) {
Byte octave_byte = new Byte(display_octave.getValue());
noteNumber += octave_byte*12;
System.out.println("percussion Note display pitch " + display_note.getValue()+ display_octave.getValue() + "maps to notenumber value " + noteNumber);
}
}
Element voice = note.getFirstChildElement("voice"); // voice is property element of note
if (voice != null && !newNote.isHarmonicNote()) {
//parseVoice(p, curVoice);
if (Byte.parseByte(voice.getValue()) - 1 != curLayer){
curLayer = Byte.parseByte(voice.getValue());
curLayer = (byte) (curLayer - 1);
//System.out.println("Layer changed to " + curLayer);
fireLayerChanged(curLayer);
}
}
Element pitch = note.getFirstChildElement("pitch"); //pitch
if (pitch != null) {
String sStep = pitch.getFirstChildElement("step").getValue();
switch (sStep.charAt(0)) {
case 'C': noteNumber = 0; break;
case 'D': noteNumber = 2; break;
case 'E': noteNumber = 4; break;
case 'F': noteNumber = 5; break;
case 'G': noteNumber = 7; break;
case 'A': noteNumber = 9; break;
case 'B': noteNumber = 11; break;
}
Element alter = pitch.getFirstChildElement("alter");
if (alter != null) {
String sAlter = alter.getValue();
if (sAlter != null) {
noteNumber += Integer.parseInt(sAlter);
if (noteNumber > 11) {
noteNumber = 0;
}
else if (noteNumber < 0) {
noteNumber = 11;
}
}
}
Element octave = pitch.getFirstChildElement("octave");
if (octave != null) {
String sOctave = octave.getValue();
logger.log(Level.INFO, "Octave Value: {0}", sOctave);
if (sOctave != null) {
octaveNumber = Byte.parseByte(sOctave);
}
}
// Compute the actual note number, based on octave and note
int intNoteNumber = ((octaveNumber) * 12) + noteNumber;
if (intNoteNumber > 127) {
throw new RuntimeException("Note value "+intNoteNumber+" is larger than 127");
}
noteNumber = (byte) intNoteNumber;
} else {
if (!newNote.isPercussionNote())isRest = true;
}
// duration
Element element_duration = note.getFirstChildElement("duration");
decimalDuration = (element_duration == null) ? beats * divisions
: Double.parseDouble(element_duration.getValue()) / (beats * divisions);
/* else
{ String sDuration = type.getValue();
if (sDuration.compareToIgnoreCase("whole") == 0)
durationNumber = 1;
else if (sDuration.compareToIgnoreCase("half") == 0
durationNumber = 2;
else if (sDuration.compareToIgnoreCase("quarter") == 0)
durationNumber = 4;
else if (sDuration.compareToIgnoreCase("eighth") == 0)
durationNumber = 8;
else if (sDuration.compareToIgnoreCase("16th") == 0)
durationNumber = 16;
else if (sDuration.compareToIgnoreCase("32nd") == 0)
durationNumber = 32;
else if (sDuration.compareToIgnoreCase("64th") == 0)
durationNumber = 64;
else
throw new JFugueException(JFugueException.NOTE_DURATION_EXC, "", sDuration);
decimalDuration = 1.0 / durationNumber;
Element element_dot = note.getFirstChildElement("dot");
if (element_dot != null)
decimalDuration *= 1.5;
}
*/
// Tempo is in PPQ (Pulses Per Quarter). Turn that into "PPW", then multiply that by durationNumber for WHQITXN notes
double PPW = (double) this.getTempo() * 4.0; // 4 quarter notes in a whole note
//Tied Note
Element notations = note.getFirstChildElement("notations");
if (notations != null) {
Element tied = notations.getFirstChildElement("tied");
if (tied != null) {
Attribute tiedType = tied.getAttribute("type");
String sTiedType = tiedType.getValue();
if (sTiedType.compareToIgnoreCase("start") == 0) {
isStartOfTie = true;
}
else if (sTiedType.compareToIgnoreCase("end") == 0) {
isEndOfTie = true;
}
logger.log(Level.INFO, "Is start of tie = {0}", isStartOfTie );
logger.log(Level.INFO, "TiedType = {0}", sTiedType );
}
// Velocity
Element dynamics = notations.getFirstChildElement("dynamics");
if (dynamics != null) {
Node dynamic = dynamics.getChild(0);
if (dynamic != null) {
for (int x = 0; x < this.volumes.length; ++x) {
if (dynamic.getValue().compareToIgnoreCase(this.volumes[x]) == 0) {
this.curVelocity = (byte) (((this.maxVelocity - this.minVelocity) / (this.volumes.length - 1)) * x);
}
}
}
}
}
byte attackVelocity = this.curVelocity;
byte decayVelocity = this.curVelocity;
// Set up the note
if (isRest) {
newNote.setRest(true);
newNote.setDuration(decimalDuration);
newNote.setOnVelocity((byte) 0); // turn off sound for rest notes
newNote.setOffVelocity((byte) 0);
} else {
newNote.setValue(noteNumber);
newNote.setDuration(decimalDuration);
newNote.setStartOfTie(isStartOfTie);
newNote.setEndOfTie(isEndOfTie);
newNote.setOnVelocity(attackVelocity);
newNote.setOffVelocity(decayVelocity);
}
/*
* attempt to adjust for rounding errors in non-supported durations if
* (newNote.getType() == Note.FIRST) { if ((totalMeasurePct +
* decimalDuration) > 1.) { decimalDuration = 1. - totalMeasurePct;
* lastNoteInMeasureDuration = decimalDuration; totalMeasurePct = 1.; }
* else { float minDif = (1.f / (beats * divisions)); if (1. -
* (totalMeasurePct + decimalDuration) < minDif) { decimalDuration = (1.
* - totalMeasurePct); totalMeasurePct = 1.; } else totalMeasurePct +=
* decimalDuration; } } else if (totalMeasurePct == 1.) // just did a
* last note in measure decimalDuration = lastNoteInMeasureDuration;
*/
newNote.setDuration(decimalDuration);
logger.log(Level.INFO, "note duration = {0}", newNote.getDuration());
if (newNote.isHarmonicNote())
System.out.println("this is harmonic note");
//logger.log(Level.INFO, "This is a harmonic note");
System.out.println(newNote.toDebugString());
fireNoteParsed(newNote);
//Add Lyric
Element lyric = note.getFirstChildElement("lyric");
if (lyric != null) {
Element lyric_text_element = lyric.getFirstChildElement("text");
if (lyric_text_element != null) {
String lyric_text_string = lyric_text_element.getValue();
fireLyricParsed(lyric_text_string);
}
}
}
/**
* Parses a voice and fires a voice element
*
* @param v
* is the voice number 1 - 16
* @throws JFugueException
* if there is a problem parsing the element
*/
private void parseVoice(int p, int v) {
if (v == 10) fireTrackChanged((byte)v);
else {
//scroll through voiceDef objects looking for this particular combination of p v
byte voiceNumber = -1;// XML part ID's are 1-based, JFugue voice numbers are 0-based
for (byte x = 0; x < this.nextVoice; ++x) {// class variable nextVoice has been previously initialized to zero
if (p == voices[x].part && v == voices[x].voice) //class variable voices is an array of voiceDef objects. These objects match a part index to a voice index.
voiceNumber = x;
}
// if Voice not found, add a new voiceDef to the array
if (voiceNumber == -1) {
voiceNumber = nextVoice;
voices[voiceNumber] = new voiceDef();
voices[voiceNumber].part = p;
voices[voiceNumber].voice = v;
++nextVoice;
}
if (voiceNumber != this.curVoice) {
fireTrackChanged(voiceNumber);
}
curVoice = voiceNumber;
}
}
/**
* Parses a <code>XMLpart.midi_instruments</code> and fires a voice or
* instrument events
*
* @param instruments
* is the <code>XMLpart.midiinstruments</code> string to parse
* Can be a list of ~ separated pairs - midi-channel|InstName
* where InstName can be a midi-name, midi-bank, or program
* Element
*/
//private void parsePartElementInstruments(int p, String [] instruments) { // NOT USED
/*if (instruments.indexOf('~') > -1) { //if the ~ character is present in the string there are more than one midi instrument
String[] instArray = instruments.split("~"); //split the string on ~ and put the substrings into an array
// just do the first in the array
String[] midiArray = instArray[0].split("|"); //split the first midi instrument substring on |
if (midiArray.length > 0 && midiArray[1].length() > 0)//if the second section of the midi instrument substring (channel) is not empty
parseVoice(p, Integer.parseInt(midiArray[1]) - 1); //run parseVoice using the channel -1 since Jfugue starts voices at 0
if (midiArray.length > 2) //parses the midi instrument name if the name is present
parseInstrument(midiArray[2]); //this parses a string
} */
//else { //else there must only be one instrument ie the part name
// if (instruments. (instruments.length - 1) == '|') {
// instruments = instruments.substring(0, instruments.length() - 1); //truncates instruments to the channel info
// }
// parseInstrument(instruments[0]); //parses this channel
//}
//}
/**
* parses <code>inst</code> and fires an Instrument Event
*
* @param inst
* is a String that represents the instrument. If it is a numeric
* value, it is interpreted as a midi-bank or program. If it is
* an instrument name, it is looked up in the Dictionary as an
* instrument name.
*/
private void parseInstrument(String inst) {
byte instrumentNumber;
logger.log(Level.INFO, "Starting parseInstrument");
try {
instrumentNumber = Byte.parseByte(inst); //if the inst string is a number ie the midi number
} catch (NumberFormatException e) {
Object value = MidiDictionary.INSTRUMENT_STRING_TO_BYTE.get(inst); // otherwise map the midi_name to its byte code
instrumentNumber = (value == null) ? -1 : (Byte)value;
}
logger.log(Level.INFO, "Instrument element: inst = {0}", inst);
logger.log(Level.INFO, "Instrument number: {0}", instrumentNumber);
if (instrumentNumber > -1) {
fireInstrumentParsed(instrumentNumber);
}
}
/**
* converts beats per minute (BPM) to pulses per minute (PPM) assuming 240
* pulses per second In MusicXML, BPM can be fractional, so
* <code>BPMtoPPM</code> takes a float argument
*
* @param bpm
* @return ppm
*/
public static int BPMtoPPM(float bpm) {
return (new Float((60.f * 240.f) / bpm).intValue());
}
}