m@0: /*============================================================================= m@0: * File: MidiPlayer.java m@0: * Author: Marcus Pearce m@0: * Created: <2007-02-14 12:13:56 marcusp> marcus@16: * Time-stamp: <2011-11-15 16:52:06 marcusp> m@0: *============================================================================= m@0: */ m@0: m@0: /* m@0: * Based on: m@0: * http://www.jsresources.org/examples/SimpleMidiPlayer.html m@0: * http://www.jsresources.org/examples/DumpSequence.html m@0: */ m@0: m@0: import java.io.File; m@0: import java.io.IOException; m@0: import java.util.ArrayList; m@0: m@0: import javax.sound.midi.MidiSystem; m@0: import javax.sound.midi.MidiDevice; m@0: import javax.sound.midi.InvalidMidiDataException; m@0: import javax.sound.midi.MidiUnavailableException; m@0: import javax.sound.midi.MetaEventListener; m@0: import javax.sound.midi.Sequence; m@0: import javax.sound.midi.Sequencer; m@0: import javax.sound.midi.Synthesizer; m@0: import javax.sound.midi.MidiChannel; m@0: import javax.sound.midi.Receiver; m@0: import javax.sound.midi.Transmitter; m@0: import javax.sound.midi.Track; m@0: import javax.sound.midi.MidiEvent; m@0: import javax.sound.midi.MidiMessage; m@0: import javax.sound.midi.ShortMessage; m@0: import javax.sound.midi.MetaMessage; m@0: import javax.sound.midi.SysexMessage; m@0: import javax.sound.midi.MetaMessage; JShulver@22: import java.util.Arrays; jeremy@24: import java.util.Iterator; jeremy@24: jeremy@24: m@0: public class MidiPlayer { m@0: m@0: /* variables */ m@0: private Sequencer sequencer = null; m@0: private Synthesizer synthesizer = null; m@0: private Sequence sequence = null; jeremy@24: private ArrayList onsets, offsets = null; CBussey@39: private ArrayList pitches, velocities = null; CBussey@39: private File midiFile; CBussey@39: private boolean defaultMD = false; CBussey@39: private int midiDevice; jeremy@24: private boolean debug; m@0: m@0: /* accessors */ CBussey@39: public Sequencer getSequencer() { return sequencer; } CBussey@39: public boolean usingDefault() { return defaultMD; } m@0: CBussey@39: /* Constructors */ CBussey@39: public MidiPlayer(String path, int deviceNumber, boolean d) { CBussey@39: if(deviceNumber == -1) { CBussey@39: defaultMD = true; CBussey@39: midiDevice = 0; CBussey@39: } CBussey@39: else{ CBussey@39: midiDevice = deviceNumber; CBussey@39: } CBussey@39: midiFile = new File(path); CBussey@39: debug = d; CBussey@39: setup(); CBussey@39: } CBussey@39: CBussey@39: private void setup(){ CBussey@39: // Get sequence m@0: try { sequence = MidiSystem.getSequence(midiFile); } m@0: catch (InvalidMidiDataException e) { m@0: e.printStackTrace(); m@0: System.exit(1); m@0: } m@0: catch (IOException e) { m@0: e.printStackTrace(); m@0: System.exit(1); m@0: } m@0: //sequencer.setTempoInBPM(bpm); m@0: // Workaround bug in JDK m@0: // sequencer.addMetaEventListener(new MetaEventListener() { m@0: // public void meta(MetaMessage event) { m@0: // if (event.getType() == 47) { m@0: // sequencer.close(); m@0: // if (synthesizer != null) { m@0: // synthesizer.close(); m@0: // } m@0: // } m@0: // } m@0: // }); CBussey@39: // Set up MIDI output for sequence m@0: try { m@0: MidiDevice.Info msinfo[] = MidiSystem.getMidiDeviceInfo(); m@0: for(int i = 0; i < msinfo.length; i++) { CBussey@39: MidiDevice.Info m = msinfo[i]; CBussey@39: System.out.println("Name: " + m.getName() + CBussey@39: "; Vendor: " + m.getVendor() + CBussey@39: "; Version: " + m.getVersion()); m@0: } CBussey@39: MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]); CBussey@39: synth.open(); CBussey@39: // Get sequencer CBussey@39: try { sequencer = MidiSystem.getSequencer(defaultMD); } CBussey@39: catch (MidiUnavailableException e) { CBussey@39: e.printStackTrace(); CBussey@39: System.exit(1); CBussey@39: } m@0: // synthesizer = MidiSystem.getSynthesizer(); CBussey@39: CBussey@39: try { sequencer.open(); }//// CBussey@39: catch (MidiUnavailableException e) { CBussey@39: e.printStackTrace(); CBussey@39: System.exit(1); CBussey@39: } CBussey@39: // Assign sequence to sequencer CBussey@39: try { sequencer.setSequence(sequence); } CBussey@39: catch (InvalidMidiDataException e) { CBussey@39: e.printStackTrace(); CBussey@39: System.exit(1); CBussey@39: }//// m@0: m@0: // Change the patch m@0: // MidiChannel channels[] = synthesizer.getChannels(); m@0: // for (int i = 0; i < channels.length; i++) m@0: // channels[i].programChange(65); CBussey@39: m@0: Receiver synthReceiver = synth.getReceiver(); m@0: Transmitter seqTransmitter = sequencer.getTransmitter(); m@0: seqTransmitter.setReceiver(synthReceiver); m@0: m@0: } m@0: catch (MidiUnavailableException e) { m@0: e.printStackTrace(); m@0: } m@0: m@0: // compute data from the MIDI file jeremy@24: jeremy@24: /* m@0: onsets = computeOnsets(); m@0: offsets = computeOffsets(); m@0: pitches = computePitches(); jeremy@24: */ jeremy@24: registerEvents(); m@0: jeremy@24: if (debug) { jeremy@24: String divisionType; jeremy@24: if (sequence.getDivisionType() == sequence.PPQ) jeremy@24: divisionType = "ppq"; jeremy@24: else jeremy@24: divisionType = "smpte"; jeremy@24: System.out.println("division type = " + divisionType + jeremy@24: "; resolution = " + sequence.getResolution()); jeremy@24: } m@0: } m@0: m@0: /* number of microseconds per MIDI tick */ m@0: private double microsecondsPerTick() { m@0: double seqTickLength = (float)sequence.getTickLength(); m@0: double seqMicrosecondLength = (float)sequence.getMicrosecondLength(); m@0: double microsecondsPerTick = seqMicrosecondLength / seqTickLength; m@0: //System.out.println("seqTickLength = " + seqTickLength); m@0: //System.out.println("seqMicrosecondLength = " + seqMicrosecondLength); m@0: //System.out.println("microsecondsPerTick = " + microsecondsPerTick); m@0: return microsecondsPerTick; m@0: } m@0: m@0: private long ticksToMicroseconds(long tick) { m@0: double microsecondsPerTick = microsecondsPerTick(); m@0: double seconds = (double)tick * microsecondsPerTick; m@0: m@0: return (long)(10 * Math.floor(seconds * 0.1)); m@0: } m@0: m@0: /* compute a list of note onset times (microseconds) */ m@0: private ArrayList computeOnsets() { m@0: ArrayList ons = new ArrayList(); m@0: Track[] tracks = sequence.getTracks(); m@0: m@0: for (int nTrack = 0; nTrack < tracks.length; nTrack++) { m@0: Track track = tracks[nTrack]; m@0: for (int nEvent = 0; nEvent < track.size(); nEvent++) { m@0: MidiEvent event = track.get(nEvent); m@0: MidiMessage message = event.getMessage(); m@0: if (message instanceof ShortMessage && m@0: ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) { m@0: // System.out.println("onset in ticks = " + event.getTick()+ m@0: // "; onset in microseconds = " + m@0: // ticksToMicroseconds(event.getTick())); JShulver@22: //if the event does not have a velocity of zero (i.e. switching a note off) JShulver@22: if(message.getMessage()[2] != 0) { JShulver@22: ons.add(ticksToMicroseconds(event.getTick())); //THEN we can add the note JShulver@22: } m@0: } m@0: } m@0: } m@0: return ons; m@0: } m@0: m@0: /* compute a list of note offset times (microseconds) */ m@0: private ArrayList computeOffsets() { m@0: ArrayList offs = new ArrayList(); m@0: Track[] tracks = sequence.getTracks(); m@0: m@0: for (int nTrack = 0; nTrack < tracks.length; nTrack++) { m@0: Track track = tracks[nTrack]; m@0: for (int nEvent = 0; nEvent < track.size(); nEvent++) { m@0: MidiEvent event = track.get(nEvent); m@0: MidiMessage message = event.getMessage(); m@0: if (message instanceof ShortMessage && m@0: ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) { m@0: //System.out.println("offset in ticks = " + event.getTick()+ m@0: // "; microsecondsPerTick = " + microsecondsPerTick + m@0: // "; offset in microseconds = " + m@0: // (float)event.getTick() * microsecondsPerTick); m@0: offs.add(ticksToMicroseconds(event.getTick())); m@0: } JShulver@22: //if we have not found a note off, JShulver@22: else if (message instanceof ShortMessage JShulver@22: && ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON JShulver@22: && message.getMessage()[2] == 0) { //but it is a note on with a velocity of zero JShulver@22: offs.add(ticksToMicroseconds(event.getTick())); //add it as an off signal JShulver@22: } JShulver@22: m@0: } m@0: } m@0: return offs; m@0: } m@0: m@0: /* compute a list of note pitches */ m@0: private ArrayList computePitches() { m@0: ArrayList pit = new ArrayList(); m@0: Track[] tracks = sequence.getTracks(); m@0: m@0: for (int nTrack = 0; nTrack < tracks.length; nTrack++) { m@0: Track track = tracks[nTrack]; m@0: for (int nEvent = 0; nEvent < track.size(); nEvent++) { m@0: MidiEvent event = track.get(nEvent); m@0: MidiMessage message = event.getMessage(); m@0: if (message instanceof ShortMessage && m@0: ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) m@0: pit.add(((ShortMessage)message).getData1()); m@0: } m@0: } m@0: return pit; m@0: } m@0: jeremy@24: /* jeremy@24: * Simultaneously construct lists of event onsets, offsets and pitches jeremy@24: */ jeremy@24: private void registerEvents() { jeremy@24: onsets = new ArrayList(); jeremy@24: offsets = new ArrayList(); jeremy@24: pitches = new ArrayList(); jeremy@24: velocities = new ArrayList(); jeremy@24: jeremy@24: Track[] tracks = sequence.getTracks(); jeremy@24: jeremy@24: // Iterate over MIDI tracks jeremy@24: for (int i = 0; i < tracks.length; i++) { jeremy@24: Track track = tracks[i]; jeremy@24: // Iterate over track events jeremy@24: for (int j = 0; j < track.size(); j++) { jeremy@24: registerEvent(i, j); jeremy@24: } jeremy@24: } jeremy@24: jeremy@24: if (debug) { jeremy@24: System.out.println("\nRegistered events..."); jeremy@24: Iterator oi = onsets.iterator(); jeremy@24: Iterator fi = offsets.iterator(); jeremy@24: Iterator pi = pitches.iterator(); jeremy@24: Iterator vi = velocities.iterator(); jeremy@24: jeremy@24: int pos = 1; jeremy@24: while (oi.hasNext() && fi.hasNext() && pi.hasNext() && vi.hasNext()) { jeremy@24: System.out.println("Event " + pos + ": onset " + oi.next() + ", offset " + fi.next() jeremy@24: + ", pitch " + pi.next() + ", velocity " + vi.next()); jeremy@24: pos++; jeremy@24: } jeremy@24: jeremy@24: if (oi.hasNext() || fi.hasNext() || pi.hasNext() || vi.hasNext()) { jeremy@24: System.out.println("Warning: event lists not equal length."); jeremy@24: } jeremy@24: } jeremy@24: } jeremy@24: jeremy@24: /* jeremy@24: * Add the given event to the onset, offset and pitch lists. jeremy@24: */ jeremy@24: private void registerEvent(int trackIndex, int eventIndex) { jeremy@24: jeremy@24: Track track = sequence.getTracks()[trackIndex]; jeremy@24: MidiEvent event = track.get(eventIndex); jeremy@24: MidiMessage message = event.getMessage(); jeremy@24: jeremy@24: // Register NOTE_ON events jeremy@24: if (message instanceof ShortMessage) { jeremy@24: ShortMessage shortMsg = (ShortMessage) message; jeremy@24: int velocity = shortMsg.getData2(); jeremy@24: jeremy@24: if (shortMsg.getCommand() == ShortMessage.NOTE_ON && velocity != 0) { jeremy@24: jeremy@24: long onset = ticksToMicroseconds(event.getTick()); jeremy@24: int pitch = shortMsg.getData1(); jeremy@24: long offset = getOffset(trackIndex, eventIndex, onset, pitch); jeremy@24: jeremy@24: registerEvent(onset, offset, pitch, velocity); jeremy@24: } jeremy@24: } jeremy@24: } jeremy@24: jeremy@24: /* jeremy@24: * Add the event details to the onset, offset and pitch lists, jeremy@24: * use onset to determine the list position. jeremy@24: */ jeremy@24: private void registerEvent(long onset, long offset, int pitch, int velocity) { jeremy@24: jeremy@24: int index = 0; jeremy@24: boolean inserted = false; jeremy@24: jeremy@24: while (index < onsets.size()) { jeremy@24: jeremy@24: if (onsets.get(index) > onset) jeremy@24: break; jeremy@24: else jeremy@24: index++; jeremy@24: } jeremy@24: jeremy@24: onsets.add(index, onset); jeremy@24: offsets.add(index, offset); jeremy@24: pitches.add(index, pitch); jeremy@24: velocities.add(index, pitch); jeremy@24: } jeremy@24: jeremy@24: jeremy@24: jeremy@24: /* jeremy@24: * Find the matching NOTE_OFF event *after* the given NOTE_ON event jeremy@24: * (the th event in track ) jeremy@24: */ jeremy@24: private long getOffset(int trackNum, int onsetIndex, long onsetTime, int pitch) { jeremy@24: jeremy@24: long offset = -1; jeremy@24: Track track = sequence.getTracks()[trackNum]; jeremy@24: jeremy@24: // Iterate through remaining events looking for NOTE_OFF jeremy@24: for (int k = onsetIndex + 1; k < track.size(); k++) { jeremy@24: MidiEvent event = track.get(k); jeremy@24: MidiMessage message = event.getMessage(); jeremy@24: jeremy@24: if (message instanceof ShortMessage) { jeremy@24: ShortMessage shortMsg = (ShortMessage) message; jeremy@24: int command = shortMsg.getCommand(); jeremy@24: jeremy@24: // Check NOTE_OFF message jeremy@24: if (command == ShortMessage.NOTE_OFF jeremy@24: || (command == ShortMessage.NOTE_ON && shortMsg.getData2() == 0)) { jeremy@24: int pitch2 = shortMsg.getData1(); jeremy@24: // If pitches are identical then offset found jeremy@24: if (pitch == pitch2) { jeremy@24: offset = ticksToMicroseconds(event.getTick()); jeremy@24: break; jeremy@24: } jeremy@24: } jeremy@24: } jeremy@24: } jeremy@24: jeremy@24: if (offset < 0) { jeremy@24: System.out.println("No NOTE_OFF found for track " + trackNum jeremy@24: + ", onset " + onsetTime + ", pitch " + pitch); jeremy@24: } jeremy@24: jeremy@24: return offset; jeremy@24: } jeremy@24: jeremy@24: jeremy@24: m@0: /* return a list of note onset times (milliseconds) */ m@0: public ArrayList getOnsets() { return onsets; } m@0: m@0: /* return a list of note offset times (milliseconds) */ m@0: public ArrayList getOffsets() { return offsets; } m@0: m@0: /* return a list of note pitches */ m@0: public ArrayList getPitches() { return pitches; } m@0: m@0: /* return a list of note durations (microseconds) */ m@0: public ArrayList getDurations() { m@0: Object[] ons = onsets.toArray(); m@0: Object[] offs = offsets.toArray(); m@0: m@0: ArrayList durations = new ArrayList(); m@0: m@0: for(int i = 0; i < ons.length; i++) { m@0: durations.add(((Long)offs[i]).longValue() - m@0: ((Long)ons[i]).longValue()); m@0: } m@0: return durations; m@0: } m@0: m@0: /* return a list of inter-onset intervals (microseconds) */ m@0: public ArrayList getInterOnsetIntervals() { m@0: Object[] ons = onsets.toArray(); m@0: m@0: ArrayList iois = new ArrayList(); m@0: long firstIOI = 0; m@0: iois.add(firstIOI); // IOI of first note is zero m@0: m@0: for(int i = 1; i < ons.length; i++) { m@0: iois.add(((Long)ons[i]).longValue() - m@0: ((Long)ons[i-1]).longValue()); m@0: } m@0: return iois; m@0: } m@0: m@0: /* return the length of a rest preceding a note */ m@0: public ArrayList getDeltaST() { m@0: Object[] durs = getDurations().toArray(); m@0: Object[] iois = getInterOnsetIntervals().toArray(); m@0: m@0: ArrayList deltaST = new ArrayList(); m@0: long firstDeltaST = 0; m@0: deltaST.add(firstDeltaST); // deltaST of first note is zero m@0: m@0: for(int i = 1; i < durs.length; i++) { m@0: deltaST.add(((Long)durs[i-1]).longValue() - m@0: ((Long)iois[i]).longValue()); m@0: } m@0: return deltaST; m@0: } m@0: jeremy@24: /* jeremy@24: * Return the tatum of the midi file, i.e. the shortest jeremy@24: * note duration or positive rest. jeremy@24: */ JShulver@23: public long getTatum() { JShulver@23: JShulver@23: m@0: Object[] durs = getDurations().toArray(); m@0: Object[] rests = getDeltaST().toArray(); m@0: long tatum = -1; m@0: jeremy@24: m@0: for(int i = 0; i < durs.length; i++) { m@0: long dur = ((Long)durs[i]).longValue(); m@0: long rest = ((Long)rests[i]).longValue(); m@0: long min = dur; jeremy@24: if (rest > 0) m@0: min = Math.min(dur, rest); jeremy@24: if (debug) jeremy@24: System.out.println("Dur: " + dur + " Rest: " + rest); m@0: if (tatum < 0) m@0: tatum = dur; m@0: else if (min < tatum) m@0: tatum = min; m@0: } JShulver@23: //tatum = sequence.getResolution()*1000; m@0: return tatum; m@0: } m@0: m@0: /* return length of sequence in microseconds */ m@0: public long getLength() { m@0: return sequence.getMicrosecondLength(); m@0: } m@0: m@0: /* play the midi file */ m@0: public void play() { marcus@16: //System.out.println("MidiPlayer.play: run sequencer.start()."); m@0: sequencer.start(); m@0: } m@0: m@0: public void stop() { m@4: if (synthesizer != null) { m@4: synthesizer.close(); m@4: } m@4: if (sequencer != null) { m@4: sequencer.close(); m@4: } m@4: sequencer = null; m@4: synthesizer = null; m@4: } m@0: }