jm.midi.MidiParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmusic Show documentation
Show all versions of jmusic Show documentation
JMusic - Java Music Library
The newest version!
/*
Copyright (C) 2000 Andrew Sorensen & Andrew Brown
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or any
later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
// updates by Sean Hayes to complete key signature and time signature parsing
// fix to parsing of concurrent events by Guan Yin
package jm.midi;
import jm.JMC;
import jm.midi.event.*;
import jm.music.data.Note;
import jm.music.data.Part;
import jm.music.data.Phrase;
import jm.music.data.Score;
import java.util.*;
/**
* A MIDI parser
*
* @author Andrew Sorensen and Sean T. Hayes
*/
public final class MidiParser implements JMC {
//-----------------------------------------------------------
//Converts a SMF into jMusic Score data
//-----------------------------------------------------------
private static double tickRemainder = 0.0;
/**
* Convert a SMF into the jMusic data type
*
* @param score
* @param smf
*/
public static void SMFToScore(Score score, SMF smf) {
System.out.println("Convert SMF to JM");
Enumeration aEnum = smf.getTrackList().elements();
//Go through tracks
while (aEnum.hasMoreElements()) {
Part part = new Part();
Track smfTrack = (Track) aEnum.nextElement();
Vector evtList = smfTrack.getEvtList();
Vector phrVct = new Vector();
sortEvents(score, evtList, phrVct, smf, part);
for (int i = 0; i < phrVct.size(); i++) {
part.addPhrase((Phrase) phrVct.elementAt(i));
}
score.addPart(part);
score.clean();
}
}
private static void sortEvents(Score score, Vector evtList, Vector phrVct, SMF smf, Part part) {
double startTime = 0.0;
double[] currentLength = new double[100];
Note[] curNote = new Note[100];
int numOfPhrases = 0;
double oldTime = 0.0;
int phrIndex = 0;
//Go through evts
for (int i = 0; i < evtList.size(); i++) {
Event evt = (Event) evtList.elementAt(i);
startTime += (double) evt.getTime() / (double) smf.getPPQN();
if (evt.getID() == 007) {
PChange pchg = (PChange) evt;
part.setInstrument(pchg.getValue());
//if this event is a NoteOn event go on
} else if (evt.getID() == 020) {
TempoEvent t = (TempoEvent) evt;
score.setTempo(t.getTempo());
} else if (evt.getID() == 005) {
NoteOn noteOn = (NoteOn) evt;
part.setChannel(noteOn.getMidiChannel());
short pitch = noteOn.getPitch();
int dynamic = noteOn.getVelocity();
short midiChannel = noteOn.getMidiChannel();
//if you're a true NoteOn
if (dynamic > 0) {
noteOn(phrIndex, curNote, smf, i,
currentLength, startTime,
phrVct, midiChannel,
pitch, dynamic, evtList);
}
} else if (evt instanceof TimeSig) {
TimeSig timeSig = (TimeSig) evt;
score.setNumerator(timeSig.getNumerator());
score.setDenominator(timeSig.getDenominator());
} else if (evt instanceof KeySig) {
KeySig keySig = (KeySig) evt;
score.setKeySignature(keySig.getKeySig());
score.setKeyQuality(keySig.getKeyQuality());
}
// else
// {
// System.out.printf( "Unused event: %o\n", evt.getID() );
// if( evt instanceof CChange )
// {
// CChange cChange = (CChange)evt;
// System.out.printf( "\tUnused event: %d\t%d\t%d\t%d\n",
// cChange.getControllerNum(),
// cChange.getMidiChannel(),
// cChange.getTime(),
// cChange.getValue() );
// }
// else if( evt.getID() == 027)
// {
// System.out.printf( "\tUnused event: %d\t%X\t%o\n",
// evt.getTime(),
// evt.getTime(),
// evt.getTime() );
// }
// }
}
}
//------------------------------------------------------------------
// Converts a score into a SMF
//------------------------------------------------------------------
// MODIFIED 6/12/2003 Ron Legere to avoid use of magic note values for Program CHanges
// Etc.
private static void noteOn(int phrIndex, Note[] curNote, SMF smf, int i,
double[] currentLength, double startTime, Vector phrVct,
short midiChannel, short pitch, int dynamic, Vector evtList) {
phrIndex = -1;
//work out what phrase is ready to accept a note
for (int p = 0; p < phrVct.size(); p++) {
//Warning 0.02 should really be fixed
if (currentLength[p] <= (startTime + 0.08)) {
phrIndex = p;
break;
}
}
//need to create new phrase for a new voice?
if (phrIndex == -1) {
phrIndex = phrVct.size();
phrVct.addElement(new Phrase(startTime));
currentLength[phrIndex] = startTime;
}
//Do we need to add a rest ?
if ((startTime > currentLength[phrIndex]) &&
(curNote[phrIndex] != null)) {
double newTime = startTime - currentLength[phrIndex];
//perform a level of quantisation first
if (newTime < 0.25) {
double length =
curNote[phrIndex].getRhythmValue();
curNote[phrIndex].setRhythmValue(
length + newTime);
} else {
Note restNote = new Note(REST, newTime, 0);
restNote.setPan(midiChannel);
restNote.setDuration(newTime);
restNote.setOffset(0.0);
((Phrase) phrVct.elementAt(phrIndex)).
addNote(restNote);
}
currentLength[phrIndex] += newTime;
}
// get end time
double time = MidiUtil.getEndEvt(pitch, evtList, i) /
(double) smf.getPPQN();
// create the new note
Note tempNote = new Note(pitch, time, dynamic);
tempNote.setDuration(time);
curNote[phrIndex] = tempNote;
((Phrase) phrVct.elementAt(phrIndex)).addNote(curNote[phrIndex]);
currentLength[phrIndex] += curNote[phrIndex].getRhythmValue();
}
/**
* Converts jmusic score data into SMF data
*
* @param score
* @param smf
*/
public static void scoreToSMF(Score score, SMF smf) {
if (VERBOSE) System.out.println("Converting to SMF data structure...");
double scoreTempo = score.getTempo();
double partTempoMultiplier = 1.0;
double phraseTempoMultiplier = 1.0;
int phraseNumb;
Phrase phrase1, phrase2;
//Add a tempo track at the start of top of the list
//Add time sig to the tempo track
Track smfT = new Track();
smfT.addEvent(new TempoEvent(0, score.getTempo()));
smfT.addEvent(new TimeSig(0, score.getNumerator(), score.getDenominator()));
smfT.addEvent(new KeySig(0, score.getKeySignature()));
smfT.addEvent(new EndTrack());
smf.getTrackList().addElement(smfT);
//---------------------------------------------------
int partCount = 0;
Enumeration aEnum = score.getPartList().elements();
while (aEnum.hasMoreElements()) {
Track smfTrack = new Track();
Part inst = (Part) aEnum.nextElement();
System.out.print(" Part " + partCount + " '" + inst.getTitle() +
"' to SMF Track on Ch. " + inst.getChannel() + ": ");
partCount++;
// set up tempo difference between score and track - if any
if (inst.getTempo() != Part.DEFAULT_TEMPO) partTempoMultiplier =
scoreTempo / inst.getTempo();
else partTempoMultiplier = 1.0;
//System.out.println("partTempoMultiplier = " + partTempoMultiplier);
//order phrases based on their startTimes
phraseNumb = inst.getPhraseList().size();
for (int i = 0; i < phraseNumb; i++) {
phrase1 = (Phrase) inst.getPhraseList().elementAt(i);
for (int j = 0; j < phraseNumb; j++) {
phrase2 = (Phrase) inst.getPhraseList().elementAt(j);
if (phrase2.getStartTime() > phrase1.getStartTime()) {
inst.getPhraseList().setElementAt(phrase2, i);
inst.getPhraseList().setElementAt(phrase1, j);
break;
}
}
}
//break Note objects into NoteStart's and NoteEnd's
//as well as combining all phrases into one list
// HashMap midiEvents = new HashMap();
class EventPair {
public double time;
public Event ev;
public EventPair(double t, Event e) {
time = t;
ev = e;
}
}
;
LinkedList midiEvents = new LinkedList();
/*if(inst.getTempo() != Part.DEFAULT_TEMPO){
//System.out.println("Adding part tempo");
midiEvents.add(new EventPair(0, new TempoEvent(inst.getTempo())));
} */
//if this part has a Program Change value then set it
if (inst.getInstrument() != NO_INSTRUMENT) {
//System.out.println("Instrument change no. " + inst.getInstrument());
midiEvents.add(new EventPair(0, new PChange((short) inst.getInstrument(), (short) inst.getChannel(), 0)));
}
if (inst.getNumerator() != NO_NUMERATOR) {
midiEvents.add(new EventPair(0, new TimeSig(inst.getNumerator(), inst.getDenominator())));
}
if (inst.getKeySignature() != NO_KEY_SIGNATURE) {
midiEvents.add(new EventPair(0, new KeySig(inst.getKeySignature(), inst.getKeyQuality())));
}
Enumeration partEnum = inst.getPhraseList().elements();
double max = 0;
double startTime = 0.0;
double offsetValue = 0.0;
int phraseCounter = 0;
while (partEnum.hasMoreElements()) {
Phrase phrase = (Phrase) partEnum.nextElement();
Enumeration phraseEnum = phrase.getNoteList().elements();
startTime = phrase.getStartTime() * partTempoMultiplier;
if (phrase.getInstrument() != NO_INSTRUMENT) {
midiEvents.add(new EventPair(0, new PChange((short) phrase.getInstrument(), (short) inst.getChannel(), 0)));
}
if (phrase.getTempo() != Phrase.DEFAULT_TEMPO) {
phraseTempoMultiplier = scoreTempo / phrase.getTempo(); //(scoreTempo * partTempoMultiplier) / phrase.getTempo();
} else {
phraseTempoMultiplier = partTempoMultiplier;
}
////////////////////////////////////////////////
int noteCounter = 0;
//System.out.println();
System.out.print(" Phrase " + phraseCounter++ + ":");
// set a silly starting value to force and initial pan cc event
double pan = -1.0;
resetTicker(); // zero the ppqn error calculator
while (phraseEnum.hasMoreElements()) {
Note note = (Note) phraseEnum.nextElement();
offsetValue = note.getOffset();
// add a pan control change if required
if (note.getPan() != pan) {
pan = note.getPan();
midiEvents.add(new EventPair(startTime + offsetValue, new CChange((short) 10, (short) (pan * 127), (short) inst.getChannel(), 0)));
}
//check for frequency rather than MIDI notes
int pitch = 0;
if (note.getPitchType() == Note.FREQUENCY) {
System.err.println("jMusic warning: converting note frequency to the closest MIDI pitch for SMF.");
//System.exit(1);
pitch = Note.freqToMidiPitch(note.getFrequency());
} else pitch = note.getPitch();
if (pitch != REST) {
midiEvents.add(new EventPair(new Double(startTime + offsetValue), new NoteOn((short) pitch, (short) note.getDynamic(), (short) inst.getChannel(), 0)));
// Add a NoteOn for the END of the note with 0 dynamic, as recommended.
//create a timing event at the end of the notes duration
double endTime = startTime + (note.getDuration() * phraseTempoMultiplier);
// Add the note-off time to the list
midiEvents.add(new EventPair(new Double(endTime + offsetValue), new NoteOn((short) pitch, (short) 0, (short) inst.getChannel(), 0)));
}
// move the note-on time forward by the rhythmic value
startTime += tickRounder(note.getRhythmValue() * phraseTempoMultiplier); //time between start times
System.out.print("."); // completed a note
}
}
/*
//Sort lists so start times are in the right order
Enumeration start = midiNoteEvents.elements();
Enumeration timeing = timeingList.elements();
Vector sortedStarts = new Vector();
Vector sortedEvents = new Vector();
while(start.hasMoreElements()){
double smallest = ((Double)start.nextElement()).doubleValue();
Event anevent = (Event) timeing.nextElement();
int index = 0, count = 0;
while(start.hasMoreElements()){
count++;
double d1 = ((Double)start.nextElement()).doubleValue();
Event event1 = (Event) timeing.nextElement();
if(smallest == d1){ //if note time is equal
if(zeroVelEventQ(event1)) {
index = count;
}
}
if(smallest > d1){
smallest = d1;
index = count;
}
}
sortedStarts.addElement(midiNoteEvents.elementAt(index));
sortedEvents.addElement(timeingList.elementAt(index));
midiNoteEvents.removeElementAt(index);
timeingList.removeElementAt(index);
//reset lists for next run
start = midiNoteEvents.elements();
timeing = timeingList.elements();
}
*/
//Sort the hashmap by starttime (key value)
class CompareKey implements Comparator {
public int compare(Object a, Object b) {
EventPair ae = (EventPair) a;
EventPair be = (EventPair) b;
if (ae.time - be.time < 0)
return -1;
else if (ae.time - be.time > 0)
return 1;
else
return 0;
}
}
Collections.sort(midiEvents, new CompareKey());
//Add times to events, now that things are sorted
double st = 0.0; //start time
double sortStart; // start time from list of notes ons and offs.
int time; // the start time as ppqn value
resetTicker();
for (int index = 0; index < midiEvents.size(); index++) {
EventPair ep = midiEvents.get(index);
Event event = ep.ev;
sortStart = ep.time;
time = (int) (((((sortStart - st) * (double) smf.getPPQN()))) + 0.5);
st = sortStart;
event.setTime(time);
smfTrack.addEvent(event);
}
smfTrack.addEvent(new EndTrack());
//add this track to the SMF
smf.getTrackList().addElement(smfTrack);
System.out.println();
}
}
// Helper function
//
private static boolean zeroVelEventQ(Event e) {
if (e.getID() == 5) {
// its a NoteOn
if (((NoteOn) e).getVelocity() == 0) return true;
}
// most commonly:
return false;
}
private static void resetTicker() {
tickRemainder = 0.0;
}
/**
* We need to call this any time we calculate unusual time values,
* to prevent time creep due to the MIDI tick roundoff error.
* This method wriiten by Bob Lee.
*/
private static double tickRounder(double timeValue) {
final double tick = 1. / 480.;
final double halfTick = 1. / 960.;
int ticks = (int) (timeValue * 480.);
double rounded = ((double) ticks) * tick;
tickRemainder += timeValue - rounded;
if (tickRemainder > halfTick) {
rounded += tick;
tickRemainder -= tick;
}
return rounded;
}
}