annotate MidiPlayer.java @ 39:796b3e3e053f

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