m@0: /*============================================================================= m@0: * File: MidiPlayer.java m@0: * Author: Marcus Pearce m@0: * Created: <2007-02-14 12:13:56 marcusp> m@0: * Time-stamp: <2008-10-20 16:52:05 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; m@0: 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; m@0: private ArrayList onsets, offsets, pitches = null; m@0: private int midiDevice = 0; /* 4 for usb midi device */ m@0: m@0: /* accessors */ m@0: public Sequencer getSequencer() { return sequencer; } m@0: m@0: /* Constructor */ m@0: public MidiPlayer(String path) { m@0: File midiFile = new File(path); m@0: m@0: // 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: // Get sequencer m@0: try { sequencer = MidiSystem.getSequencer(); } m@0: catch (MidiUnavailableException 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: // }); m@0: // Open sequencer m@0: try { sequencer.open(); } m@0: catch (MidiUnavailableException e) { m@0: e.printStackTrace(); m@0: System.exit(1); m@0: } m@0: // Assign sequence to sequencer m@0: try { sequencer.setSequence(sequence); } m@0: catch (InvalidMidiDataException e) { m@0: e.printStackTrace(); m@0: System.exit(1); m@0: } m@0: // 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++) { m@0: MidiDevice.Info m = msinfo[i]; m@0: // System.out.println("Name: " + m.getName() + m@0: // "; Vendor: " + m.getVendor() + m@0: // "; Version: " + m.getVersion()); m@0: } m@0: // synthesizer = MidiSystem.getSynthesizer(); m@0: MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]); 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); m@0: m@0: synth.open(); 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 m@0: onsets = computeOnsets(); m@0: offsets = computeOffsets(); m@0: pitches = computePitches(); m@0: m@0: //String divisionType; m@0: //if (sequence.getDivisionType() == sequence.PPQ) m@0: // divisionType = "ppq"; m@0: //else m@0: // divisionType = "smpte"; m@0: //System.out.println("division type = " + divisionType + m@0: // "; resolution = " + sequence.getResolution()); 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())); m@0: ons.add(ticksToMicroseconds(event.getTick())); 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: } 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: 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: m@0: /* return the tatum of the midi file */ m@0: public long getTatum() { m@0: Object[] durs = getDurations().toArray(); m@0: Object[] rests = getDeltaST().toArray(); m@0: long tatum = -1; m@0: 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; m@0: if (rest != 0) m@0: min = Math.min(dur, rest); m@0: m@0: if (tatum < 0) m@0: tatum = dur; m@0: else if (min < tatum) m@0: tatum = min; m@0: } 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() { m@0: sequencer.start(); m@0: } m@0: m@0: public void stop() { m@0: sequencer.close(); m@0: } m@0: }