/*
* 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 javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.SysexMessage;
import javax.sound.midi.Track;
/**
* Parses MIDI data, whether from a file, a connected device, or some other stream.
*
* @version 4.0.3 - A Note event with 0 duration is now sent when a note is first encountered
*/
public final class MidiParser extends Parser
{
long[][] tempNoteRegistry = new long[16][255];
byte[][] tempNoteAttackRegistry = new byte[16][255];
int tempo;
private static final int DEFAULT_TEMPO = 120;
public MidiParser()
{
this.tempo = DEFAULT_TEMPO;
// Create a two dimensional array of bytes [ track, note ] - when a NoteOn event is found,
// populate the proper spot in the array with the note's start time. When a NoteOff event
// is found, new Time and Note objects are constructed and added to the composition
for (int m=0; m < 16; m++) {
for (int n=0; n < 255; n++) {
tempNoteRegistry[m][n] = 0L;
tempNoteAttackRegistry[m][n] = (byte)0;
}
}
}
/**
* Parses a <code>Sequence</code> and fires events to subscribed <code>ParserListener</code>
* interfaces. As the Sequence is parsed, events are sent
* to <code>ParserListener</code> interfaces, which are responsible for doing
* something interesting with the music data, such as adding notes to a pattern.
*
* @param sequence the <code>Sequence</code> to parse
* @throws Exception if there is an error parsing the pattern
*/
public void parse(Sequence sequence)
{
this.tempo = DEFAULT_TEMPO;
// Get the MIDI tracks from the sequence. Expect a maximum of 16 tracks.
Track[] tracks = sequence.getTracks();
// Compute the size of this adventure for the ParserProgressListener
long totalCount = 0;
long counter = 0;
for (byte i=0; i < tracks.length; i++)
{
totalCount += tracks[i].size();
}
// And now to parse the MIDI!
for (int t = 0; t < tracks.length; t++)
{
int trackSize = tracks[t].size();
if (trackSize > 0)
{
fireVoiceEvent(new Voice((byte)t));
for (int ev = 0; ev < trackSize; ev++)
{
counter++;
fireProgressReported("Parsing MIDI...", counter, totalCount);
MidiEvent event = tracks[t].get(ev);
MidiMessage message = event.getMessage();
trace("Message received: ",message);
parse(message, event.getTick());
}
}
}
}
/**
* Delegator method that calls specific parsers depending on the
* type of MidiMessage passed in.
* @param message the message to parse
* @param timestamp the time at which the message was encountered in this track
*/
public void parse(MidiMessage message, long timestamp)
{
if (message instanceof ShortMessage)
{
parseShortMessage((ShortMessage)message, timestamp);
}
else if (message instanceof SysexMessage)
{
parseSysexMessage((SysexMessage)message, timestamp);
}
else if (message instanceof MetaMessage)
{
parseMetaMessage((MetaMessage)message, timestamp);
}
}
/**
* Parses instances of ShortMessage.
* @param message The message to parse
* @param timestamp the time at which the message was encountered in this track
*/
private void parseShortMessage(ShortMessage message, long timestamp)
{
int track = message.getChannel();
switch (message.getCommand())
{
case ShortMessage.PROGRAM_CHANGE : // 0xC0, 192
trace("Program change to ",message.getData1());
Instrument instrument = new Instrument((byte)message.getData1());
fireTimeEvent(new Time(timestamp));
fireVoiceEvent(new Voice((byte)track));
fireInstrumentEvent(instrument);
break;
case ShortMessage.CONTROL_CHANGE : // 0xB0, 176
trace("Controller change to ",message.getData1(),", value = ",message.getData2());
Controller controller = new Controller((byte)message.getData1(), (byte)message.getData2());
fireTimeEvent(new Time(timestamp));
fireVoiceEvent(new Voice((byte)track));
fireControllerEvent(controller);
break;
case ShortMessage.NOTE_ON : // 0x90, 144
if (message.getData2() == 0) {
// A velocity of zero in a note-on event is a note-off event
noteOffEvent(timestamp, track, message.getData1(), message.getData2());
} else {
noteOnEvent(timestamp, track, message.getData1(), message.getData2());
}
break;
case ShortMessage.NOTE_OFF : // 0x80, 128
noteOffEvent(timestamp, track, message.getData1(), message.getData2());
break;
case ShortMessage.CHANNEL_PRESSURE : // 0xD0, 208
trace("Channel pressure, pressure = ",message.getData1());
ChannelPressure pressure = new ChannelPressure((byte)message.getData1());
fireTimeEvent(new Time(timestamp));
fireVoiceEvent(new Voice((byte)track));
fireChannelPressureEvent(pressure);
break;
case ShortMessage.POLY_PRESSURE : // 0xA0, 128
trace("Poly pressure on key ",message.getData1(),", pressure = ",message.getData2());
PolyphonicPressure poly = new PolyphonicPressure((byte)message.getData1(), (byte)message.getData2());
fireTimeEvent(new Time(timestamp));
fireVoiceEvent(new Voice((byte)track));
firePolyphonicPressureEvent(poly);
break;
case ShortMessage.PITCH_BEND : // 0xE0, 224
trace("Pitch Bend, data1= ",message.getData1(),", data2= ",message.getData2());
PitchBend bend = new PitchBend((byte)message.getData1(), (byte)message.getData2());
fireTimeEvent(new Time(timestamp));
fireVoiceEvent(new Voice((byte)track));
firePitchBendEvent(bend);
break;
default :
trace("Unparsed message: ",message.getCommand());
break;
}
}
private void noteOnEvent(long timestamp, int track, int data1, int data2)
{
trace("Note on ",data1," - attack is ",data2);
tempNoteRegistry[track][data1] = timestamp;
tempNoteAttackRegistry[track][data1] = (byte)data2;
// Added 9/27/2008 - fire a Note with duration 0 to signify a that a Note was pressed
Note note = new Note((byte)data1, 0);
note.setDecimalDuration(0);
note.setAttackVelocity((byte)data2);
fireNoteEvent(note);
}
private void noteOffEvent(long timestamp, int track, int data1, int data2)
{
long time = tempNoteRegistry[track][data1];
trace("Note off ",data1," - decay is ",data2,". Duration is ",(timestamp - time));
fireTimeEvent(new Time(time));
fireVoiceEvent(new Voice((byte)track));
Note note = new Note((byte)data1, (long)(timestamp - time));
note.setDecimalDuration((double)((timestamp - time) / (this.tempo * 4.0D)));
note.setAttackVelocity(tempNoteAttackRegistry[track][data1]);
note.setDecayVelocity((byte)data2);
fireNoteEvent(note);
tempNoteRegistry[track][data1] = 0L;
}
/**
* Parses instances of SysexMessage.
* @param message The message to parse
* @param timestamp the time at which the message was encountered in this track
*/
private void parseSysexMessage(SysexMessage message, long timestamp)
{
// Nothing to do - JFugue doesn't use sysex messages
trace("SysexMessage received but not parsed by JFugue (doesn't use them)");
}
/**
* Parses instances of MetaMessage.
* @param message The message to parse
* @param timestamp the time at which the message was encountered in this track
*/
private void parseMetaMessage(MetaMessage message, long timestamp)
{
switch (message.getType())
{
case 0x51 : parseTempo(message, timestamp); break;
case 0x59 : break; // Even though we care about Key Signatures, we don't want to read one in from a MIDI file,
// because the notes that we'll receive will already be adjusted for the key signature.
// MIDI's Key Signature is more about notating sheet music that influencing the played notes.
default : break;
}
// Nothing to do - JFugue doesn't use sysex messages
trace("MetaMessage received but not parsed by JFugue (doesn't use them)");
}
private void parseTempo(MetaMessage message, long timestamp)
{
int beatsPerMinute = TimeFactor.parseMicrosecondsPerBeat(message, timestamp);
trace("Tempo Event, bpm = ",beatsPerMinute);
fireTimeEvent(new Time(timestamp));
fireTempoEvent(new Tempo(beatsPerMinute));
this.tempo = beatsPerMinute;
}
}