annotate MidiPlayer.java @ 52:76e99859bdb4 tip

Add class files for previous changes.
author Marcus Pearce <marcus.pearce@eecs.qmul.ac.uk>
date Wed, 25 Feb 2015 10:11:04 +0000
parents 75354c638975
children
rev   line source
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;
jeremy@24 38 import java.util.Iterator;
jeremy@24 39
jeremy@24 40
m@0 41 public class MidiPlayer {
m@0 42
m@0 43 /* variables */
m@0 44 private Sequencer sequencer = null;
m@0 45 private Synthesizer synthesizer = null;
m@0 46 private Sequence sequence = null;
jeremy@24 47 private ArrayList<Long> onsets, offsets = null;
CBussey@39 48 private ArrayList<Integer> pitches, velocities = null;
CBussey@39 49 private File midiFile;
CBussey@39 50 private boolean defaultMD = false;
CBussey@39 51 private int midiDevice;
jeremy@24 52 private boolean debug;
m@0 53
m@0 54 /* accessors */
CBussey@39 55 public Sequencer getSequencer() { return sequencer; }
CBussey@39 56 public boolean usingDefault() { return defaultMD; }
m@0 57
CBussey@39 58 /* Constructors */
CBussey@39 59 public MidiPlayer(String path, int deviceNumber, boolean d) {
CBussey@39 60 if(deviceNumber == -1) {
CBussey@39 61 defaultMD = true;
CBussey@39 62 midiDevice = 0;
CBussey@39 63 }
CBussey@39 64 else{
CBussey@39 65 midiDevice = deviceNumber;
CBussey@39 66 }
CBussey@39 67 midiFile = new File(path);
CBussey@39 68 debug = d;
CBussey@39 69 setup();
CBussey@39 70 }
CBussey@39 71
CBussey@39 72 private void setup(){
CBussey@39 73 // Get sequence
m@0 74 try { sequence = MidiSystem.getSequence(midiFile); }
m@0 75 catch (InvalidMidiDataException e) {
m@0 76 e.printStackTrace();
m@0 77 System.exit(1);
m@0 78 }
m@0 79 catch (IOException e) {
m@0 80 e.printStackTrace();
m@0 81 System.exit(1);
m@0 82 }
m@0 83 //sequencer.setTempoInBPM(bpm);
m@0 84 // Workaround bug in JDK
m@0 85 // sequencer.addMetaEventListener(new MetaEventListener() {
m@0 86 // public void meta(MetaMessage event) {
m@0 87 // if (event.getType() == 47) {
m@0 88 // sequencer.close();
m@0 89 // if (synthesizer != null) {
m@0 90 // synthesizer.close();
m@0 91 // }
m@0 92 // }
m@0 93 // }
m@0 94 // });
CBussey@39 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++) {
CBussey@39 99 MidiDevice.Info m = msinfo[i];
CBussey@39 100 System.out.println("Name: " + m.getName() +
CBussey@39 101 "; Vendor: " + m.getVendor() +
CBussey@39 102 "; Version: " + m.getVersion());
m@0 103 }
CBussey@39 104 MidiDevice synth = MidiSystem.getMidiDevice(msinfo[midiDevice]);
CBussey@39 105 synth.open();
CBussey@39 106 // Get sequencer
CBussey@39 107 try { sequencer = MidiSystem.getSequencer(defaultMD); }
CBussey@39 108 catch (MidiUnavailableException e) {
CBussey@39 109 e.printStackTrace();
CBussey@39 110 System.exit(1);
CBussey@39 111 }
m@0 112 // synthesizer = MidiSystem.getSynthesizer();
CBussey@39 113
CBussey@39 114 try { sequencer.open(); }////
CBussey@39 115 catch (MidiUnavailableException e) {
CBussey@39 116 e.printStackTrace();
CBussey@39 117 System.exit(1);
CBussey@39 118 }
CBussey@39 119 // Assign sequence to sequencer
CBussey@39 120 try { sequencer.setSequence(sequence); }
CBussey@39 121 catch (InvalidMidiDataException e) {
CBussey@39 122 e.printStackTrace();
CBussey@39 123 System.exit(1);
CBussey@39 124 }////
m@0 125
m@0 126 // Change the patch
m@0 127 // MidiChannel channels[] = synthesizer.getChannels();
m@0 128 // for (int i = 0; i < channels.length; i++)
m@0 129 // channels[i].programChange(65);
CBussey@39 130
m@0 131 Receiver synthReceiver = synth.getReceiver();
m@0 132 Transmitter seqTransmitter = sequencer.getTransmitter();
m@0 133 seqTransmitter.setReceiver(synthReceiver);
m@0 134
m@0 135 }
m@0 136 catch (MidiUnavailableException e) {
m@0 137 e.printStackTrace();
m@0 138 }
m@0 139
m@0 140 // compute data from the MIDI file
jeremy@24 141
jeremy@24 142 /*
m@0 143 onsets = computeOnsets();
m@0 144 offsets = computeOffsets();
m@0 145 pitches = computePitches();
jeremy@24 146 */
jeremy@24 147 registerEvents();
m@0 148
jeremy@24 149 if (debug) {
jeremy@24 150 String divisionType;
jeremy@24 151 if (sequence.getDivisionType() == sequence.PPQ)
jeremy@24 152 divisionType = "ppq";
jeremy@24 153 else
jeremy@24 154 divisionType = "smpte";
jeremy@24 155 System.out.println("division type = " + divisionType +
jeremy@24 156 "; resolution = " + sequence.getResolution());
jeremy@24 157 }
m@0 158 }
m@0 159
m@0 160 /* number of microseconds per MIDI tick */
m@0 161 private double microsecondsPerTick() {
m@0 162 double seqTickLength = (float)sequence.getTickLength();
m@0 163 double seqMicrosecondLength = (float)sequence.getMicrosecondLength();
m@0 164 double microsecondsPerTick = seqMicrosecondLength / seqTickLength;
m@0 165 //System.out.println("seqTickLength = " + seqTickLength);
m@0 166 //System.out.println("seqMicrosecondLength = " + seqMicrosecondLength);
m@0 167 //System.out.println("microsecondsPerTick = " + microsecondsPerTick);
m@0 168 return microsecondsPerTick;
m@0 169 }
m@0 170
m@0 171 private long ticksToMicroseconds(long tick) {
m@0 172 double microsecondsPerTick = microsecondsPerTick();
m@0 173 double seconds = (double)tick * microsecondsPerTick;
m@0 174
m@0 175 return (long)(10 * Math.floor(seconds * 0.1));
m@0 176 }
m@0 177
m@0 178 /* compute a list of note onset times (microseconds) */
m@0 179 private ArrayList computeOnsets() {
m@0 180 ArrayList ons = new ArrayList();
m@0 181 Track[] tracks = sequence.getTracks();
m@0 182
m@0 183 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
m@0 184 Track track = tracks[nTrack];
m@0 185 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
m@0 186 MidiEvent event = track.get(nEvent);
m@0 187 MidiMessage message = event.getMessage();
m@0 188 if (message instanceof ShortMessage &&
m@0 189 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON) {
m@0 190 // System.out.println("onset in ticks = " + event.getTick()+
m@0 191 // "; onset in microseconds = " +
m@0 192 // ticksToMicroseconds(event.getTick()));
JShulver@22 193 //if the event does not have a velocity of zero (i.e. switching a note off)
JShulver@22 194 if(message.getMessage()[2] != 0) {
JShulver@22 195 ons.add(ticksToMicroseconds(event.getTick())); //THEN we can add the note
JShulver@22 196 }
m@0 197 }
m@0 198 }
m@0 199 }
m@0 200 return ons;
m@0 201 }
m@0 202
m@0 203 /* compute a list of note offset times (microseconds) */
m@0 204 private ArrayList computeOffsets() {
m@0 205 ArrayList offs = new ArrayList();
m@0 206 Track[] tracks = sequence.getTracks();
m@0 207
m@0 208 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
m@0 209 Track track = tracks[nTrack];
m@0 210 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
m@0 211 MidiEvent event = track.get(nEvent);
m@0 212 MidiMessage message = event.getMessage();
m@0 213 if (message instanceof ShortMessage &&
m@0 214 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_OFF) {
m@0 215 //System.out.println("offset in ticks = " + event.getTick()+
m@0 216 // "; microsecondsPerTick = " + microsecondsPerTick +
m@0 217 // "; offset in microseconds = " +
m@0 218 // (float)event.getTick() * microsecondsPerTick);
m@0 219 offs.add(ticksToMicroseconds(event.getTick()));
m@0 220 }
JShulver@22 221 //if we have not found a note off,
JShulver@22 222 else if (message instanceof ShortMessage
JShulver@22 223 && ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON
JShulver@22 224 && message.getMessage()[2] == 0) { //but it is a note on with a velocity of zero
JShulver@22 225 offs.add(ticksToMicroseconds(event.getTick())); //add it as an off signal
JShulver@22 226 }
JShulver@22 227
m@0 228 }
m@0 229 }
m@0 230 return offs;
m@0 231 }
m@0 232
m@0 233 /* compute a list of note pitches */
m@0 234 private ArrayList computePitches() {
m@0 235 ArrayList pit = new ArrayList();
m@0 236 Track[] tracks = sequence.getTracks();
m@0 237
m@0 238 for (int nTrack = 0; nTrack < tracks.length; nTrack++) {
m@0 239 Track track = tracks[nTrack];
m@0 240 for (int nEvent = 0; nEvent < track.size(); nEvent++) {
m@0 241 MidiEvent event = track.get(nEvent);
m@0 242 MidiMessage message = event.getMessage();
m@0 243 if (message instanceof ShortMessage &&
m@0 244 ((ShortMessage)message).getCommand() == ShortMessage.NOTE_ON)
m@0 245 pit.add(((ShortMessage)message).getData1());
m@0 246 }
m@0 247 }
m@0 248 return pit;
m@0 249 }
m@0 250
jeremy@24 251 /*
jeremy@24 252 * Simultaneously construct lists of event onsets, offsets and pitches
jeremy@24 253 */
jeremy@24 254 private void registerEvents() {
jeremy@24 255 onsets = new ArrayList<Long>();
jeremy@24 256 offsets = new ArrayList<Long>();
jeremy@24 257 pitches = new ArrayList<Integer>();
jeremy@24 258 velocities = new ArrayList<Integer>();
jeremy@24 259
jeremy@24 260 Track[] tracks = sequence.getTracks();
jeremy@24 261
jeremy@24 262 // Iterate over MIDI tracks
jeremy@24 263 for (int i = 0; i < tracks.length; i++) {
jeremy@24 264 Track track = tracks[i];
jeremy@24 265 // Iterate over track events
jeremy@24 266 for (int j = 0; j < track.size(); j++) {
jeremy@24 267 registerEvent(i, j);
jeremy@24 268 }
jeremy@24 269 }
jeremy@24 270
jeremy@24 271 if (debug) {
jeremy@24 272 System.out.println("\nRegistered events...");
jeremy@24 273 Iterator<Long> oi = onsets.iterator();
jeremy@24 274 Iterator<Long> fi = offsets.iterator();
jeremy@24 275 Iterator<Integer> pi = pitches.iterator();
jeremy@24 276 Iterator<Integer> vi = velocities.iterator();
jeremy@24 277
jeremy@24 278 int pos = 1;
jeremy@24 279 while (oi.hasNext() && fi.hasNext() && pi.hasNext() && vi.hasNext()) {
jeremy@24 280 System.out.println("Event " + pos + ": onset " + oi.next() + ", offset " + fi.next()
jeremy@24 281 + ", pitch " + pi.next() + ", velocity " + vi.next());
jeremy@24 282 pos++;
jeremy@24 283 }
jeremy@24 284
jeremy@24 285 if (oi.hasNext() || fi.hasNext() || pi.hasNext() || vi.hasNext()) {
jeremy@24 286 System.out.println("Warning: event lists not equal length.");
jeremy@24 287 }
jeremy@24 288 }
jeremy@24 289 }
jeremy@24 290
jeremy@24 291 /*
jeremy@24 292 * Add the given event to the onset, offset and pitch lists.
jeremy@24 293 */
jeremy@24 294 private void registerEvent(int trackIndex, int eventIndex) {
jeremy@24 295
jeremy@24 296 Track track = sequence.getTracks()[trackIndex];
jeremy@24 297 MidiEvent event = track.get(eventIndex);
jeremy@24 298 MidiMessage message = event.getMessage();
jeremy@24 299
jeremy@24 300 // Register NOTE_ON events
jeremy@24 301 if (message instanceof ShortMessage) {
jeremy@24 302 ShortMessage shortMsg = (ShortMessage) message;
jeremy@24 303 int velocity = shortMsg.getData2();
jeremy@24 304
jeremy@24 305 if (shortMsg.getCommand() == ShortMessage.NOTE_ON && velocity != 0) {
jeremy@24 306
jeremy@24 307 long onset = ticksToMicroseconds(event.getTick());
jeremy@24 308 int pitch = shortMsg.getData1();
jeremy@24 309 long offset = getOffset(trackIndex, eventIndex, onset, pitch);
jeremy@24 310
jeremy@24 311 registerEvent(onset, offset, pitch, velocity);
jeremy@24 312 }
jeremy@24 313 }
jeremy@24 314 }
jeremy@24 315
jeremy@24 316 /*
jeremy@24 317 * Add the event details to the onset, offset and pitch lists,
jeremy@24 318 * use onset to determine the list position.
jeremy@24 319 */
jeremy@24 320 private void registerEvent(long onset, long offset, int pitch, int velocity) {
jeremy@24 321
jeremy@24 322 int index = 0;
jeremy@24 323 boolean inserted = false;
jeremy@24 324
jeremy@24 325 while (index < onsets.size()) {
jeremy@24 326
jeremy@24 327 if (onsets.get(index) > onset)
jeremy@24 328 break;
jeremy@24 329 else
jeremy@24 330 index++;
jeremy@24 331 }
jeremy@24 332
jeremy@24 333 onsets.add(index, onset);
jeremy@24 334 offsets.add(index, offset);
jeremy@24 335 pitches.add(index, pitch);
jeremy@24 336 velocities.add(index, pitch);
jeremy@24 337 }
jeremy@24 338
jeremy@24 339
jeremy@24 340
jeremy@24 341 /*
jeremy@24 342 * Find the matching NOTE_OFF event *after* the given NOTE_ON event
jeremy@24 343 * (the <onsetIndex>th event in track <trackNum>)
jeremy@24 344 */
jeremy@24 345 private long getOffset(int trackNum, int onsetIndex, long onsetTime, int pitch) {
jeremy@24 346
jeremy@24 347 long offset = -1;
jeremy@24 348 Track track = sequence.getTracks()[trackNum];
jeremy@24 349
jeremy@24 350 // Iterate through remaining events looking for NOTE_OFF
jeremy@24 351 for (int k = onsetIndex + 1; k < track.size(); k++) {
jeremy@24 352 MidiEvent event = track.get(k);
jeremy@24 353 MidiMessage message = event.getMessage();
jeremy@24 354
jeremy@24 355 if (message instanceof ShortMessage) {
jeremy@24 356 ShortMessage shortMsg = (ShortMessage) message;
jeremy@24 357 int command = shortMsg.getCommand();
jeremy@24 358
jeremy@24 359 // Check NOTE_OFF message
jeremy@24 360 if (command == ShortMessage.NOTE_OFF
jeremy@24 361 || (command == ShortMessage.NOTE_ON && shortMsg.getData2() == 0)) {
jeremy@24 362 int pitch2 = shortMsg.getData1();
jeremy@24 363 // If pitches are identical then offset found
jeremy@24 364 if (pitch == pitch2) {
jeremy@24 365 offset = ticksToMicroseconds(event.getTick());
jeremy@24 366 break;
jeremy@24 367 }
jeremy@24 368 }
jeremy@24 369 }
jeremy@24 370 }
jeremy@24 371
jeremy@24 372 if (offset < 0) {
jeremy@24 373 System.out.println("No NOTE_OFF found for track " + trackNum
jeremy@24 374 + ", onset " + onsetTime + ", pitch " + pitch);
jeremy@24 375 }
jeremy@24 376
jeremy@24 377 return offset;
jeremy@24 378 }
jeremy@24 379
jeremy@24 380
jeremy@24 381
m@0 382 /* return a list of note onset times (milliseconds) */
m@0 383 public ArrayList getOnsets() { return onsets; }
m@0 384
m@0 385 /* return a list of note offset times (milliseconds) */
m@0 386 public ArrayList getOffsets() { return offsets; }
m@0 387
m@0 388 /* return a list of note pitches */
m@0 389 public ArrayList getPitches() { return pitches; }
m@0 390
m@0 391 /* return a list of note durations (microseconds) */
m@0 392 public ArrayList getDurations() {
m@0 393 Object[] ons = onsets.toArray();
m@0 394 Object[] offs = offsets.toArray();
m@0 395
m@0 396 ArrayList durations = new ArrayList();
m@0 397
m@0 398 for(int i = 0; i < ons.length; i++) {
m@0 399 durations.add(((Long)offs[i]).longValue() -
m@0 400 ((Long)ons[i]).longValue());
m@0 401 }
m@0 402 return durations;
m@0 403 }
m@0 404
m@0 405 /* return a list of inter-onset intervals (microseconds) */
m@0 406 public ArrayList getInterOnsetIntervals() {
m@0 407 Object[] ons = onsets.toArray();
m@0 408
m@0 409 ArrayList iois = new ArrayList();
m@0 410 long firstIOI = 0;
m@0 411 iois.add(firstIOI); // IOI of first note is zero
m@0 412
m@0 413 for(int i = 1; i < ons.length; i++) {
m@0 414 iois.add(((Long)ons[i]).longValue() -
m@0 415 ((Long)ons[i-1]).longValue());
m@0 416 }
m@0 417 return iois;
m@0 418 }
m@0 419
m@0 420 /* return the length of a rest preceding a note */
m@0 421 public ArrayList getDeltaST() {
m@0 422 Object[] durs = getDurations().toArray();
m@0 423 Object[] iois = getInterOnsetIntervals().toArray();
m@0 424
m@0 425 ArrayList deltaST = new ArrayList();
m@0 426 long firstDeltaST = 0;
m@0 427 deltaST.add(firstDeltaST); // deltaST of first note is zero
m@0 428
m@0 429 for(int i = 1; i < durs.length; i++) {
m@0 430 deltaST.add(((Long)durs[i-1]).longValue() -
m@0 431 ((Long)iois[i]).longValue());
m@0 432 }
m@0 433 return deltaST;
m@0 434 }
m@0 435
jeremy@24 436 /*
jeremy@24 437 * Return the tatum of the midi file, i.e. the shortest
jeremy@24 438 * note duration or positive rest.
jeremy@24 439 */
JShulver@23 440 public long getTatum() {
JShulver@23 441
JShulver@23 442
m@0 443 Object[] durs = getDurations().toArray();
m@0 444 Object[] rests = getDeltaST().toArray();
m@0 445 long tatum = -1;
m@0 446
jeremy@24 447
m@0 448 for(int i = 0; i < durs.length; i++) {
m@0 449 long dur = ((Long)durs[i]).longValue();
m@0 450 long rest = ((Long)rests[i]).longValue();
m@0 451 long min = dur;
jeremy@24 452 if (rest > 0)
m@0 453 min = Math.min(dur, rest);
jeremy@24 454 if (debug)
jeremy@24 455 System.out.println("Dur: " + dur + " Rest: " + rest);
m@0 456 if (tatum < 0)
m@0 457 tatum = dur;
m@0 458 else if (min < tatum)
m@0 459 tatum = min;
m@0 460 }
JShulver@23 461 //tatum = sequence.getResolution()*1000;
m@0 462 return tatum;
m@0 463 }
m@0 464
m@0 465 /* return length of sequence in microseconds */
m@0 466 public long getLength() {
m@0 467 return sequence.getMicrosecondLength();
m@0 468 }
m@0 469
m@0 470 /* play the midi file */
m@0 471 public void play() {
marcus@16 472 //System.out.println("MidiPlayer.play: run sequencer.start().");
m@0 473 sequencer.start();
m@0 474 }
m@0 475
m@0 476 public void stop() {
m@4 477 if (synthesizer != null) {
m@4 478 synthesizer.close();
m@4 479 }
m@4 480 if (sequencer != null) {
m@4 481 sequencer.close();
m@4 482 }
m@4 483 sequencer = null;
m@4 484 synthesizer = null;
m@4 485 }
m@0 486 }