1 package jportmidi; 2 3 /* PortMidi is a general class intended for any Java program using 4 the PortMidi library. It encapsulates JPortMidiApi with a more 5 object-oriented interface. A single PortMidi object can manage 6 up to one input stream and one output stream. 7 8 This class is not safely callable from multiple threads. It 9 is the client's responsibility to periodically call the Poll 10 method which checks for midi input and handles it. 11 */ 12 13 import jportmidi.*; 14 import jportmidi.JPortMidiApi.*; 15 16 public class JPortMidi { 17 18 // timecode to send message immediately 19 public final int NOW = 0; 20 21 // midi codes 22 public final int MIDI_NOTE_OFF = 0x80; 23 public final int MIDI_NOTE_ON = 0x90; 24 public final int CTRL_ALL_OFF = 123; 25 public final int MIDI_PITCH_BEND = 0xE0; 26 public final int MIDI_CLOCK = 0xF8; 27 public final int MIDI_CONTROL = 0xB0; 28 public final int MIDI_PROGRAM = 0xC0; 29 public final int MIDI_START = 0xFA; 30 public final int MIDI_STOP = 0xFC; 31 public final int MIDI_POLY_TOUCH = 0xA0; 32 public final int MIDI_TOUCH = 0xD0; 33 34 // error code -- cannot refresh device list while stream is open: 35 public final int pmStreamOpen = -5000; 36 public final int pmOutputNotOpen = -4999; 37 38 // access to JPortMidiApi is through a single, global instance 39 private static JPortMidiApi pm; 40 // a reference count tracks how many objects have it open 41 private static int pmRefCount = 0; 42 private static int openCount = 0; 43 44 public int error; // user can check here for error codes 45 private PortMidiStream input; 46 private PortMidiStream output; 47 private PmEvent buffer; 48 protected int timestamp; // remember timestamp from incoming messages 49 protected boolean trace = false; // used to print midi msgs for debugging 50 51 JPortMidi()52 public JPortMidi() throws JPortMidiException { 53 if (pmRefCount == 0) { 54 pm = new JPortMidiApi(); 55 pmRefCount++; 56 System.out.println("Calling Pm_Initialize"); 57 checkError(pm.Pm_Initialize()); 58 System.out.println("Called Pm_Initialize"); 59 } 60 buffer = new PmEvent(); 61 } 62 getTrace()63 public boolean getTrace() { return trace; } 64 65 // set the trace flag and return previous value setTrace(boolean flag)66 public boolean setTrace(boolean flag) { 67 boolean previous = trace; 68 trace = flag; 69 return previous; 70 } 71 72 // WARNING: you must not call this if any devices are open refreshDeviceLists()73 public void refreshDeviceLists() 74 throws JPortMidiException 75 { 76 if (openCount > 0) { 77 throw new JPortMidiException(pmStreamOpen, 78 "RefreshDeviceLists called while stream is open"); 79 } 80 checkError(pm.Pm_Terminate()); 81 checkError(pm.Pm_Initialize()); 82 } 83 84 // there is no control over when/whether this is called, but it seems 85 // to be a good idea to close things when this object is collected finalize()86 public void finalize() { 87 if (input != null) { 88 error = pm.Pm_Close(input); 89 } 90 if (input != null) { 91 int rslt = pm.Pm_Close(output); 92 // we may lose an error code from closing output, but don't 93 // lose any real error from closing input... 94 if (error == pm.pmNoError) error = rslt; 95 } 96 pmRefCount--; 97 if (pmRefCount == 0) { 98 error = pm.Pm_Terminate(); 99 } 100 } 101 checkError(int err)102 int checkError(int err) throws JPortMidiException 103 { 104 // note that Pm_Read and Pm_Write return positive result values 105 // which are not errors, so compare with >= 106 if (err >= pm.pmNoError) return err; 107 if (err == pm.pmHostError) { 108 throw new JPortMidiException(err, pm.Pm_GetHostErrorText()); 109 } else { 110 throw new JPortMidiException(err, pm.Pm_GetErrorText(err)); 111 } 112 } 113 114 // ******** ACCESS TO TIME *********** 115 timeStart(int resolution)116 public void timeStart(int resolution) throws JPortMidiException { 117 checkError(pm.Pt_TimeStart(resolution)); 118 } 119 timeStop()120 public void timeStop() throws JPortMidiException { 121 checkError(pm.Pt_TimeStop()); 122 } 123 timeGet()124 public int timeGet() { 125 return pm.Pt_Time(); 126 } 127 timeStarted()128 public boolean timeStarted() { 129 return pm.Pt_TimeStarted(); 130 } 131 132 // ******* QUERY DEVICE INFORMATION ********* 133 countDevices()134 public int countDevices() throws JPortMidiException { 135 return checkError(pm.Pm_CountDevices()); 136 } 137 getDefaultInputDeviceID()138 public int getDefaultInputDeviceID() throws JPortMidiException { 139 return checkError(pm.Pm_GetDefaultInputDeviceID()); 140 } 141 getDefaultOutputDeviceID()142 public int getDefaultOutputDeviceID() throws JPortMidiException { 143 return checkError(pm.Pm_GetDefaultOutputDeviceID()); 144 } 145 getDeviceInterf(int i)146 public String getDeviceInterf(int i) { 147 return pm.Pm_GetDeviceInterf(i); 148 } 149 getDeviceName(int i)150 public String getDeviceName(int i) { 151 return pm.Pm_GetDeviceName(i); 152 } 153 getDeviceInput(int i)154 public boolean getDeviceInput(int i) { 155 return pm.Pm_GetDeviceInput(i); 156 } 157 getDeviceOutput(int i)158 public boolean getDeviceOutput(int i) { 159 return pm.Pm_GetDeviceOutput(i); 160 } 161 162 // ********** MIDI INTERFACE ************ 163 isOpenInput()164 public boolean isOpenInput() { 165 return input != null; 166 } 167 openInput(int inputDevice, int bufferSize)168 public void openInput(int inputDevice, int bufferSize) 169 throws JPortMidiException 170 { 171 openInput(inputDevice, "", bufferSize); 172 } 173 openInput(int inputDevice, String inputDriverInfo, int bufferSize)174 public void openInput(int inputDevice, String inputDriverInfo, int bufferSize) 175 throws JPortMidiException 176 { 177 if (isOpenInput()) pm.Pm_Close(input); 178 else input = new PortMidiStream(); 179 if (trace) { 180 System.out.println("openInput " + getDeviceName(inputDevice)); 181 } 182 checkError(pm.Pm_OpenInput(input, inputDevice, 183 inputDriverInfo, bufferSize)); 184 // if no exception, then increase count of open streams 185 openCount++; 186 } 187 isOpenOutput()188 public boolean isOpenOutput() { 189 return output != null; 190 } 191 openOutput(int outputDevice, int bufferSize, int latency)192 public void openOutput(int outputDevice, int bufferSize, int latency) 193 throws JPortMidiException 194 { 195 openOutput(outputDevice, "", bufferSize, latency); 196 } 197 openOutput(int outputDevice, String outputDriverInfo, int bufferSize, int latency)198 public void openOutput(int outputDevice, String outputDriverInfo, 199 int bufferSize, int latency) throws JPortMidiException { 200 if (isOpenOutput()) pm.Pm_Close(output); 201 else output = new PortMidiStream(); 202 if (trace) { 203 System.out.println("openOutput " + getDeviceName(outputDevice)); 204 } 205 checkError(pm.Pm_OpenOutput(output, outputDevice, outputDriverInfo, 206 bufferSize, latency)); 207 // if no exception, then increase count of open streams 208 openCount++; 209 } 210 setFilter(int filters)211 public void setFilter(int filters) throws JPortMidiException { 212 if (input == null) return; // no effect if input not open 213 checkError(pm.Pm_SetFilter(input, filters)); 214 } 215 setChannelMask(int mask)216 public void setChannelMask(int mask) throws JPortMidiException { 217 if (input == null) return; // no effect if input not open 218 checkError(pm.Pm_SetChannelMask(input, mask)); 219 } 220 abort()221 public void abort() throws JPortMidiException { 222 if (output == null) return; // no effect if output not open 223 checkError(pm.Pm_Abort(output)); 224 } 225 226 // In keeping with the idea that this class represents an input and output, 227 // there are separate Close methods for input and output streams, avoiding 228 // the need for clients to ever deal directly with a stream object closeInput()229 public void closeInput() throws JPortMidiException { 230 if (input == null) return; // no effect if input not open 231 checkError(pm.Pm_Close(input)); 232 input = null; 233 openCount--; 234 } 235 closeOutput()236 public void closeOutput() throws JPortMidiException { 237 if (output == null) return; // no effect if output not open 238 checkError(pm.Pm_Close(output)); 239 output = null; 240 openCount--; 241 } 242 243 // Poll should be called by client to process input messages (if any) poll()244 public void poll() throws JPortMidiException { 245 if (input == null) return; // does nothing until input is opened 246 while (true) { 247 int rslt = pm.Pm_Read(input, buffer); 248 checkError(rslt); 249 if (rslt == 0) return; // no more messages 250 handleMidiIn(buffer); 251 } 252 } 253 writeShort(int when, int msg)254 public void writeShort(int when, int msg) throws JPortMidiException { 255 if (output == null) 256 throw new JPortMidiException(pmOutputNotOpen, 257 "Output stream not open"); 258 if (trace) { 259 System.out.println("writeShort: " + Integer.toHexString(msg)); 260 } 261 checkError(pm.Pm_WriteShort(output, when, msg)); 262 } 263 writeSysEx(int when, byte msg[])264 public void writeSysEx(int when, byte msg[]) throws JPortMidiException { 265 if (output == null) 266 throw new JPortMidiException(pmOutputNotOpen, 267 "Output stream not open"); 268 if (trace) { 269 System.out.print("writeSysEx: "); 270 for (int i = 0; i < msg.length; i++) { 271 System.out.print(Integer.toHexString(msg[i])); 272 } 273 System.out.print("\n"); 274 } 275 checkError(pm.Pm_WriteSysEx(output, when, msg)); 276 } 277 midiChanMessage(int chan, int status, int data1, int data2)278 public int midiChanMessage(int chan, int status, int data1, int data2) { 279 return (((data2 << 16) & 0xFF0000) | 280 ((data1 << 8) & 0xFF00) | 281 (status & 0xF0) | 282 (chan & 0xF)); 283 } 284 midiMessage(int status, int data1, int data2)285 public int midiMessage(int status, int data1, int data2) { 286 return ((((data2) << 16) & 0xFF0000) | 287 (((data1) << 8) & 0xFF00) | 288 ((status) & 0xFF)); 289 } 290 midiAllOff(int channel)291 public void midiAllOff(int channel) throws JPortMidiException { 292 midiAllOff(channel, NOW); 293 } 294 midiAllOff(int chan, int when)295 public void midiAllOff(int chan, int when) throws JPortMidiException { 296 writeShort(when, midiChanMessage(chan, MIDI_CONTROL, CTRL_ALL_OFF, 0)); 297 } 298 midiPitchBend(int chan, int value)299 public void midiPitchBend(int chan, int value) throws JPortMidiException { 300 midiPitchBend(chan, value, NOW); 301 } 302 midiPitchBend(int chan, int value, int when)303 public void midiPitchBend(int chan, int value, int when) 304 throws JPortMidiException { 305 writeShort(when, 306 midiChanMessage(chan, MIDI_PITCH_BEND, value, value >> 7)); 307 } 308 midiClock()309 public void midiClock() throws JPortMidiException { 310 midiClock(NOW); 311 } 312 midiClock(int when)313 public void midiClock(int when) throws JPortMidiException { 314 writeShort(when, midiMessage(MIDI_CLOCK, 0, 0)); 315 } 316 midiControl(int chan, int control, int value)317 public void midiControl(int chan, int control, int value) 318 throws JPortMidiException { 319 midiControl(chan, control, value, NOW); 320 } 321 midiControl(int chan, int control, int value, int when)322 public void midiControl(int chan, int control, int value, int when) 323 throws JPortMidiException { 324 writeShort(when, midiChanMessage(chan, MIDI_CONTROL, control, value)); 325 } 326 midiNote(int chan, int pitch, int vel)327 public void midiNote(int chan, int pitch, int vel) 328 throws JPortMidiException { 329 midiNote(chan, pitch, vel, NOW); 330 } 331 midiNote(int chan, int pitch, int vel, int when)332 public void midiNote(int chan, int pitch, int vel, int when) 333 throws JPortMidiException { 334 writeShort(when, midiChanMessage(chan, MIDI_NOTE_ON, pitch, vel)); 335 } 336 midiProgram(int chan, int program)337 public void midiProgram(int chan, int program) 338 throws JPortMidiException { 339 midiProgram(chan, program, NOW); 340 } 341 midiProgram(int chan, int program, int when)342 public void midiProgram(int chan, int program, int when) 343 throws JPortMidiException { 344 writeShort(when, midiChanMessage(chan, MIDI_PROGRAM, program, 0)); 345 } 346 midiStart()347 public void midiStart() 348 throws JPortMidiException { 349 midiStart(NOW); 350 } 351 midiStart(int when)352 public void midiStart(int when) 353 throws JPortMidiException { 354 writeShort(when, midiMessage(MIDI_START, 0, 0)); 355 } 356 midiStop()357 public void midiStop() 358 throws JPortMidiException { 359 midiStop(NOW); 360 } 361 midiStop(int when)362 public void midiStop(int when) 363 throws JPortMidiException { 364 writeShort(when, midiMessage(MIDI_STOP, 0, 0)); 365 } 366 midiPolyTouch(int chan, int key, int value)367 public void midiPolyTouch(int chan, int key, int value) 368 throws JPortMidiException { 369 midiPolyTouch(chan, key, value, NOW); 370 } 371 midiPolyTouch(int chan, int key, int value, int when)372 public void midiPolyTouch(int chan, int key, int value, int when) 373 throws JPortMidiException { 374 writeShort(when, midiChanMessage(chan, MIDI_POLY_TOUCH, key, value)); 375 } 376 midiTouch(int chan, int value)377 public void midiTouch(int chan, int value) 378 throws JPortMidiException { 379 midiTouch(chan, value, NOW); 380 } 381 midiTouch(int chan, int value, int when)382 public void midiTouch(int chan, int value, int when) 383 throws JPortMidiException { 384 writeShort(when, midiChanMessage(chan, MIDI_TOUCH, value, 0)); 385 } 386 387 // ****** now we implement the message handlers ****** 388 389 // an array for incoming sysex messages that can grow. 390 // The downside is that after getting a message, we 391 392 private byte sysexBuffer[] = null; 393 private int sysexBufferIndex = 0; 394 sysexBufferReset()395 void sysexBufferReset() { 396 sysexBufferIndex = 0; 397 if (sysexBuffer == null) sysexBuffer = new byte[256]; 398 } 399 sysexBufferCheck()400 void sysexBufferCheck() { 401 if (sysexBuffer.length < sysexBufferIndex + 4) { 402 byte bigger[] = new byte[sysexBuffer.length * 2]; 403 for (int i = 0; i < sysexBufferIndex; i++) { 404 bigger[i] = sysexBuffer[i]; 405 } 406 sysexBuffer = bigger; 407 } 408 // now we have space to write some bytes 409 } 410 411 // call this to insert Sysex and EOX status bytes 412 // call sysexBufferAppendBytes to insert anything else sysexBufferAppendStatus(byte status)413 void sysexBufferAppendStatus(byte status) { 414 sysexBuffer[sysexBufferIndex++] = status; 415 } 416 sysexBufferAppendBytes(int msg, int len)417 void sysexBufferAppendBytes(int msg, int len) { 418 for (int i = 0; i < len; i++) { 419 byte b = (byte) msg; 420 if ((msg & 0x80) != 0) { 421 if (b == 0xF7) { // end of sysex 422 sysexBufferAppendStatus(b); 423 sysex(sysexBuffer, sysexBufferIndex); 424 return; 425 } 426 // recursively handle embedded real-time messages 427 PmEvent buffer = new PmEvent(); 428 buffer.timestamp = timestamp; 429 buffer.message = b; 430 handleMidiIn(buffer); 431 } else { 432 sysexBuffer[sysexBufferIndex++] = b; 433 } 434 msg = msg >> 8; 435 } 436 } 437 sysexBegin(int msg)438 void sysexBegin(int msg) { 439 sysexBufferReset(); // start from 0, we have at least 256 bytes now 440 sysexBufferAppendStatus((byte) (msg & 0xFF)); // first byte is special 441 sysexBufferAppendBytes(msg >> 8, 3); // process remaining bytes 442 } 443 handleMidiIn(PmEvent buffer)444 public void handleMidiIn(PmEvent buffer) 445 { 446 if (trace) { 447 System.out.println("handleMidiIn: " + 448 Integer.toHexString(buffer.message)); 449 } 450 // rather than pass timestamps to every handler, where typically 451 // timestamps are ignored, just save the timestamp as a member 452 // variable where methods can access it if they want it 453 timestamp = buffer.timestamp; 454 int status = buffer.message & 0xFF; 455 if (status < 0x80) { 456 sysexBufferCheck(); // make enough space 457 sysexBufferAppendBytes(buffer.message, 4); // process 4 bytes 458 return; 459 } 460 int command = status & 0xF0; 461 int channel = status & 0x0F; 462 int data1 = (buffer.message >> 8) & 0xFF; 463 int data2 = (buffer.message >> 16) & 0xFF; 464 switch (command) { 465 case MIDI_NOTE_OFF: 466 noteOff(channel, data1, data2); break; 467 case MIDI_NOTE_ON: 468 if (data2 > 0) { 469 noteOn(channel, data1, data2); break; 470 } else { 471 noteOff(channel, data1); 472 } 473 break; 474 case MIDI_CONTROL: 475 control(channel, data1, data2); break; 476 case MIDI_POLY_TOUCH: 477 polyTouch(channel, data1, data2); break; 478 case MIDI_TOUCH: 479 touch(channel, data1); break; 480 case MIDI_PITCH_BEND: 481 pitchBend(channel, (data1 + (data2 << 7)) - 8192); break; 482 case MIDI_PROGRAM: 483 program(channel, data1); break; 484 case 0xF0: 485 switch (channel) { 486 case 0: sysexBegin(buffer.message); break; 487 case 1: mtcQuarterFrame(data1); 488 case 2: songPosition(data1 + (data2 << 7)); break; 489 case 3: songSelect(data1); break; 490 case 4: /* unused */ break; 491 case 5: /* unused */ break; 492 case 6: tuneRequest(); break; 493 case 7: sysexBufferAppendBytes(buffer.message, buffer.message); break; 494 case 8: clock(); break; 495 case 9: tick(); break; 496 case 0xA: clockStart(); break; 497 case 0xB: clockContinue(); break; 498 case 0xC: clockStop(); break; 499 case 0xD: /* unused */ break; 500 case 0xE: activeSense(); break; 501 case 0xF: reset(); break; 502 } 503 } 504 } 505 506 // the value ranges from +8181 to -8192. The interpretation is 507 // synthesizer dependent. Often the range is +/- one whole step 508 // (two semitones), but the range is usually adjustable within 509 // the synthesizer. pitchBend(int channel, int value)510 void pitchBend(int channel, int value) { return; } control(int channel, int control, int value)511 void control(int channel, int control, int value) { return; } noteOn(int channel, int pitch, int velocity)512 void noteOn(int channel, int pitch, int velocity) { return; } 513 // you can handle velocity in note-off if you want, but the default 514 // is to drop the velocity and call the simpler NoteOff handler noteOff(int channel, int pitch, int velocity)515 void noteOff(int channel, int pitch, int velocity) { 516 noteOff(channel, pitch); 517 } 518 // if the subclass wants to implement NoteOff with velocity, it 519 // should override the following to make sure all NoteOffs are handled noteOff(int channel, int pitch)520 void noteOff(int channel, int pitch) { return; } program(int channel, int program)521 void program(int channel, int program) { return; } 522 // the byte array may be bigger than the message, length tells how 523 // many bytes in the array are part of the message sysex(byte[] msg, int length)524 void sysex(byte[] msg, int length) { return; } polyTouch(int channel, int key, int value)525 void polyTouch(int channel, int key, int value) { return; } touch(int channel, int value)526 void touch(int channel, int value) { return; } mtcQuarterFrame(int value)527 void mtcQuarterFrame(int value) { return; } 528 // the value is a 14-bit integer representing 16th notes songPosition(int value)529 void songPosition(int value) { return; } songSelect(int value)530 void songSelect(int value) { return; } tuneRequest()531 void tuneRequest() { return; } clock()532 void clock() { return; } // represents 1/24th of a quarter note tick()533 void tick() { return; } // represents 10ms clockStart()534 void clockStart() { return; } clockStop()535 void clockStop() { return; } clockContinue()536 void clockContinue() { return; } activeSense()537 void activeSense() { return; } reset()538 void reset() { return; } 539 } 540