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>
|
m@4
|
5 * Time-stamp: <2010-11-18 11:01:56 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;
|
m@0
|
37
|
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()));
|
m@0
|
169 ons.add(ticksToMicroseconds(event.getTick()));
|
m@0
|
170 }
|
m@0
|
171 }
|
m@0
|
172 }
|
m@0
|
173 return ons;
|
m@0
|
174 }
|
m@0
|
175
|
m@0
|
176 /* compute a list of note offset times (microseconds) */
|
m@0
|
177 private ArrayList computeOffsets() {
|
m@0
|
178 ArrayList offs = new ArrayList();
|
m@0
|
179 Track[] tracks = sequence.getTracks();
|
m@0
|
180
|
m@0
|
181 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
182 Track track = tracks[nTrack];
|
m@0
|
183 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
184 MidiEvent event = track.get(nEvent);
|
m@0
|
185 MidiMessage message = event.getMessage();
|
m@0
|
186 if (message instanceof ShortMessage &&
|
m@0
|
187 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) {
|
m@0
|
188 //System.out.println("offset in ticks = " + event.getTick()+
|
m@0
|
189 // "; microsecondsPerTick = " + microsecondsPerTick +
|
m@0
|
190 // "; offset in microseconds = " +
|
m@0
|
191 // (float)event.getTick() * microsecondsPerTick);
|
m@0
|
192 offs.add(ticksToMicroseconds(event.getTick()));
|
m@0
|
193 }
|
m@0
|
194 }
|
m@0
|
195 }
|
m@0
|
196 return offs;
|
m@0
|
197 }
|
m@0
|
198
|
m@0
|
199 /* compute a list of note pitches */
|
m@0
|
200 private ArrayList computePitches() {
|
m@0
|
201 ArrayList pit = new ArrayList();
|
m@0
|
202 Track[] tracks = sequence.getTracks();
|
m@0
|
203
|
m@0
|
204 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
205 Track track = tracks[nTrack];
|
m@0
|
206 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
207 MidiEvent event = track.get(nEvent);
|
m@0
|
208 MidiMessage message = event.getMessage();
|
m@0
|
209 if (message instanceof ShortMessage &&
|
m@0
|
210 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON)
|
m@0
|
211 pit.add(((ShortMessage)message).getData1());
|
m@0
|
212 }
|
m@0
|
213 }
|
m@0
|
214 return pit;
|
m@0
|
215 }
|
m@0
|
216
|
m@0
|
217 /* return a list of note onset times (milliseconds) */
|
m@0
|
218 public ArrayList getOnsets() { return onsets; }
|
m@0
|
219
|
m@0
|
220 /* return a list of note offset times (milliseconds) */
|
m@0
|
221 public ArrayList getOffsets() { return offsets; }
|
m@0
|
222
|
m@0
|
223 /* return a list of note pitches */
|
m@0
|
224 public ArrayList getPitches() { return pitches; }
|
m@0
|
225
|
m@0
|
226 /* return a list of note durations (microseconds) */
|
m@0
|
227 public ArrayList getDurations() {
|
m@0
|
228 Object[] ons = onsets.toArray();
|
m@0
|
229 Object[] offs = offsets.toArray();
|
m@0
|
230
|
m@0
|
231 ArrayList durations = new ArrayList();
|
m@0
|
232
|
m@0
|
233 for(int i = 0; i < ons.length; i++) {
|
m@0
|
234 durations.add(((Long)offs[i]).longValue() -
|
m@0
|
235 ((Long)ons[i]).longValue());
|
m@0
|
236 }
|
m@0
|
237 return durations;
|
m@0
|
238 }
|
m@0
|
239
|
m@0
|
240 /* return a list of inter-onset intervals (microseconds) */
|
m@0
|
241 public ArrayList getInterOnsetIntervals() {
|
m@0
|
242 Object[] ons = onsets.toArray();
|
m@0
|
243
|
m@0
|
244 ArrayList iois = new ArrayList();
|
m@0
|
245 long firstIOI = 0;
|
m@0
|
246 iois.add(firstIOI); // IOI of first note is zero
|
m@0
|
247
|
m@0
|
248 for(int i = 1; i < ons.length; i++) {
|
m@0
|
249 iois.add(((Long)ons[i]).longValue() -
|
m@0
|
250 ((Long)ons[i-1]).longValue());
|
m@0
|
251 }
|
m@0
|
252 return iois;
|
m@0
|
253 }
|
m@0
|
254
|
m@0
|
255 /* return the length of a rest preceding a note */
|
m@0
|
256 public ArrayList getDeltaST() {
|
m@0
|
257 Object[] durs = getDurations().toArray();
|
m@0
|
258 Object[] iois = getInterOnsetIntervals().toArray();
|
m@0
|
259
|
m@0
|
260 ArrayList deltaST = new ArrayList();
|
m@0
|
261 long firstDeltaST = 0;
|
m@0
|
262 deltaST.add(firstDeltaST); // deltaST of first note is zero
|
m@0
|
263
|
m@0
|
264 for(int i = 1; i < durs.length; i++) {
|
m@0
|
265 deltaST.add(((Long)durs[i-1]).longValue() -
|
m@0
|
266 ((Long)iois[i]).longValue());
|
m@0
|
267 }
|
m@0
|
268 return deltaST;
|
m@0
|
269 }
|
m@0
|
270
|
m@0
|
271 /* return the tatum of the midi file */
|
m@0
|
272 public long getTatum() {
|
m@0
|
273 Object[] durs = getDurations().toArray();
|
m@0
|
274 Object[] rests = getDeltaST().toArray();
|
m@0
|
275 long tatum = -1;
|
m@0
|
276
|
m@0
|
277 for(int i = 0; i < durs.length; i++) {
|
m@0
|
278 long dur = ((Long)durs[i]).longValue();
|
m@0
|
279 long rest = ((Long)rests[i]).longValue();
|
m@0
|
280 long min = dur;
|
m@0
|
281 if (rest != 0)
|
m@0
|
282 min = Math.min(dur, rest);
|
m@0
|
283
|
m@0
|
284 if (tatum < 0)
|
m@0
|
285 tatum = dur;
|
m@0
|
286 else if (min < tatum)
|
m@0
|
287 tatum = min;
|
m@0
|
288 }
|
m@0
|
289 return tatum;
|
m@0
|
290 }
|
m@0
|
291
|
m@0
|
292 /* return length of sequence in microseconds */
|
m@0
|
293 public long getLength() {
|
m@0
|
294 return sequence.getMicrosecondLength();
|
m@0
|
295 }
|
m@0
|
296
|
m@0
|
297 /* play the midi file */
|
m@0
|
298 public void play() {
|
m@0
|
299 sequencer.start();
|
m@0
|
300 }
|
m@0
|
301
|
m@0
|
302 public void stop() {
|
m@4
|
303 if (synthesizer != null) {
|
m@4
|
304 synthesizer.close();
|
m@4
|
305 }
|
m@4
|
306 if (sequencer != null) {
|
m@4
|
307 sequencer.close();
|
m@4
|
308 }
|
m@4
|
309 sequencer = null;
|
m@4
|
310 synthesizer = null;
|
m@4
|
311 }
|
m@0
|
312 }
|