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.ArrayList;
018import java.util.Collections;
019import java.util.List;
020import java.util.Set;
021import java.util.concurrent.ConcurrentHashMap;
022import java.util.concurrent.atomic.AtomicBoolean;
023import java.util.concurrent.atomic.AtomicLong;
024
025import javax.sound.midi.MidiDevice;
026import javax.sound.midi.MidiUnavailableException;
027import javax.sound.midi.Receiver;
028import javax.sound.midi.Transmitter;
029
030/**
031 * CoreMidiDestination - implementation for MidiDevice
032 * that can have any number of receivers but no transmitters.
033 * 
034 *  @see CoreMidiSource
035 *
036 */
037
038public class CoreMidiDestination implements MidiDevice {
039
040  private CoreMidiDeviceInfo info;
041  private final AtomicBoolean isOpen;  // Tracks whether we are connected to CoreMIDI and can be used
042  private final AtomicLong startTime;  // The system time in microseconds when the port was opened
043  private final Set<CoreMidiReceiver> receivers;
044
045  /**
046   * Default constructor. 
047   * 
048   * @param info         CoreMidiDeviceInfo object providing details of the MIDI interface
049   * 
050   */
051
052  CoreMidiDestination(final CoreMidiDeviceInfo info) {
053
054    this.info = info;
055    isOpen = new AtomicBoolean(false);
056    startTime = new AtomicLong(0);
057    receivers = Collections.newSetFromMap(new ConcurrentHashMap<CoreMidiReceiver, Boolean>());
058
059  }
060
061  /** 
062   * Gets the MIDI Info object
063   * 
064   * @return the MIDI Info object, which provides details about the interface
065   * 
066   */
067
068  @Override
069  public Info getDeviceInfo() {
070
071    return info;
072
073  }
074
075  /**
076   * Changes the MIDI Info object; can only be done by this package as a result of a MIDI environment change event.
077   * @param info CoreMidiDeviceInfo object
078   */
079  void updateDeviceInfo(CoreMidiDeviceInfo info) {
080
081    this.info = info;
082
083  }
084
085  /**
086   * Opens the Core MIDI Device
087   * 
088   * @throws MidiUnavailableException if the MIDI system cannot be accessed
089   * 
090   */
091
092  @Override
093  public void open() throws MidiUnavailableException {
094
095    if ( isOpen.compareAndSet(false, true) ) {
096
097      // Track the system time in microseconds
098      startTime.set(getMicroSecondTime());
099
100    }
101
102  }
103
104  /**
105   * Closes the Core MIDI Device, which also closes all of its receivers
106   * 
107   */
108
109  @Override
110  public void close() {
111
112    if ( isOpen.compareAndSet(true, false) ) {
113
114      // Reset the context data
115      startTime.set(0);
116
117      // Close all our receivers, which will also clear the list.
118      // We iterate on a copy of the receiver list to avoid issues with concurrent modification.
119      for ( Receiver receiver : getReceivers() ) {
120
121        receiver.close();
122
123      }
124
125    }
126
127  }
128
129  /**
130   * Checks to see if the MIDI Device is open
131   * 
132   * @see javax.sound.midi.MidiDevice#isOpen()
133   * 
134   * @return true if the device is open, otherwise false;
135   *  
136   */
137  @Override
138  public boolean isOpen() {
139
140    return isOpen.get();
141
142  }
143
144  /**
145   * Obtains the time in microseconds that has elapsed since this MIDI Device was opened.
146   *
147   * @return the time in microseconds that has elapsed since this MIDI Device was opened.
148   * 
149   * @see javax.sound.midi.MidiDevice#getMicrosecondPosition()
150   * 
151   */
152
153  @Override
154  public long getMicrosecondPosition() {
155
156    // Return the elapsed time in Microseconds
157    return getMicroSecondTime() - startTime.get();
158
159  }
160
161  /**
162   * Obtains the time in microseconds at which this MIDI Device was opened.
163   *
164   * @return the time in microseconds that was recorded when this device was opened.
165   */
166
167  public long getStartTime() {
168
169    return startTime.get();
170
171  }
172
173  /**
174   * Gets the maximum number of receivers that can be attached to this device.
175   * 
176   * @see javax.sound.midi.MidiDevice#getMaxReceivers()
177   * 
178   * @return the maximum number of receivers that can be attached to this device. -1 is returned to indicate that the number is unlimited
179   */
180
181  @Override
182  public int getMaxReceivers() {
183
184    // A CoreMidiDestination can support any number of receivers
185    return -1;
186
187  }
188
189  /**
190   * Gets the maximum number of transmitters that can be attached to this device.
191   * 
192   * @see javax.sound.midi.MidiDevice#getMaxTransmitters()
193   * 
194   * @return the maximum number of transmitters that can be attached to this device. -1 is returned to indicate that the number is unlimited
195   * 
196   */
197
198  @Override
199  public int getMaxTransmitters() {
200
201    // A CoreMIDI Destination has no transmitters
202    return 0;
203
204  }
205
206  /**
207   * Creates and returns a MIDI Receiver for use with this Device
208   * 
209   * @see javax.sound.midi.MidiDevice#getReceiver()
210   * 
211   * @return the created receiver
212   * 
213   */
214
215  @Override
216  public Receiver getReceiver() throws MidiUnavailableException {
217
218    // Create a new receiver
219    CoreMidiReceiver receiver =  new CoreMidiReceiver(this);
220
221    // Add it to the set of open receivers
222    receivers.add(receiver);
223
224    // Finally return it
225    return receiver;
226
227  }
228
229  /**  
230   * Gets a list of receivers connected to the device
231   * 
232   * @see javax.sound.midi.MidiDevice#getReceivers()
233   * 
234   * @return the list of receivers that have been created from this device that are still open
235   * 
236   */
237
238  @Override
239  public List<Receiver> getReceivers() {
240
241    // Return an immutable copy of our current set of open receivers
242    return Collections.unmodifiableList(new ArrayList<Receiver>(receivers));
243    
244  }
245
246  /**
247   * Reacts to the closing of a receiver by removing it from the set of active receivers
248   *
249   * @param receiver the transmitter which is reporting itself as having closed
250   */
251
252  void receiverClosed(CoreMidiReceiver receiver) {
253
254    receivers.remove(receiver);
255
256  }
257
258  /**
259   * Gets a transmitter for this device (which is also added to the internal list
260   * 
261   * @see javax.sound.midi.MidiDevice#getTransmitter()
262   * 
263   * @return  a transmitter for this device
264   * 
265   */
266
267  @Override
268  public Transmitter getTransmitter() throws MidiUnavailableException {
269
270    throw new MidiUnavailableException("CoreMidiDestination has no sources (Transmitters)");
271
272  }
273
274  /**
275   * Gets the list of transmitters registered with this MIDI device
276   * 
277   * @see javax.sound.midi.MidiDevice#getTransmitters()
278   * 
279   * @return    The list of transmitters registered with this MIDI device
280   * 
281   */
282
283  @Override
284  public List<Transmitter> getTransmitters() {
285
286    // A CoreMIDI Destination has no transmitters
287    return Collections.emptyList();
288
289  }
290
291  //////////////////////////////
292  ///// JNI Interfaces
293  //////////////////////////////
294
295  /*
296   * Static initializer for loading the native library
297   *
298   */
299
300  static {
301
302    try {
303
304      Loader.load();
305
306    } catch (Throwable t) {
307
308      System.err.println("Unable to load native library, CoreMIDI4J will stay inactive: " + t);
309
310    }
311
312  }
313
314  /**
315   * Obtains the current system time in microseconds.
316   *
317   * @return The current system time in microseconds.
318   * 
319   */
320
321  private native long getMicroSecondTime();
322
323}