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}