m@0
|
1 /*=============================================================================
|
m@0
|
2 * File: MidiPlayer.java
|
m@0
|
3 * Author: Marcus Pearce <m.pearce@gold.ac.uk>
|
m@0
|
4 * Created: <2007-02-14 12:13:56 marcusp>
|
marcus@16
|
5 * Time-stamp: <2011-11-15 16:52:06 marcusp>
|
m@0
|
6 *=============================================================================
|
m@0
|
7 */
|
m@0
|
8
|
m@0
|
9 /*
|
m@0
|
10 * Based on:
|
m@0
|
11 * http://www.jsresources.org/examples/SimpleMidiPlayer.html
|
m@0
|
12 * http://www.jsresources.org/examples/DumpSequence.html
|
m@0
|
13 */
|
m@0
|
14
|
m@0
|
15 import java.io.File;
|
m@0
|
16 import java.io.IOException;
|
m@0
|
17 import java.util.ArrayList;
|
m@0
|
18
|
m@0
|
19 import javax.sound.midi.MidiSystem;
|
m@0
|
20 import javax.sound.midi.MidiDevice;
|
m@0
|
21 import javax.sound.midi.InvalidMidiDataException;
|
m@0
|
22 import javax.sound.midi.MidiUnavailableException;
|
m@0
|
23 import javax.sound.midi.MetaEventListener;
|
m@0
|
24 import javax.sound.midi.Sequence;
|
m@0
|
25 import javax.sound.midi.Sequencer;
|
m@0
|
26 import javax.sound.midi.Synthesizer;
|
m@0
|
27 import javax.sound.midi.MidiChannel;
|
m@0
|
28 import javax.sound.midi.Receiver;
|
m@0
|
29 import javax.sound.midi.Transmitter;
|
m@0
|
30 import javax.sound.midi.Track;
|
m@0
|
31 import javax.sound.midi.MidiEvent;
|
m@0
|
32 import javax.sound.midi.MidiMessage;
|
m@0
|
33 import javax.sound.midi.ShortMessage;
|
m@0
|
34 import javax.sound.midi.MetaMessage;
|
m@0
|
35 import javax.sound.midi.SysexMessage;
|
m@0
|
36 import javax.sound.midi.MetaMessage;
|
JShulver@22
|
37 import java.util.Arrays;
|
m@0
|
38 public class MidiPlayer {
|
m@0
|
39
|
m@0
|
40 /* variables */
|
m@0
|
41 private Sequencer sequencer = null;
|
m@0
|
42 private Synthesizer synthesizer = null;
|
m@0
|
43 private Sequence sequence = null;
|
m@0
|
44 private ArrayList onsets, offsets, pitches = null;
|
m@0
|
45 private int midiDevice = 0; /* 4 for usb midi device */
|
m@0
|
46
|
m@0
|
47 /* accessors */
|
m@0
|
48 public Sequencer getSequencer() { return sequencer; }
|
m@0
|
49
|
m@0
|
50 /* Constructor */
|
m@1
|
51 public MidiPlayer(String path, int deviceNumber) {
|
m@0
|
52 File midiFile = new File(path);
|
m@1
|
53 midiDevice = deviceNumber;
|
m@0
|
54
|
m@0
|
55 // Get sequence
|
m@0
|
56 try { sequence = MidiSystem.getSequence(midiFile); }
|
m@0
|
57 catch (InvalidMidiDataException e) {
|
m@0
|
58 e.printStackTrace();
|
m@0
|
59 System.exit(1);
|
m@0
|
60 }
|
m@0
|
61 catch (IOException e) {
|
m@0
|
62 e.printStackTrace();
|
m@0
|
63 System.exit(1);
|
m@0
|
64 }
|
m@0
|
65 // Get sequencer
|
m@0
|
66 try { sequencer = MidiSystem.getSequencer(); }
|
m@0
|
67 catch (MidiUnavailableException e) {
|
m@0
|
68 e.printStackTrace();
|
m@0
|
69 System.exit(1);
|
m@0
|
70 }
|
m@0
|
71 //sequencer.setTempoInBPM(bpm);
|
m@0
|
72 // Workaround bug in JDK
|
m@0
|
73 // sequencer.addMetaEventListener(new MetaEventListener() {
|
m@0
|
74 // public void meta(MetaMessage event) {
|
m@0
|
75 // if (event.getType() == 47) {
|
m@0
|
76 // sequencer.close();
|
m@0
|
77 // if (synthesizer != null) {
|
m@0
|
78 // synthesizer.close();
|
m@0
|
79 // }
|
m@0
|
80 // }
|
m@0
|
81 // }
|
m@0
|
82 // });
|
m@0
|
83 // Open sequencer
|
m@0
|
84 try { sequencer.open(); }
|
m@0
|
85 catch (MidiUnavailableException e) {
|
m@0
|
86 e.printStackTrace();
|
m@0
|
87 System.exit(1);
|
m@0
|
88 }
|
m@0
|
89 // Assign sequence to sequencer
|
m@0
|
90 try { sequencer.setSequence(sequence); }
|
m@0
|
91 catch (InvalidMidiDataException e) {
|
m@0
|
92 e.printStackTrace();
|
m@0
|
93 System.exit(1);
|
m@0
|
94 }
|
m@0
|
95 // Set up MIDI output for sequence
|
m@0
|
96 try {
|
m@0
|
97 MidiDevice.Info msinfo[] = MidiSystem.getMidiDeviceInfo();
|
m@0
|
98 for(int i = 0; i < msinfo.length; i++) {
|
m@0
|
99 MidiDevice.Info m = msinfo[i];
|
m@0
|
100 // System.out.println("Name: " + m.getName() +
|
m@0
|
101 // "; Vendor: " + m.getVendor() +
|
m@0
|
102 // "; Version: " + m.getVersion());
|
m@0
|
103 }
|
m@0
|
104 // synthesizer = MidiSystem.getSynthesizer();
|
m@0
|
105 MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]);
|
m@0
|
106
|
m@0
|
107 // Change the patch
|
m@0
|
108 // MidiChannel channels[] = synthesizer.getChannels();
|
m@0
|
109 // for (int i = 0; i < channels.length; i++)
|
m@0
|
110 // channels[i].programChange(65);
|
m@0
|
111
|
m@0
|
112 synth.open();
|
m@0
|
113 Receiver synthReceiver = synth.getReceiver();
|
m@0
|
114 Transmitter seqTransmitter = sequencer.getTransmitter();
|
m@0
|
115 seqTransmitter.setReceiver(synthReceiver);
|
m@0
|
116
|
m@0
|
117 }
|
m@0
|
118 catch (MidiUnavailableException e) {
|
m@0
|
119 e.printStackTrace();
|
m@0
|
120 }
|
m@0
|
121
|
m@0
|
122 // compute data from the MIDI file
|
m@0
|
123 onsets = computeOnsets();
|
m@0
|
124 offsets = computeOffsets();
|
m@0
|
125 pitches = computePitches();
|
m@0
|
126
|
m@0
|
127 //String divisionType;
|
m@0
|
128 //if (sequence.getDivisionType() == sequence.PPQ)
|
m@0
|
129 // divisionType = "ppq";
|
m@0
|
130 //else
|
m@0
|
131 // divisionType = "smpte";
|
m@0
|
132 //System.out.println("division type = " + divisionType +
|
m@0
|
133 // "; resolution = " + sequence.getResolution());
|
m@0
|
134 }
|
m@0
|
135
|
m@0
|
136 /* number of microseconds per MIDI tick */
|
m@0
|
137 private double microsecondsPerTick() {
|
m@0
|
138 double seqTickLength = (float)sequence.getTickLength();
|
m@0
|
139 double seqMicrosecondLength = (float)sequence.getMicrosecondLength();
|
m@0
|
140 double microsecondsPerTick = seqMicrosecondLength / seqTickLength;
|
m@0
|
141 //System.out.println("seqTickLength = " + seqTickLength);
|
m@0
|
142 //System.out.println("seqMicrosecondLength = " + seqMicrosecondLength);
|
m@0
|
143 //System.out.println("microsecondsPerTick = " + microsecondsPerTick);
|
m@0
|
144 return microsecondsPerTick;
|
m@0
|
145 }
|
m@0
|
146
|
m@0
|
147 private long ticksToMicroseconds(long tick) {
|
m@0
|
148 double microsecondsPerTick = microsecondsPerTick();
|
m@0
|
149 double seconds = (double)tick * microsecondsPerTick;
|
m@0
|
150
|
m@0
|
151 return (long)(10 * Math.floor(seconds * 0.1));
|
m@0
|
152 }
|
m@0
|
153
|
m@0
|
154 /* compute a list of note onset times (microseconds) */
|
m@0
|
155 private ArrayList computeOnsets() {
|
m@0
|
156 ArrayList ons = new ArrayList();
|
m@0
|
157 Track[] tracks = sequence.getTracks();
|
m@0
|
158
|
m@0
|
159 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
160 Track track = tracks[nTrack];
|
m@0
|
161 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
162 MidiEvent event = track.get(nEvent);
|
m@0
|
163 MidiMessage message = event.getMessage();
|
m@0
|
164 if (message instanceof ShortMessage &&
|
m@0
|
165 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) {
|
m@0
|
166 // System.out.println("onset in ticks = " + event.getTick()+
|
m@0
|
167 // "; onset in microseconds = " +
|
m@0
|
168 // ticksToMicroseconds(event.getTick()));
|
JShulver@22
|
169 //if the event does not have a velocity of zero (i.e. switching a note off)
|
JShulver@22
|
170 if(message.getMessage()[2] != 0) {
|
JShulver@22
|
171 ons.add(ticksToMicroseconds(event.getTick())); //THEN we can add the note
|
JShulver@22
|
172 }
|
m@0
|
173 }
|
m@0
|
174 }
|
m@0
|
175 }
|
m@0
|
176 return ons;
|
m@0
|
177 }
|
m@0
|
178
|
m@0
|
179 /* compute a list of note offset times (microseconds) */
|
m@0
|
180 private ArrayList computeOffsets() {
|
m@0
|
181 ArrayList offs = new ArrayList();
|
m@0
|
182 Track[] tracks = sequence.getTracks();
|
m@0
|
183
|
m@0
|
184 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
185 Track track = tracks[nTrack];
|
m@0
|
186 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
187 MidiEvent event = track.get(nEvent);
|
m@0
|
188 MidiMessage message = event.getMessage();
|
m@0
|
189 if (message instanceof ShortMessage &&
|
m@0
|
190 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) {
|
m@0
|
191 //System.out.println("offset in ticks = " + event.getTick()+
|
m@0
|
192 // "; microsecondsPerTick = " + microsecondsPerTick +
|
m@0
|
193 // "; offset in microseconds = " +
|
m@0
|
194 // (float)event.getTick() * microsecondsPerTick);
|
m@0
|
195 offs.add(ticksToMicroseconds(event.getTick()));
|
m@0
|
196 }
|
JShulver@22
|
197 //if we have not found a note off,
|
JShulver@22
|
198 else if (message instanceof ShortMessage
|
JShulver@22
|
199 && ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON
|
JShulver@22
|
200 && message.getMessage()[2] == 0) { //but it is a note on with a velocity of zero
|
JShulver@22
|
201 offs.add(ticksToMicroseconds(event.getTick())); //add it as an off signal
|
JShulver@22
|
202 }
|
JShulver@22
|
203
|
m@0
|
204 }
|
m@0
|
205 }
|
m@0
|
206 return offs;
|
m@0
|
207 }
|
m@0
|
208
|
m@0
|
209 /* compute a list of note pitches */
|
m@0
|
210 private ArrayList computePitches() {
|
m@0
|
211 ArrayList pit = new ArrayList();
|
m@0
|
212 Track[] tracks = sequence.getTracks();
|
m@0
|
213
|
m@0
|
214 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
215 Track track = tracks[nTrack];
|
m@0
|
216 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
217 MidiEvent event = track.get(nEvent);
|
m@0
|
218 MidiMessage message = event.getMessage();
|
m@0
|
219 if (message instanceof ShortMessage &&
|
m@0
|
220 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON)
|
m@0
|
221 pit.add(((ShortMessage)message).getData1());
|
m@0
|
222 }
|
m@0
|
223 }
|
m@0
|
224 return pit;
|
m@0
|
225 }
|
m@0
|
226
|
m@0
|
227 /* return a list of note onset times (milliseconds) */
|
m@0
|
228 public ArrayList getOnsets() { return onsets; }
|
m@0
|
229
|
m@0
|
230 /* return a list of note offset times (milliseconds) */
|
m@0
|
231 public ArrayList getOffsets() { return offsets; }
|
m@0
|
232
|
m@0
|
233 /* return a list of note pitches */
|
m@0
|
234 public ArrayList getPitches() { return pitches; }
|
m@0
|
235
|
m@0
|
236 /* return a list of note durations (microseconds) */
|
m@0
|
237 public ArrayList getDurations() {
|
m@0
|
238 Object[] ons = onsets.toArray();
|
m@0
|
239 Object[] offs = offsets.toArray();
|
m@0
|
240
|
m@0
|
241 ArrayList durations = new ArrayList();
|
m@0
|
242
|
m@0
|
243 for(int i = 0; i < ons.length; i++) {
|
m@0
|
244 durations.add(((Long)offs[i]).longValue() -
|
m@0
|
245 ((Long)ons[i]).longValue());
|
m@0
|
246 }
|
m@0
|
247 return durations;
|
m@0
|
248 }
|
m@0
|
249
|
m@0
|
250 /* return a list of inter-onset intervals (microseconds) */
|
m@0
|
251 public ArrayList getInterOnsetIntervals() {
|
m@0
|
252 Object[] ons = onsets.toArray();
|
m@0
|
253
|
m@0
|
254 ArrayList iois = new ArrayList();
|
m@0
|
255 long firstIOI = 0;
|
m@0
|
256 iois.add(firstIOI); // IOI of first note is zero
|
m@0
|
257
|
m@0
|
258 for(int i = 1; i < ons.length; i++) {
|
m@0
|
259 iois.add(((Long)ons[i]).longValue() -
|
m@0
|
260 ((Long)ons[i-1]).longValue());
|
m@0
|
261 }
|
m@0
|
262 return iois;
|
m@0
|
263 }
|
m@0
|
264
|
m@0
|
265 /* return the length of a rest preceding a note */
|
m@0
|
266 public ArrayList getDeltaST() {
|
m@0
|
267 Object[] durs = getDurations().toArray();
|
m@0
|
268 Object[] iois = getInterOnsetIntervals().toArray();
|
m@0
|
269
|
m@0
|
270 ArrayList deltaST = new ArrayList();
|
m@0
|
271 long firstDeltaST = 0;
|
m@0
|
272 deltaST.add(firstDeltaST); // deltaST of first note is zero
|
m@0
|
273
|
m@0
|
274 for(int i = 1; i < durs.length; i++) {
|
m@0
|
275 deltaST.add(((Long)durs[i-1]).longValue() -
|
m@0
|
276 ((Long)iois[i]).longValue());
|
m@0
|
277 }
|
m@0
|
278 return deltaST;
|
m@0
|
279 }
|
m@0
|
280
|
m@0
|
281 /* return the tatum of the midi file */
|
m@0
|
282 public long getTatum() {
|
m@0
|
283 Object[] durs = getDurations().toArray();
|
m@0
|
284 Object[] rests = getDeltaST().toArray();
|
m@0
|
285 long tatum = -1;
|
m@0
|
286
|
m@0
|
287 for(int i = 0; i < durs.length; i++) {
|
m@0
|
288 long dur = ((Long)durs[i]).longValue();
|
m@0
|
289 long rest = ((Long)rests[i]).longValue();
|
m@0
|
290 long min = dur;
|
m@0
|
291 if (rest != 0)
|
m@0
|
292 min = Math.min(dur, rest);
|
m@0
|
293
|
m@0
|
294 if (tatum < 0)
|
m@0
|
295 tatum = dur;
|
m@0
|
296 else if (min < tatum)
|
m@0
|
297 tatum = min;
|
m@0
|
298 }
|
m@0
|
299 return tatum;
|
m@0
|
300 }
|
m@0
|
301
|
m@0
|
302 /* return length of sequence in microseconds */
|
m@0
|
303 public long getLength() {
|
m@0
|
304 return sequence.getMicrosecondLength();
|
m@0
|
305 }
|
m@0
|
306
|
m@0
|
307 /* play the midi file */
|
m@0
|
308 public void play() {
|
marcus@16
|
309 //System.out.println("MidiPlayer.play: run sequencer.start().");
|
m@0
|
310 sequencer.start();
|
m@0
|
311 }
|
m@0
|
312
|
m@0
|
313 public void stop() {
|
m@4
|
314 if (synthesizer != null) {
|
m@4
|
315 synthesizer.close();
|
m@4
|
316 }
|
m@4
|
317 if (sequencer != null) {
|
m@4
|
318 sequencer.close();
|
m@4
|
319 }
|
m@4
|
320 sequencer = null;
|
m@4
|
321 synthesizer = null;
|
m@4
|
322 }
|
m@0
|
323 }
|