/* * 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, timestamp - time); note.setDecimalDuration( (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; } }