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