/* * 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 nu.xom.Attribute; import nu.xom.DocType; import nu.xom.Document; import nu.xom.Element; import nu.xom.Elements; import org.jfugue.parser.ParserListener; import org.jfugue.theory.Chord; import org.jfugue.theory.Note; /** * This class is used to build a MusicXML file) given a * MIDI Sequence, Music String, etc. * *@author E.Philip Sobolik */ public class MusicXmlParserListener implements ParserListener { private Element root; // top-level node of entire MusicXML Document private Element elCurMeasure; // notes, etc. are added to this measure private Element elPartList; // may need to add score-parts to this private Element elCurScorePart; // may need to add instruments to this private Element elCurPart; // current 'voice' add measures to this private static final int MUSICXMLDIVISIONS = 4; // 4 divisions per quarter note private static final double WHOLE = 1024.0; private static final double QUARTER = 256.0; public MusicXmlParserListener() { root = new Element("score-partwise"); Element elID = new Element("identification"); Element elCreator = new Element("creator"); elCreator.addAttribute(new Attribute("type", "software")); elCreator.appendChild("JFugue MusicXMLRenderer"); elID.appendChild(elCreator); root.appendChild(elID); // add an empty score-part list here (before any parts are added) // score-parts are added to this as they are generated elPartList = new Element("part-list"); root.appendChild(elPartList); } /** * creates the internal <code>Document</code> with the top-level * <code>Element</code> and then creates the MusicXML file (as a * string) from the internal <code>Document</code> * @return the completed MusicXML file as a String */ public String getMusicXMLString() { return getMusicXMLDoc().toXML(); } /** * creates the internal <code>Document</code> with the top-level * <code>Element</code>. * @return the completed MusicXML file as a <code>Document</code> */ public Document getMusicXMLDoc() { // finishCurrentVoice(); // remove empty measures Elements elDocParts = root.getChildElements("part"); for (int xP = 0; xP < elDocParts.size(); ++xP) { Element elDocPart = elDocParts.get(xP); Elements elPartMeasures = elDocPart.getChildElements("measure"); for (int xM = 0; xM < elPartMeasures.size(); ++xM) if (elPartMeasures.get(xM).getChildCount() < 1) elDocPart.removeChild(xM); } // create the Document Document xomDoc = new Document(root); DocType docType = new DocType("score-partwise", "-//Recordare//DTD MusicXML 1.1 Partwise//EN", "http://www.musicxml.org/dtds/partwise.dtd"); xomDoc.insertChild(docType, 0); return xomDoc; } // public void doFirstMeasure(boolean bAddDefaults) // { // if (elCurPart == null) // newVoice(new Voice((byte)0)); // if (elCurMeasure == null) // { elCurMeasure = new Element("measure"); // elCurMeasure.addAttribute(new Attribute("number", Integer.toString(1))); // // // assemble attributes element // Element elAttributes = new Element("attributes"); // if (bAddDefaults) // { // divisions - 4 per beat // Element elDivisions = new Element("divisions"); // elDivisions.appendChild(Integer.toString(MUSICXMLDIVISIONS)); // elAttributes.appendChild(elDivisions); // // beats - 1 beat per measure // Element elTime = new Element("time"); // Element elBeats = new Element("beats"); // elBeats.appendChild(Integer.toString(4)); // elTime.appendChild(elBeats); // Element elBeatType = new Element("beat-type"); // elBeatType.appendChild(Integer.toString(4)); // elTime.appendChild(elBeatType); // elAttributes.appendChild(elTime); // } // if (bAddDefaults) // { // Clef - assumed to be treble clef // Element elClef = new Element("clef"); // Element elSign = new Element("sign"); // elSign.appendChild("G"); // Element elLine = new Element("line"); // elLine.appendChild("2"); // elClef.appendChild(elSign); // elClef.appendChild(elLine); // elAttributes.appendChild(elClef); // } // // add the attributes to the measure // if (elAttributes.getChildCount() > 0) // elCurMeasure.appendChild(elAttributes); // // // key signature // if (bAddDefaults) // doKeySig(new KeySignature((byte)0, (byte)0)); // C major // if (bAddDefaults) // doTempo(new Tempo(120)); // 120 BMP default // } // } // doFirstMeasure // // private void finishCurrentVoice() // { // String sCurPartID = (elCurPart == null) // ? null // : elCurPart.getAttribute("id").getValue(); // boolean bCurVoiceExists = false; // Elements elParts = root.getChildElements("part"); // Element elExistingCurPart = null; // // for (int x = 0; x < elParts.size(); ++x) // { Element elP = elParts.get(x); // String sPID = elP.getAttribute("id").getValue(); // // if (sPID.compareTo(sCurPartID) == 0) // { bCurVoiceExists = true; // elExistingCurPart = elP; // } // } // // // finish the current measure // if (elCurPart != null) // { finishCurrentMeasure(); // if (bCurVoiceExists == true) // root.replaceChild(elExistingCurPart, elCurPart); // else // root.appendChild(elCurPart); // } // } // finishCurrentVoice // // private void newVoice(Voice voice) // {// add a part to the part list // elCurScorePart = new Element("score-part"); // Attribute atPart = new Attribute("id", voice.getMusicString()); // elCurScorePart.addAttribute(atPart); // // empty part name - Finale ignores it and Sibelius gets it wrong // elCurScorePart.appendChild(new Element("part-name")); // Element elPL = root.getFirstChildElement("part-list"); // elPL.appendChild(elCurScorePart); // // // start a new part - note that the score-part and the part have the // // same id attribute // elCurPart = new Element("part"); // Attribute atPart2 = new Attribute(atPart); // elCurPart.addAttribute(atPart2); // elCurMeasure = null; // doFirstMeasure(true); // } // newVoice // // public void instrumentEvent(Instrument instrument) // { // Element elInstrName = new Element("instrument-name"); // elInstrName.appendChild(instrument.getInstrumentName()); // // Element elInstrument = new Element("score-instrument"); // elInstrument.addAttribute(new Attribute("id", Byte.toString(instrument.getInstrument()))); // elInstrument.appendChild(elInstrName); // } // // public void tempoEvent(Tempo tempo) // { doTempo(tempo); // } // // private void doTempo(Tempo tempo) // { Element elDirection = new Element("direction"); // elDirection.addAttribute(new Attribute("placement", "above")); // Element elDirectionType = new Element("direction-type"); // Element elMetronome = new Element("metronome"); // Element elBeatUnit = new Element("beat-unit"); // // assume quarter note beat unit // elBeatUnit.appendChild("quarter"); // Element elPerMinute = new Element("per-minute"); // Integer iBPM = new Float(PPMtoBPM(tempo.getTempo())).intValue(); // elPerMinute.appendChild(iBPM.toString()); // // assemble all the pieces // elMetronome.appendChild(elBeatUnit); // elMetronome.appendChild(elPerMinute); // elDirectionType.appendChild(elMetronome); // elDirection.appendChild(elDirectionType); // // attach the whole thing to the current measure // if (elCurMeasure == null) // doFirstMeasure(true); // elCurMeasure.appendChild(elDirection); // } // doTempo // // public void layerEvent(Layer layer) // { //// pattern.add(layer.getMusicString()); // } // // public void timeEvent(Time time) // { // // pattern.add(time.getMusicString()); // } // // public void systemExclusiveEvent(SystemExclusive sysex) // { // // NOP // } // // public void keySignatureEvent(KeySignature keySig) // { doKeySig(keySig); // } // // private void doKeySig(KeySignature keySig) // { Element elKey = new Element("key"); // // build the key element // Element elFifths = new Element("fifths"); // elFifths.appendChild(Byte.toString(keySig.getKeySig())); // elKey.appendChild(elFifths); // Element elMode = new Element("mode"); // elMode.appendChild((keySig.getScale() == 1 ? "minor" : "major")); // elKey.appendChild(elMode); // // add the key to the attributes element of the current measure // if (elCurMeasure == null) // doFirstMeasure(true); // Element elAttributes = elCurMeasure.getFirstChildElement("attributes"); // boolean bNewAttributes = (elAttributes == null); // if (bNewAttributes == true) // elAttributes = new Element("attributes"); // elAttributes.appendChild(elKey); // if (bNewAttributes == true) // elCurMeasure.appendChild(elAttributes); // } // doKeySig // // public void measureEvent(Measure measure) // { // first measure stuff // if (elCurMeasure == null) // doFirstMeasure(false); // else // { // add the current measure to the part // finishCurrentMeasure(); // newMeasure(); // } // } // measureEvent // // private void finishCurrentMeasure() // { // // if the part exists, replace it with the new one // // otherwise, add the new one // { if (elCurMeasure.getParent() == null) // elCurPart.appendChild(elCurMeasure); // else // { int sCurMNum = Integer.parseInt(elCurMeasure.getAttributeValue("number")); // Elements elMeasures = elCurPart.getChildElements("measure"); // for (int x = 0; x < elMeasures.size(); ++x) // { Element elM = elMeasures.get(x); // int sMNum = Integer.parseInt(elM.getAttributeValue("number")); // if (sMNum == sCurMNum) // elCurPart.replaceChild(elM, elCurMeasure); // } // } // } // } // finishCurrentMeasure // // private void newMeasure() // { Integer nextNumber = 1; // boolean bNewMeasure = true; // // if there aren't any notes in the measure, // // continue to use the current measure // Elements elMeasures = elCurPart.getChildElements("measure"); // Element elLastMeasure = null; // if (elMeasures.size() > 0) // { elLastMeasure = elMeasures.get(elMeasures.size()-1); // // get the new measure number from the last one // Attribute elNumber = elLastMeasure.getAttribute("number"); // if (elLastMeasure.getChildElements("note").size() < 1) // bNewMeasure = false; // else // nextNumber = Integer.parseInt(elNumber.getValue()) + 1; // } // else // { // first measure may not have been added yet // bNewMeasure = (elCurMeasure.getChildElements("note").size() > 0); // } // if (bNewMeasure) // { // start the new measure // elCurMeasure = new Element("measure"); // // // add the new measure number // elCurMeasure.addAttribute(new Attribute("number", // Integer.toString(nextNumber))); // } // // else continue using the same elCurMeasure // } // newMeasure // // public void controllerEvent(Controller controller) // { //// pattern.add(controller.getMusicString()); // } // // public void channelPressureEvent(ChannelPressure channelPressure) // { //// pattern.add(channelPressure.getMusicString()); // } // // public void polyphonicPressureEvent(PolyphonicPressure polyphonicPressure) // { //// pattern.add(polyphonicPressure.getMusicString()); // } // // public void pitchBendEvent(PitchBend pitchBend) // { //// pattern.add(pitchBend.getMusicString()); // } // // public void noteEvent(Note note) // { doNote(note, false); // } // // private void doNote(Note note, boolean bChord) // { // Element elNote = new Element("note"); // // if (bChord) // elNote.appendChild(new Element("chord")); // // // rest // if (note.isRest()) // { Element elRest = new Element("rest"); // elNote.appendChild(elRest); // } // else // { // pitch // Element elPitch = new Element("pitch"); // // step - note letter name without sharp or flat // Element elStep = new Element("step"); // String sPitch = Note.NOTES[note.getValue() % 12]; // int iAlter = 0; // if (sPitch.length() > 1) // { iAlter = sPitch.contains("#") ? 1 : -1; // sPitch = sPitch.substring(0,1); // } // elStep.appendChild(sPitch); // elPitch.appendChild(elStep); // // alter - -1 = flat, 1 = sharp // if (iAlter != 0) // { Element elAlter = new Element("alter"); // elAlter.appendChild(Integer.toString(iAlter)); // elPitch.appendChild(elAlter); // } // // octave // Element elOctave = new Element("octave"); // elOctave.appendChild(Integer.toString(note.getValue() / 12)); // elPitch.appendChild(elOctave); // // elNote.appendChild(elPitch); // } // // duration // Element elDuration = new Element("duration"); // double dDuration = note.getDecimalDuration(); // int iXMLDuration = (int) // ((dDuration * WHOLE * MUSICXMLDIVISIONS) / QUARTER); // elDuration.appendChild(Integer.toString(iXMLDuration)); // elNote.appendChild(elDuration); // // tie start/stop // boolean bDoNotation = false; // if (note.isStartOfTie()) // { Element elTie = new Element("tie"); // Attribute atType = new Attribute("type", "start"); // elTie.addAttribute(atType); // elNote.appendChild(elTie); // bDoNotation = true; // } // else if (note.isEndOfTie()) // { Element elTie = new Element("tie"); // Attribute atType = new Attribute("type", "stop"); // elTie.addAttribute(atType); // elNote.appendChild(elTie); // bDoNotation = true; // } // // duration type // String sDuration; // boolean bDotted = false; // if (dDuration == 1.0) // sDuration = "whole"; // else if (dDuration == 0.75) // { sDuration = "half"; // bDotted = true; // } // else if (dDuration == 0.5) // sDuration = "half"; // else if (dDuration == 0.375) // { sDuration = "quarter"; // bDotted = true; // } // else if (dDuration == 0.25) // sDuration = "quarter"; // else if (dDuration == 0.1875) // { sDuration = "eighth"; // bDotted = true; // } // else if (dDuration == 0.125) // sDuration = "eighth"; // else if (dDuration == 0.09375) // { sDuration = "16th"; // bDotted = true; // } // else if (dDuration == 0.0625) // sDuration = "16th"; // else if (dDuration == 0.046875) // { sDuration = "32nd"; // bDotted = true; // } // else if (dDuration == 0.03125) // sDuration = "32nd"; // else if (dDuration == 0.0234375) // { sDuration = "64th"; // bDotted = true; // } // else if (dDuration == 0.015625) // sDuration = "64th"; // else if (dDuration == 0.01171875) // { sDuration = "128th"; // bDotted = true; // } // else if (dDuration == 0.0078125) // sDuration = "128th"; // else sDuration = "/" + Double.toString(dDuration); // Element elType = new Element("type"); // elType.appendChild(sDuration); // elNote.appendChild(elType); // // dotted // if (bDotted) // { Element elDot = new Element("dot"); // elNote.appendChild(elDot); // } // // notations // if (bDoNotation) // { Element elNotations = new Element("notations"); // if (note.isStartOfTie()) // { Element elTied = new Element("tied"); // Attribute atStart = new Attribute("type", "start"); // elTied.addAttribute(atStart); // elNotations.appendChild(elTied); // } // else if (note.isEndOfTie()) // { Element elTied = new Element("tied"); // Attribute atStart = new Attribute("type", "stop"); // elTied.addAttribute(atStart); // elNotations.appendChild(elTied); // } // elNote.appendChild(elNotations); // } // if (elCurMeasure == null) // doFirstMeasure(true); // elCurMeasure.appendChild(elNote); // } // doNote // // public void sequentialNoteEvent(Note note) // { // } // // public void parallelNoteEvent(Note note) // { doNote(note, true); // } // // /** // * converts pulses per minute (PPM) to beats per minute (BPM) assuming 240 pulses per second // * In MusicXML, BPM can be fractional, so <code>PPMtoBPM</code> returns a float // * @param ppm // * @return bpm // */ // public static float PPMtoBPM(int ppm) // { // convert PPM to BPM assuming 240 pulses per second // return( new Float((60.f * 240.f) / ppm) ); // } @Override public void beforeParsingStarts() { // TODO Auto-generated method stub } @Override public void afterParsingFinished() { // TODO Auto-generated method stub } @Override public void onTrackChanged(byte track) { // String sReqVoice = voice.getMusicString(); // String sCurPartID = (elCurPart == null) // ? null // : elCurPart.getAttribute("id").getValue(); // // // if current voice is the same as the requested one, do nothing // if (sCurPartID != null) // if (sReqVoice.compareTo(sCurPartID) == 0) // return; // // // check if the requested voice already exists // boolean bNewVoiceExists = false; // Elements elParts = root.getChildElements("part"); // Element elExistingNewPart = null; // for (int x = 0; x < elParts.size(); ++x) // { Element elP = elParts.get(x); // String sPID = elP.getAttribute("id").getValue(); // // if (sPID.compareTo(sReqVoice) == 0) // { bNewVoiceExists = true; // elExistingNewPart = elP; // } // } // // finishCurrentVoice(); // // // start the new part // // if the new part exists, set the working part to the existing one // // otherwise, start a new part // if (bNewVoiceExists) // elCurPart = elExistingNewPart; // else // newVoice(voice); // // // start the first/next measure of the working part // // note: doesn't start a new measure if there // // aren't any notes in the current measure // newMeasure(); } @Override public void onLayerChanged(byte layer) { } @Override public void onInstrumentParsed(byte instrument) { } @Override public void onTempoChanged(int tempoBPM) { } @Override public void onKeySignatureParsed(byte key, byte scale) { } @Override public void onTimeSignatureParsed(byte numerator, byte denominator) { } @Override public void onBarLineParsed(long id) { } @Override public void onTrackBeatTimeBookmarked(String timeBookmarkId) { } @Override public void onTrackBeatTimeBookmarkRequested(String timeBookmarkId) { } @Override public void onTrackBeatTimeRequested(double time) { } @Override public void onPitchWheelParsed(byte lsb, byte msb) { } @Override public void onChannelPressureParsed(byte pressure) { } @Override public void onPolyphonicPressureParsed(byte key, byte pressure) { } @Override public void onSystemExclusiveParsed(byte... bytes) { } @Override public void onControllerEventParsed(byte controller, byte value) { } @Override public void onLyricParsed(String lyric) { } @Override public void onMarkerParsed(String marker) { } @Override public void onFunctionParsed(String id, Object message) { } @Override public void onNoteParsed(Note note) { } @Override public void onChordParsed(Chord chord) { } }