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@0
|
5 * Time-stamp: <2008-10-20 16:52:05 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@0
|
51 public MidiPlayer(String path) {
|
m@0
|
52 File midiFile = new File(path);
|
m@0
|
53
|
m@0
|
54 // Get sequence
|
m@0
|
55 try { sequence = MidiSystem.getSequence(midiFile); }
|
m@0
|
56 catch (InvalidMidiDataException e) {
|
m@0
|
57 e.printStackTrace();
|
m@0
|
58 System.exit(1);
|
m@0
|
59 }
|
m@0
|
60 catch (IOException e) {
|
m@0
|
61 e.printStackTrace();
|
m@0
|
62 System.exit(1);
|
m@0
|
63 }
|
m@0
|
64 // Get sequencer
|
m@0
|
65 try { sequencer = MidiSystem.getSequencer(); }
|
m@0
|
66 catch (MidiUnavailableException e) {
|
m@0
|
67 e.printStackTrace();
|
m@0
|
68 System.exit(1);
|
m@0
|
69 }
|
m@0
|
70 //sequencer.setTempoInBPM(bpm);
|
m@0
|
71 // Workaround bug in JDK
|
m@0
|
72 // sequencer.addMetaEventListener(new MetaEventListener() {
|
m@0
|
73 // public void meta(MetaMessage event) {
|
m@0
|
74 // if (event.getType() == 47) {
|
m@0
|
75 // sequencer.close();
|
m@0
|
76 // if (synthesizer != null) {
|
m@0
|
77 // synthesizer.close();
|
m@0
|
78 // }
|
m@0
|
79 // }
|
m@0
|
80 // }
|
m@0
|
81 // });
|
m@0
|
82 // Open sequencer
|
m@0
|
83 try { sequencer.open(); }
|
m@0
|
84 catch (MidiUnavailableException e) {
|
m@0
|
85 e.printStackTrace();
|
m@0
|
86 System.exit(1);
|
m@0
|
87 }
|
m@0
|
88 // Assign sequence to sequencer
|
m@0
|
89 try { sequencer.setSequence(sequence); }
|
m@0
|
90 catch (InvalidMidiDataException e) {
|
m@0
|
91 e.printStackTrace();
|
m@0
|
92 System.exit(1);
|
m@0
|
93 }
|
m@0
|
94 // Set up MIDI output for sequence
|
m@0
|
95 try {
|
m@0
|
96 MidiDevice.Info msinfo[] = MidiSystem.getMidiDeviceInfo();
|
m@0
|
97 for(int i = 0; i < msinfo.length; i++) {
|
m@0
|
98 MidiDevice.Info m = msinfo[i];
|
m@0
|
99 // System.out.println("Name: " + m.getName() +
|
m@0
|
100 // "; Vendor: " + m.getVendor() +
|
m@0
|
101 // "; Version: " + m.getVersion());
|
m@0
|
102 }
|
m@0
|
103 // synthesizer = MidiSystem.getSynthesizer();
|
m@0
|
104 MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]);
|
m@0
|
105
|
m@0
|
106 // Change the patch
|
m@0
|
107 // MidiChannel channels[] = synthesizer.getChannels();
|
m@0
|
108 // for (int i = 0; i < channels.length; i++)
|
m@0
|
109 // channels[i].programChange(65);
|
m@0
|
110
|
m@0
|
111 synth.open();
|
m@0
|
112 Receiver synthReceiver = synth.getReceiver();
|
m@0
|
113 Transmitter seqTransmitter = sequencer.getTransmitter();
|
m@0
|
114 seqTransmitter.setReceiver(synthReceiver);
|
m@0
|
115
|
m@0
|
116 }
|
m@0
|
117 catch (MidiUnavailableException e) {
|
m@0
|
118 e.printStackTrace();
|
m@0
|
119 }
|
m@0
|
120
|
m@0
|
121 // compute data from the MIDI file
|
m@0
|
122 onsets = computeOnsets();
|
m@0
|
123 offsets = computeOffsets();
|
m@0
|
124 pitches = computePitches();
|
m@0
|
125
|
m@0
|
126 //String divisionType;
|
m@0
|
127 //if (sequence.getDivisionType() == sequence.PPQ)
|
m@0
|
128 // divisionType = "ppq";
|
m@0
|
129 //else
|
m@0
|
130 // divisionType = "smpte";
|
m@0
|
131 //System.out.println("division type = " + divisionType +
|
m@0
|
132 // "; resolution = " + sequence.getResolution());
|
m@0
|
133 }
|
m@0
|
134
|
m@0
|
135 /* number of microseconds per MIDI tick */
|
m@0
|
136 private double microsecondsPerTick() {
|
m@0
|
137 double seqTickLength = (float)sequence.getTickLength();
|
m@0
|
138 double seqMicrosecondLength = (float)sequence.getMicrosecondLength();
|
m@0
|
139 double microsecondsPerTick = seqMicrosecondLength / seqTickLength;
|
m@0
|
140 //System.out.println("seqTickLength = " + seqTickLength);
|
m@0
|
141 //System.out.println("seqMicrosecondLength = " + seqMicrosecondLength);
|
m@0
|
142 //System.out.println("microsecondsPerTick = " + microsecondsPerTick);
|
m@0
|
143 return microsecondsPerTick;
|
m@0
|
144 }
|
m@0
|
145
|
m@0
|
146 private long ticksToMicroseconds(long tick) {
|
m@0
|
147 double microsecondsPerTick = microsecondsPerTick();
|
m@0
|
148 double seconds = (double)tick * microsecondsPerTick;
|
m@0
|
149
|
m@0
|
150 return (long)(10 * Math.floor(seconds * 0.1));
|
m@0
|
151 }
|
m@0
|
152
|
m@0
|
153 /* compute a list of note onset times (microseconds) */
|
m@0
|
154 private ArrayList computeOnsets() {
|
m@0
|
155 ArrayList ons = new ArrayList();
|
m@0
|
156 Track[] tracks = sequence.getTracks();
|
m@0
|
157
|
m@0
|
158 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
159 Track track = tracks[nTrack];
|
m@0
|
160 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
161 MidiEvent event = track.get(nEvent);
|
m@0
|
162 MidiMessage message = event.getMessage();
|
m@0
|
163 if (message instanceof ShortMessage &&
|
m@0
|
164 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) {
|
m@0
|
165 // System.out.println("onset in ticks = " + event.getTick()+
|
m@0
|
166 // "; onset in microseconds = " +
|
m@0
|
167 // ticksToMicroseconds(event.getTick()));
|
m@0
|
168 ons.add(ticksToMicroseconds(event.getTick()));
|
m@0
|
169 }
|
m@0
|
170 }
|
m@0
|
171 }
|
m@0
|
172 return ons;
|
m@0
|
173 }
|
m@0
|
174
|
m@0
|
175 /* compute a list of note offset times (microseconds) */
|
m@0
|
176 private ArrayList computeOffsets() {
|
m@0
|
177 ArrayList offs = new ArrayList();
|
m@0
|
178 Track[] tracks = sequence.getTracks();
|
m@0
|
179
|
m@0
|
180 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
181 Track track = tracks[nTrack];
|
m@0
|
182 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
183 MidiEvent event = track.get(nEvent);
|
m@0
|
184 MidiMessage message = event.getMessage();
|
m@0
|
185 if (message instanceof ShortMessage &&
|
m@0
|
186 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) {
|
m@0
|
187 //System.out.println("offset in ticks = " + event.getTick()+
|
m@0
|
188 // "; microsecondsPerTick = " + microsecondsPerTick +
|
m@0
|
189 // "; offset in microseconds = " +
|
m@0
|
190 // (float)event.getTick() * microsecondsPerTick);
|
m@0
|
191 offs.add(ticksToMicroseconds(event.getTick()));
|
m@0
|
192 }
|
m@0
|
193 }
|
m@0
|
194 }
|
m@0
|
195 return offs;
|
m@0
|
196 }
|
m@0
|
197
|
m@0
|
198 /* compute a list of note pitches */
|
m@0
|
199 private ArrayList computePitches() {
|
m@0
|
200 ArrayList pit = new ArrayList();
|
m@0
|
201 Track[] tracks = sequence.getTracks();
|
m@0
|
202
|
m@0
|
203 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
|
m@0
|
204 Track track = tracks[nTrack];
|
m@0
|
205 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
|
m@0
|
206 MidiEvent event = track.get(nEvent);
|
m@0
|
207 MidiMessage message = event.getMessage();
|
m@0
|
208 if (message instanceof ShortMessage &&
|
m@0
|
209 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON)
|
m@0
|
210 pit.add(((ShortMessage)message).getData1());
|
m@0
|
211 }
|
m@0
|
212 }
|
m@0
|
213 return pit;
|
m@0
|
214 }
|
m@0
|
215
|
m@0
|
216 /* return a list of note onset times (milliseconds) */
|
m@0
|
217 public ArrayList getOnsets() { return onsets; }
|
m@0
|
218
|
m@0
|
219 /* return a list of note offset times (milliseconds) */
|
m@0
|
220 public ArrayList getOffsets() { return offsets; }
|
m@0
|
221
|
m@0
|
222 /* return a list of note pitches */
|
m@0
|
223 public ArrayList getPitches() { return pitches; }
|
m@0
|
224
|
m@0
|
225 /* return a list of note durations (microseconds) */
|
m@0
|
226 public ArrayList getDurations() {
|
m@0
|
227 Object[] ons = onsets.toArray();
|
m@0
|
228 Object[] offs = offsets.toArray();
|
m@0
|
229
|
m@0
|
230 ArrayList durations = new ArrayList();
|
m@0
|
231
|
m@0
|
232 for(int i = 0; i < ons.length; i++) {
|
m@0
|
233 durations.add(((Long)offs[i]).longValue() -
|
m@0
|
234 ((Long)ons[i]).longValue());
|
m@0
|
235 }
|
m@0
|
236 return durations;
|
m@0
|
237 }
|
m@0
|
238
|
m@0
|
239 /* return a list of inter-onset intervals (microseconds) */
|
m@0
|
240 public ArrayList getInterOnsetIntervals() {
|
m@0
|
241 Object[] ons = onsets.toArray();
|
m@0
|
242
|
m@0
|
243 ArrayList iois = new ArrayList();
|
m@0
|
244 long firstIOI = 0;
|
m@0
|
245 iois.add(firstIOI); // IOI of first note is zero
|
m@0
|
246
|
m@0
|
247 for(int i = 1; i < ons.length; i++) {
|
m@0
|
248 iois.add(((Long)ons[i]).longValue() -
|
m@0
|
249 ((Long)ons[i-1]).longValue());
|
m@0
|
250 }
|
m@0
|
251 return iois;
|
m@0
|
252 }
|
m@0
|
253
|
m@0
|
254 /* return the length of a rest preceding a note */
|
m@0
|
255 public ArrayList getDeltaST() {
|
m@0
|
256 Object[] durs = getDurations().toArray();
|
m@0
|
257 Object[] iois = getInterOnsetIntervals().toArray();
|
m@0
|
258
|
m@0
|
259 ArrayList deltaST = new ArrayList();
|
m@0
|
260 long firstDeltaST = 0;
|
m@0
|
261 deltaST.add(firstDeltaST); // deltaST of first note is zero
|
m@0
|
262
|
m@0
|
263 for(int i = 1; i < durs.length; i++) {
|
m@0
|
264 deltaST.add(((Long)durs[i-1]).longValue() -
|
m@0
|
265 ((Long)iois[i]).longValue());
|
m@0
|
266 }
|
m@0
|
267 return deltaST;
|
m@0
|
268 }
|
m@0
|
269
|
m@0
|
270 /* return the tatum of the midi file */
|
m@0
|
271 public long getTatum() {
|
m@0
|
272 Object[] durs = getDurations().toArray();
|
m@0
|
273 Object[] rests = getDeltaST().toArray();
|
m@0
|
274 long tatum = -1;
|
m@0
|
275
|
m@0
|
276 for(int i = 0; i < durs.length; i++) {
|
m@0
|
277 long dur = ((Long)durs[i]).longValue();
|
m@0
|
278 long rest = ((Long)rests[i]).longValue();
|
m@0
|
279 long min = dur;
|
m@0
|
280 if (rest != 0)
|
m@0
|
281 min = Math.min(dur, rest);
|
m@0
|
282
|
m@0
|
283 if (tatum < 0)
|
m@0
|
284 tatum = dur;
|
m@0
|
285 else if (min < tatum)
|
m@0
|
286 tatum = min;
|
m@0
|
287 }
|
m@0
|
288 return tatum;
|
m@0
|
289 }
|
m@0
|
290
|
m@0
|
291 /* return length of sequence in microseconds */
|
m@0
|
292 public long getLength() {
|
m@0
|
293 return sequence.getMicrosecondLength();
|
m@0
|
294 }
|
m@0
|
295
|
m@0
|
296 /* play the midi file */
|
m@0
|
297 public void play() {
|
m@0
|
298 sequencer.start();
|
m@0
|
299 }
|
m@0
|
300
|
m@0
|
301 public void stop() {
|
m@0
|
302 sequencer.close();
|
m@0
|
303 }
|
m@0
|
304 }
|