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}