Mercurial > hg > mep
view MidiPlayer.java @ 47:be66ee2fe9fe
Added abstract class EndBlockPanel to deliver end of block messages (e.g. end of practice block).
Added and implemented EndTestPanel which gives "thank you for participating" etc message at end of test.
author | Carl Bussey <c.bussey@se10.qmul.ac.uk> |
---|---|
date | Thu, 13 Jun 2013 18:33:34 +0100 |
parents | 75354c638975 |
children |
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 File midiFile; private boolean defaultMD = false; private int midiDevice; private boolean debug; /* accessors */ public Sequencer getSequencer() { return sequencer; } public boolean usingDefault() { return defaultMD; } /* Constructors */ public MidiPlayer(String path, int deviceNumber, boolean d) { if(deviceNumber == -1) { defaultMD = true; midiDevice = 0; } else{ midiDevice = deviceNumber; } midiFile = new File(path); debug = d; setup(); } private void setup(){ // Get sequence try { sequence = MidiSystem.getSequence(midiFile); } catch (InvalidMidiDataException e) { e.printStackTrace(); System.exit(1); } catch (IOException 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(); // } // } // } // }); // 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()); } MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]); synth.open(); // Get sequencer try { sequencer = MidiSystem.getSequencer(defaultMD); } catch (MidiUnavailableException e) { e.printStackTrace(); System.exit(1); } // synthesizer = MidiSystem.getSynthesizer(); 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); }//// // Change the patch // MidiChannel channels[] = synthesizer.getChannels(); // for (int i = 0; i < channels.length; i++) // channels[i].programChange(65); 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; } }