001/* 002 * Title: CoreMIDI4J 003 * Description: Core MIDI Device Provider for Java on OS X 004 * Copyright: Copyright (c) 2015-2016 005 * Company: x.factory Librarians 006 * 007 * @author Derek Cook, James Elliott 008 * 009 * CoreMIDI4J is an open source Service Provider Interface for supporting external MIDI devices on MAC OS X 010 * 011 * CREDITS - This library uses principles established by OSXMIDI4J, but converted so it operates at the JNI level with no additional libraries required 012 * 013 */ 014 015package uk.co.xfactorylibrarians.coremidi4j; 016 017import java.util.*; 018import java.util.concurrent.ConcurrentHashMap; 019import java.util.concurrent.atomic.AtomicBoolean; 020import java.util.concurrent.atomic.AtomicReference; 021 022import javax.sound.midi.InvalidMidiDataException; 023import javax.sound.midi.MidiDevice; 024import javax.sound.midi.MidiMessage; 025import javax.sound.midi.MidiUnavailableException; 026import javax.sound.midi.Receiver; 027import javax.sound.midi.ShortMessage; 028import javax.sound.midi.SysexMessage; 029import javax.sound.midi.Transmitter; 030 031/** 032 * CoreMidiDevice - a MidiDevice implementation for Apple CoreMIDI 033 * 034 */ 035 036public class CoreMidiSource implements MidiDevice { 037 038 private CoreMidiDeviceInfo info; 039 private final AtomicBoolean isOpen; 040 private final AtomicReference<CoreMidiInputPort> input; 041 private final Set<CoreMidiTransmitter> transmitters; 042 043 private int currentMessage = 0; // Will contain the status byte (> 127) while gathering a multi-byte message. 044 private boolean currentDataIsSingleByte; // Is true if currentMessage only needs one byte of data. 045 private byte firstDataByte; // Will hold the first data byte received when gathering two-byte messages. 046 private boolean wasFirstByteReceived = false; // Gets set to true when we read first byte of two-byte message. 047 private Vector<byte[]> sysexMessageData; // Accumulates runs of SYSEX data values until we see the end of message. 048 private int sysexMessageLength = 0; // Tracks the total SYSEX data length accumulated. 049 private long startTime; // The system time in microseconds when the port was opened 050 051 /** 052 * Default constructor. 053 * 054 * @param info a CoreMidiDeviceInfo object providing details of the MIDI interface 055 * 056 * 057 */ 058 059 CoreMidiSource(CoreMidiDeviceInfo info) { 060 061 this.info = info; 062 input = new AtomicReference<>(); 063 isOpen = new AtomicBoolean(false); 064 transmitters = Collections.newSetFromMap(new ConcurrentHashMap<CoreMidiTransmitter, Boolean>()); 065 066 } 067 068 /** 069 * Gets the MIDI Info object 070 * 071 * @return the MIDI Info object, which provides details about the interface 072 * 073 */ 074 075 @Override 076 public Info getDeviceInfo() { 077 078 return info; 079 080 } 081 082 /** 083 * Changes the MIDI Info object; can only be done by this package as a result of a MIDI environment change event. 084 * @param info CoreMidiDeviceInfo object 085 */ 086 void updateDeviceInfo(CoreMidiDeviceInfo info) { 087 088 this.info = info; 089 090 } 091 092 /** 093 * Opens the Core MIDI Device 094 * 095 * @throws MidiUnavailableException if the MIDI system cannot be used 096 * 097 */ 098 099 @Override 100 public void open() throws MidiUnavailableException { 101 102 if (isOpen.compareAndSet(false, true)) { 103 104 try { 105 106 // Create the input port if not already created 107 if (input.get() == null) { 108 109 input.set(CoreMidiDeviceProvider.getMIDIClient().inputPortCreate("Core Midi Provider Input")); 110 111 } 112 113 // And connect to it 114 input.get().connectSource(this); 115 116 // Get the system time in microseconds 117 startTime = this.getMicroSecondTime(); 118 119 } catch (CoreMidiException e) { 120 121 e.printStackTrace(); 122 throw new MidiUnavailableException(e.getMessage()); 123 124 } 125 126 } 127 128 } 129 130 /** 131 * Closes the Core MIDI Device, which also closes all its transmitters 132 * 133 */ 134 135 @Override 136 public void close() { 137 138 if (isOpen.compareAndSet(true, false)) { 139 140 try { 141 142 // If the port is created then disconnect from it 143 if (input.get() != null) { 144 145 try { 146 147 input.get().disconnectSource(this); 148 149 } finally { 150 151 input.set(null); 152 153 } 154 155 } 156 157 // Close all our transmitters, which will also clear the list. 158 // We iterate on a copy of the transmitter list to avoid issues with concurrent modification. 159 for (Transmitter transmitter : getTransmitters()) { 160 161 transmitter.close(); 162 163 } 164 165 } catch (CoreMidiException e) { 166 167 e.printStackTrace(); 168 169 } 170 171 } 172 173 } 174 175 /** 176 * Forcibly close because the underlying CoreMIDI device has disappeared. Behaves like {@link #close()} without 177 * attempting to detach from the now-nonexistent underlying device. 178 */ 179 180 void deviceDisappeared() { 181 182 input.set(null); 183 close(); 184 185 } 186 187 /** 188 * Checks to see if the MIDI Device is open 189 * 190 * @return true if the device is open, otherwise false; 191 * 192 * @see javax.sound.midi.MidiDevice#isOpen() 193 * 194 */ 195 196 @Override 197 public boolean isOpen() { 198 199 return isOpen.get(); 200 201 } 202 203 /** 204 * Obtains the time in microseconds that has elapsed since this MIDI Device was opened. 205 * 206 * @return the time in microseconds that has elapsed since this MIDI Device was opened. 207 * 208 * @see javax.sound.midi.MidiDevice#getMicrosecondPosition() 209 * 210 */ 211 212 @Override 213 public long getMicrosecondPosition() { 214 215 // Return the elapsed time in Microseconds 216 return this.getMicroSecondTime() - startTime; 217 218 } 219 220 /** 221 * Gets the maximum number of receivers that can be attached to this device. 222 * 223 * @return the maximum number of receivers that can be attached to this device. This is always 0 as a CoreMidiSource has no receivers 224 * 225 * @see javax.sound.midi.MidiDevice#getMaxReceivers() 226 * 227 */ 228 229 @Override 230 public int getMaxReceivers() { 231 232 // A CoreMidiSource has no receivers 233 return 0; 234 235 } 236 237 /** 238 * Gets the maximum number of transmitters that can be attached to this device. 239 * 240 * @return the maximum number of transmitters that can be attached to this device. -1 is returned to indicate that the number is unlimited 241 * 242 * @see javax.sound.midi.MidiDevice#getMaxTransmitters() 243 * 244 */ 245 246 @Override 247 public int getMaxTransmitters() { 248 249 // Any number of transmitters is supported 250 return -1; 251 252 } 253 254 /** 255 * Creates and returns a MIDI Receiver for use with this Device 256 * 257 * @return the created receiver 258 * 259 * @see javax.sound.midi.MidiDevice#getReceiver() 260 * 261 */ 262 263 @Override 264 public Receiver getReceiver() throws MidiUnavailableException { 265 266 throw new MidiUnavailableException("CoreMidiSource has no receivers"); 267 268 } 269 270 /** 271 * Gets a list of receivers connected to the device 272 * 273 * @return an empty list - we do not maintain a list of receivers 274 * 275 * @see javax.sound.midi.MidiDevice#getReceivers() 276 * 277 */ 278 279 @Override 280 public List<Receiver> getReceivers() { 281 282 // A CoreMidiSource has no receivers 283 return Collections.emptyList(); 284 285 } 286 287 /** 288 * Gets a transmitter for this device (which is also added to the internal list) 289 * 290 * @return a transmitter for this device 291 * 292 * @throws MidiUnavailableException Thrown if MIDI services unavailable. 293 * 294 * 295 * @see javax.sound.midi.MidiDevice#getTransmitter() 296 * 297 */ 298 299 @Override 300 public Transmitter getTransmitter() throws MidiUnavailableException { 301 302 // Create the transmitter 303 CoreMidiTransmitter transmitter = new CoreMidiTransmitter(this); 304 305 // Add it to the set of open transmitters 306 transmitters.add(transmitter); 307 308 // Finally return it 309 return transmitter; 310 311 } 312 313 /** 314 * Reacts to the closing of a transmitter by removing it from the set of active transmitters 315 * 316 * @param transmitter the transmitter which is reporting itself as having closed 317 */ 318 319 void transmitterClosed(CoreMidiTransmitter transmitter) { 320 321 transmitters.remove(transmitter); 322 323 } 324 325 /** 326 * Gets the list of transmitters registered with this MIDI device 327 * 328 * @return The list of transmitters created from this MIDI device that are still open 329 * 330 * @see javax.sound.midi.MidiDevice#getTransmitters() 331 * 332 */ 333 334 @Override 335 public List<Transmitter> getTransmitters() { 336 337 // Return an immutable copy of our current set of open transmitters 338 return Collections.unmodifiableList(new ArrayList<Transmitter>(transmitters)); 339 340 } 341 342 /** 343 * Checks whether a status byte represents a real-time message, which can occur even in the middle of another 344 * multi-byte message. 345 * 346 * @param status the status byte which has just been received. 347 * 348 * @return true if the status byte represents a standalone real-time message which should be passed on without 349 * interrupting any other message being gathered. 350 */ 351 352 private boolean isRealTimeMessage(byte status) { 353 354 switch ( status ) { 355 356 case (byte) ShortMessage.TIMING_CLOCK: 357 case (byte) ShortMessage.START: 358 case (byte) ShortMessage.CONTINUE: 359 case (byte) ShortMessage.STOP: 360 case (byte) ShortMessage.ACTIVE_SENSING: 361 case (byte) ShortMessage.SYSTEM_RESET: 362 return true; 363 364 default: 365 return false; 366 367 } 368 369 } 370 371 /** 372 * Checks whether a status byte represents a running-status message, which means that multiple messages can be 373 * sent without re-sending the status byte, for example to support multiple note-on messages in a row by simply 374 * sending a stream of data byte pairs after the note-on status byte. 375 * 376 * @param status the status byte which is being processed. 377 * 378 * @return true if we should stay in this status after receiving our full complement of data bytes. 379 */ 380 private boolean isRunningStatusMessage (int status) { 381 382 switch( status & 0xF0 ) { 383 384 case ShortMessage.NOTE_OFF: 385 case ShortMessage.NOTE_ON: 386 case ShortMessage.POLY_PRESSURE: 387 case ShortMessage.CONTROL_CHANGE: 388 case ShortMessage.PROGRAM_CHANGE: 389 case ShortMessage.CHANNEL_PRESSURE: 390 case ShortMessage.PITCH_BEND: 391 return true; 392 393 default: 394 return false; 395 396 } 397 398 } 399 400 /** 401 * Determine how many data bytes are expected for a given MIDI message other than a SYSEX message, which varies. 402 * 403 * @param status the status byte introducing the MIDI message. 404 * 405 * @return the number of data bytes which must be received for the message to be complete. 406 * 407 * @throws InvalidMidiDataException if the status byte is not valid. 408 */ 409 private int expectedDataLength (byte status) throws InvalidMidiDataException { 410 411 // system common and system real-time messages 412 413 switch( status &0xFF ) { 414 415 case ShortMessage.TUNE_REQUEST: 416 case ShortMessage.END_OF_EXCLUSIVE: 417 418 // System real-time messages 419 case ShortMessage.TIMING_CLOCK: 420 case 0xF9: // Undefined 421 case ShortMessage.START: 422 case ShortMessage.CONTINUE: 423 case ShortMessage.STOP: 424 case 0xFD: // Undefined 425 case ShortMessage.ACTIVE_SENSING: 426 case ShortMessage.SYSTEM_RESET: 427 return 0; 428 429 case ShortMessage.MIDI_TIME_CODE: 430 case ShortMessage.SONG_SELECT: 431 return 1; 432 433 case ShortMessage.SONG_POSITION_POINTER: 434 return 2; 435 436 default: // Fall through to next switch 437 438 } 439 440 // channel voice and mode messages 441 switch( status & 0xF0 ) { 442 443 case ShortMessage.NOTE_OFF: 444 case ShortMessage.NOTE_ON: 445 case ShortMessage.POLY_PRESSURE: 446 case ShortMessage.CONTROL_CHANGE: 447 case ShortMessage.PITCH_BEND: 448 return 2; 449 450 case ShortMessage.PROGRAM_CHANGE: 451 case ShortMessage.CHANNEL_PRESSURE: 452 return 1; 453 454 default: 455 throw new InvalidMidiDataException("Invalid status byte: " + status); 456 457 } 458 459 } 460 461 /** 462 * The message callback for receiving midi data from the JNI code 463 * 464 * @param coreTimestamp The time in microseconds since boot at which the messages should take effect 465 * @param packetlength The length of the packet of messages 466 * @param data The data array that holds the messages 467 * 468 * @throws InvalidMidiDataException if the message contained values that could not be interpreted as valid MIDI 469 * 470 */ 471 472 public void messageCallback(long coreTimestamp, int packetlength, byte data[]) throws InvalidMidiDataException { 473 474 int offset = 0; 475 476 // Convert from CoreMIDI-oriented boot-relative microseconds to Java-oriented port-relative microsecends, 477 // and from unsigned CoreMIDI semantics of 0 meaning now to signed Java semantics of -1 meaning now. 478 final long timestamp = (coreTimestamp == 0) ? -1 : coreTimestamp - startTime; 479 480 // An OSX MIDI packet may contain multiple messages 481 while (offset < packetlength) { 482 483 if (data[offset] >= 0) { 484 485 // This is a regular data byte. Process it appropriately for our current message type. 486 if (currentMessage == 0) { 487 488 throw new InvalidMidiDataException("Data received outside of a message."); 489 490 } else if (currentMessage == SysexMessage.SYSTEM_EXCLUSIVE) { 491 492 // We are in the middle of gathering system exclusive data; continue. 493 offset += processSysexData(packetlength, data, offset, timestamp); 494 495 } else if (currentDataIsSingleByte) { 496 497 // We are processing a message which only needs one data byte, this completes it 498 transmitMessage(new ShortMessage(currentMessage, data[offset++], 0), timestamp); 499 if (!isRunningStatusMessage(currentMessage)) currentMessage = 0; 500 501 } else { 502 503 // We are processing a message which needs two data bytes 504 if (wasFirstByteReceived) { 505 506 // We have the second data byte, the message is now complete 507 transmitMessage(new ShortMessage(currentMessage, firstDataByte, data[offset++]), timestamp); 508 wasFirstByteReceived = false; 509 if (!isRunningStatusMessage(currentMessage)) currentMessage = 0; 510 511 } else { 512 513 // We have just received the first data byte of a message which needs two. 514 firstDataByte = data[offset++]; 515 wasFirstByteReceived = true; 516 517 } 518 519 } 520 521 } else { 522 523 // This is a status byte, handle appropriately 524 if (isRealTimeMessage(data[offset])) { 525 526 // Real-time messages can come anywhere, including in between data bytes of other messages. 527 // Simply transmit it and move on. 528 transmitMessage(new ShortMessage(data[offset++] & 0xff), timestamp); 529 530 } else if (data[offset] == (byte) ShortMessage.END_OF_EXCLUSIVE) { 531 532 // This is the marker for the end of a system exclusive message. If we were gathering one, 533 // process (finish) it. 534 if (currentMessage == SysexMessage.SYSTEM_EXCLUSIVE) { 535 536 offset += processSysexData(packetlength, data, offset, timestamp); 537 } else { 538 539 throw new InvalidMidiDataException("Received End of Exclusive marker outside SYSEX message"); 540 541 } 542 543 } else if (data[offset] == (byte) SysexMessage.SYSTEM_EXCLUSIVE) { 544 545 // We are starting to gather a SYSEX message. 546 currentMessage = SysexMessage.SYSTEM_EXCLUSIVE; 547 sysexMessageLength = 0; 548 sysexMessageData = new Vector<>(); 549 offset += processSysexData(packetlength, data, offset, timestamp); 550 551 } else { 552 553 // Some ordinary MIDI message. 554 switch (expectedDataLength(data[offset])) { 555 556 case 0: // No data bytes, this is a standalone message, so we can send it right away. 557 transmitMessage(new ShortMessage(data[offset++] & 0xff), timestamp); 558 currentMessage = 0; // If we were in a running status, it's over now 559 break; 560 561 case 1: // We are expecting data before we can send this message. 562 currentMessage = data[offset++] & 0xff; 563 currentDataIsSingleByte = true; 564 break; 565 566 case 2: // We are expecting two bytes of data before we can send this message. 567 currentMessage = data[offset++] & 0xff; 568 currentDataIsSingleByte = false; 569 wasFirstByteReceived = false; 570 break; 571 572 default: 573 throw new InvalidMidiDataException("Unexpected data length: " + 574 expectedDataLength(data[offset])); 575 576 } 577 578 } 579 580 } 581 582 } 583 584 } 585 586 /** 587 * Creates a SYSEX message from the received (potentially partial) messages. This function is called when F7 was 588 * detected in the most recent message gathered, indicating the end of the SYSEX. 589 * 590 * @return The constructed SYSEX message 591 * 592 * @throws InvalidMidiDataException if the data is not properly formed 593 * 594 */ 595 596 private SysexMessage constructSysexMessage() throws InvalidMidiDataException { 597 598 // Create the array to hold the constructed message and reset the index (where the data will be copied) 599 byte data[] = new byte[sysexMessageLength]; 600 int index = 0; 601 602 // Iterate through the partial messages 603 for (byte[] sourceData : sysexMessageData) { 604 605 // Get the partial message 606 // Copy the partial message into the array 607 System.arraycopy(sourceData, 0, data, index, sourceData.length); 608 609 // Point the index to where the next partial message needs to be copied. 610 index += sourceData.length; 611 612 } 613 614 // We are done with the message fragments, so allow them to be garbage collected 615 sysexMessageData = null; 616 617 // Create and return the new SYSYEX Message 618 return new SysexMessage(data, sysexMessageLength); 619 620 } 621 622 /** 623 * Called when a SYSEX message is being received, either because an F0 byte has been seen at the start of a 624 * message, which starts the process of gathering a SYSEX potentially across multiple packets, or because a 625 * new packet has been received while we are still in the process of gathering bytes of a SYSEX which was 626 * started in a previous message. The partial data is added to the sysexMessageData Vector. If we see another 627 * status byte, except for one which represents a real-time message, we know the SYSEX is finished, and so we 628 * can assemble and transmit it from any fragments which have been gathered. 629 * 630 * If we see a real-time message or the end of the packet, we return the data we have gathered, 631 * and let the main loop decide what to do next. 632 * 633 * @param packetLength The length of the data packet 634 * @param sourceData The source data received from Core MIDI 635 * @param startOffset The position within the packet where the current message began 636 * @param timestamp The message timestamp 637 * 638 * @return The number of bytes consumed from the packet by the SYSEX message 639 * 640 * @throws InvalidMidiDataException if the data is not properly formed 641 * 642 */ 643 644 private int processSysexData(int packetLength, byte sourceData[], int startOffset, long timestamp) 645 throws InvalidMidiDataException { 646 647 // Look for the end of the SYSEX or packet 648 int messageLength = 0; 649 boolean foundEnd = false; 650 651 // Check to see if this packet contains the end of the message 652 while ( ( startOffset + messageLength ) < packetLength) { 653 654 byte latest = sourceData[startOffset + messageLength++]; 655 656 if (latest < 0 && messageLength + sysexMessageLength > 1) { 657 658 // We have encountered another status byte (after the F0 which starts the message). 659 // Is it the marker of the end of the SYSEX message? 660 if (latest == (byte) ShortMessage.END_OF_EXCLUSIVE) { 661 662 currentMessage = 0; // Found end marker, ready to send this SYSEX and look for next message. 663 foundEnd = true; 664 665 } else if (isRealTimeMessage(latest)) { 666 667 // Back up so the main loop can send this real-time message embedded in our data, 668 // then call us again to keep gathering SYSEX data. 669 --messageLength; 670 671 } else { 672 673 // Found the start of another message. Back up so it gets processed, and note that we have found 674 // the end of our SYSEX data. If we decide we should not pass on incomplete SYSEX messages, the 675 // code below the loop can use the fact that currentMessage is not 0 to do so. 676 --messageLength; 677 foundEnd = true; 678 679 } 680 681 break; // One way or another, we are done gathering data bytes for now. 682 683 } 684 685 } 686 687 // Create an array to hold this part of the message, if we received any actual data. 688 // (Note the source array will be released by the native function.) 689 if (messageLength > 0) { 690 691 byte data[] = new byte[messageLength]; 692 693 //Copy the data to the array 694 try { 695 696 System.arraycopy(sourceData, startOffset, data, 0, messageLength); 697 698 } catch (ArrayIndexOutOfBoundsException e) { 699 700 e.printStackTrace(); 701 702 throw e; 703 704 } 705 706 // Add the message to the vector 707 sysexMessageData.add(data); 708 709 } 710 711 // Update the length of the SYSEX message 712 sysexMessageLength += messageLength; 713 714 // If we found the end, send it now 715 if (foundEnd) { 716 717 // Again, here we could refrain from sending if currentMessage != 0, because that indicates we received 718 // a partial SYSEX message, i.e. the next message started before we received the End of Exclusive marker. 719 transmitMessage(constructSysexMessage(), timestamp); 720 721 } 722 723 return messageLength; 724 725 } 726 727 728 /** 729 * Sends a MIDI message to all of the registered transmitters 730 * 731 * @param message the message to send 732 * @param timestamp the time stamp 733 * 734 */ 735 736 private void transmitMessage(final MidiMessage message, long timestamp) { 737 738 // Uncomment the following to filter realtime messages during debugging 739 // if (isRealTimeMessage ((byte)message.getStatus())) { 740 // 741 // return; 742 // 743 // } 744 745 // Iterate over a snapshot of our transmitters to avoid issues with concurrent modification 746 for (Transmitter transmitter : getTransmitters()) { 747 748 final Receiver receiver = transmitter.getReceiver(); 749 750 // If the transmitter has a non-null receiver, then send the message 751 if (receiver != null) { 752 753 receiver.send(message, timestamp); 754 755 } 756 757 } 758 759 } 760 761 /** 762 * Formats the provided data into a HEX string, which is useful for debugging 763 * 764 * @param aByteArray The data to format 765 * 766 * @return The formatted HEX string 767 * 768 */ 769 770 private String getHexString(byte[] aByteArray) { 771 772 StringBuffer sbuf = new StringBuffer(aByteArray.length * 3 + 2); 773 774 for (byte aByte : aByteArray) { 775 776 sbuf.append(' '); 777 byte bhigh = (byte) ((aByte & 0xf0) >> 4); 778 sbuf.append((char) (bhigh > 9 ? bhigh + 'A' - 10 : bhigh + '0')); 779 byte blow = (byte) (aByte & 0x0f); 780 sbuf.append((char) (blow > 9 ? blow + 'A' - 10 : blow + '0')); 781 782 } 783 784 return new String(sbuf).trim(); 785 786 } 787 788 ////////////////////////////// 789 ///// JNI Interfaces 790 ////////////////////////////// 791 792 /* 793 * Static initializer for loading the native library 794 * 795 */ 796 797 static { 798 799 try { 800 801 Loader.load(); 802 803 } catch (Throwable t) { 804 805 System.err.println("Unable to load native library, CoreMIDI4J will stay inactive: " + t); 806 807 } 808 809 } 810 811 /** 812 * Obtains the current system time in microseconds. 813 * 814 * @return The current system time in microseconds. 815 * 816 */ 817 818 private native long getMicroSecondTime(); 819 820}