aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_mac/pmmacosxcm.c
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_mac/pmmacosxcm.c')
-rwxr-xr-xportmidi/pm_mac/pmmacosxcm.c1179
1 files changed, 0 insertions, 1179 deletions
diff --git a/portmidi/pm_mac/pmmacosxcm.c b/portmidi/pm_mac/pmmacosxcm.c
deleted file mode 100755
index e8b196c..0000000
--- a/portmidi/pm_mac/pmmacosxcm.c
+++ /dev/null
@@ -1,1179 +0,0 @@
1/*
2 * Platform interface to the MacOS X CoreMIDI framework
3 *
4 * Jon Parise <jparise at cmu.edu>
5 * and subsequent work by Andrew Zeldis and Zico Kolter
6 * and Roger B. Dannenberg
7 *
8 * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $
9 */
10
11/* Notes:
12
13 Since the input and output streams are represented by
14 MIDIEndpointRef values and almost no other state, we store the
15 MIDIEndpointRef on pm_descriptors[midi->device_id].descriptor.
16
17 OS X does not seem to have an error-code-to-text function, so we
18 will just use text messages instead of error codes.
19
20 Virtual device input synchronization: Once we create a virtual
21 device, it is always "on" and receiving messages, but it must drop
22 messages unless the device has been opened with Pm_OpenInput. To
23 open, the main thread should create all the data structures, then
24 call OSMemoryBarrier so that writes are observed, then set
25 is_opened = TRUE. To close without locks, we need to get the
26 callback to set is_opened to FALSE before we free data structures;
27 otherwise, there's a race condition where closing could delete
28 structures in use by the virtual_read_callback function. We send
29 8 MIDI resets (FF) in a single packet to our own port to signal
30 the virtual_read_callback to close it. Then, we wait for the
31 callback to recognize the "close" packet and reset is_opened.
32
33 Device scanning is done when you first open an application.
34 PortMIDI does not actively update the devices. Instead, you must
35 Pm_Terminate() and Pm_Initialize(), basically starting over. But
36 CoreMIDI does not have a way to shut down(!), and even
37 MIDIClientDispose() somehow retains state (and docs say do not
38 call it even if it worked). The solution, apparently, is to
39 call CFRunLoopRunInMode(), which somehow updates CoreMIDI
40 state.
41
42 But when do we call CFRunLoopRunInMode()? I tried calling it
43 in midi_in_poll() which is called when you call Pm_Read() since
44 that is called often. I observed that this caused the program
45 to block for as long as 50ms and fairly often for 2 or 3ms.
46 What was Apple thinking? Is it really OK to design systems that
47 can only function with a tricky multi-threaded, non-blocking
48 priority-based solution, and then not provide a proof of concept
49 or documentation? Or is Apple's design really flawed? If anyone
50 at Apple reads this, please let me know -- I'm curious.
51
52 But I digress... Here's the PortMidi approach: Since
53 CFRunLoopRunInMode() is potentially a non-realtime operation,
54 we only call it in Pm_Initialize(), where other calls to look
55 up devices and device names are quite slow to begin with. Again,
56 PortMidi does not actively scan for new or deleted devices, so
57 if devices change, you won't see it until the next Pm_Terminate
58 and Pm_Initialize.
59
60 Calling CFRunLoopRunInMode() once is probably not enough. There
61 might be better way, but it seems to work to just call it 100
62 times and insert 20 1ms delays (in case some inter-process
63 communication or synchronization is going on).
64 This adds 20ms to the wall time of Pm_Initialize(), but it
65 typically runs 30ms to much more (~4s), so this has little impact.
66 */
67
68#include <stdlib.h>
69
70/* turn on lots of debugging print statements */
71#define CM_DEBUG if (0)
72/* #define CM_DEBUG if (1) */
73
74#include "portmidi.h"
75#include "pmutil.h"
76#include "pminternal.h"
77#include "porttime.h"
78#include "pmmacosxcm.h"
79
80#include <stdio.h>
81#include <string.h>
82
83#include <CoreServices/CoreServices.h>
84#include <CoreMIDI/MIDIServices.h>
85#include <CoreAudio/HostTime.h>
86#include <unistd.h>
87#include <libkern/OSAtomic.h>
88
89#define PACKET_BUFFER_SIZE 1024
90/* maximum overall data rate (OS X limits MIDI rate in case there
91 * is a cycle among IAC ports.
92 */
93
94#define MAX_BYTES_PER_S 5400
95
96/* Apple reports that packets are dropped when the MIDI bytes/sec
97 exceeds 15000. This is computed by "tracking the number of MIDI
98 bytes scheduled into 1-second buckets over the last six seconds and
99 averaging these counts." This was confirmed in measurements
100 (2021) with pm_test/fast.c and pm_test/fastrcv.c Now, in 2022, with
101 macOS 12, pm_test/fast{rcv}.c show problems begin at 6000 bytes/sec.
102 Previously, we set MAX_BYTES_PER_S to 14000. This is reduced to
103 5400 based on testing (which shows 5700 is too high) to fix the
104 packet loss problem that showed up with macOS 12.
105
106 Experiments show this restriction applies to IAC bus MIDI, but not
107 to hardware interfaces. (I measured 0.5 Mbps each way over USB to a
108 Teensy 3.2 microcontroller implementing a USB MIDI loopback. Maybe
109 it would get 1 Mbps one-way, which would make the CoreMIDI
110 restriction 18x slower than USB. Maybe other USB MIDI
111 implementations are faster -- USB top speed for other protocols is
112 certainly higher than 1 Mbps!)
113
114 This is apparently based on timestamps, not on real time, so we
115 have to avoid constructing packets that schedule high speed output
116 regardless of when writes occur. The solution is to alter
117 timestamps to limit data rates. This adds a slight time
118 distortion, e.g. an 11 note chord with all notes on the same
119 timestamp will be altered so that the last message is delayed by
120 11 messages x 3 bytes/message / 5400 bytes/second = 6.1 ms.
121 Note that this is about 2x MIDI speed, but at least 18x slower
122 than USB MIDI.
123
124 Altering timestamps creates another problem, which is that a sender
125 that exceeds the maximum rate can queue up an unbounded number of
126 messages. With non-USB MIDI devices, you could be writing 5x faster
127 to CoreMIDI than the hardware interface can send, causing an
128 unbounded backlog, not to mention that the output stream will be a
129 steady byte stream (e.g., one 3-byte MIDI message every 0.55 ms),
130 losing any original timing or rhythm. PortMidi does not guarantee
131 delivery if, over the long run, you write faster than the hardware
132 can send.
133
134 The LIMIT_RATE symbol, if defined (which is the default), enables
135 code to modify timestamps for output to an IAC device as follows:
136
137 Before a packet is formed, the message timestamp is set to the
138 maximum of the PortMidi timestamp (converted to CoreMIDI time)
139 and min_next_time. After each send, min_next_time is updated to
140 the packet time + packet length * delay_per_byte, which limits
141 the scheduled bytes-per-second. Also, after each packet list
142 flush, min_next_time is updated to the maximum of min_next_time
143 and the real time, which prevents many bytes to be scheduled in
144 the past. (We could more directly just say packets are never
145 scheduled in the past, but we prefer to get the current time -- a
146 system call -- only when we perform the more expensive operation
147 of flushing packets, so that's when we update min_next_time to
148 the current real time. If we are sending a lot, we have to flush
149 a lot, so the time will be updated frequently when it matters.)
150
151 This possible adjustment to timestamps can distort accurate
152 timestamps by up to 0.556 us per 3-byte MIDI message.
153
154 Nothing blocks the sender from queueing up an arbitrary number of
155 messages. Timestamps should be used for accurate timing by sending
156 timestamped messages a little ahead of real time, not for
157 scheduling an entire MIDI sequence at once!
158 */
159#define LIMIT_RATE 1
160
161#define SYSEX_BUFFER_SIZE 128
162/* What is the maximum PortMidi device number for an IAC device? A
163 * cleaner design would be to not use the endpoint as our device
164 * representation. Instead, we could have a private extensible struct
165 * to keep all device information, including whether the device is
166 * implemented with the AppleMIDIIACDriver, which we need because we
167 * have to limit the data rate to this particular driver to avoid
168 * dropping messages. Rather than rewrite a lot of code, I am just
169 * allocating 64 bytes to flag which devices are IAC ones. If an IAC
170 * device number is greater than 63, PortMidi will fail to limit
171 * writes to it, but will not complain and will not access memory
172 * outside the 64-element array of char.
173 */
174#define MAX_IAC_NUM 63
175
176#define VERBOSE_ON 1
177#define VERBOSE if (VERBOSE_ON)
178
179#define MIDI_SYSEX 0xf0
180#define MIDI_EOX 0xf7
181#define MIDI_CLOCK 0xf8
182#define MIDI_STATUS_MASK 0x80
183
184// "Ref"s are pointers on 32-bit machines and ints on 64 bit machines
185// NULL_REF is our representation of either 0 or NULL
186#ifdef __LP64__
187#define NULL_REF 0
188#else
189#define NULL_REF NULL
190#endif
191
192static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */
193static MIDIPortRef portIn = NULL_REF; /* Input port handle */
194static MIDIPortRef portOut = NULL_REF; /* Output port handle */
195static char isIAC[MAX_IAC_NUM + 1]; /* is device an IAC device */
196
197extern pm_fns_node pm_macosx_in_dictionary;
198extern pm_fns_node pm_macosx_out_dictionary;
199
200typedef struct coremidi_info_struct {
201 int is_virtual; /* virtual device (TRUE) or actual device (FALSE)? */
202 UInt64 delta; /* difference between stream time and real time in ns */
203 int sysex_mode; /* middle of sending sysex */
204 uint32_t sysex_word; /* accumulate data when receiving sysex */
205 uint32_t sysex_byte_count; /* count how many received */
206 char error[PM_HOST_ERROR_MSG_LEN];
207 char callback_error[PM_HOST_ERROR_MSG_LEN];
208 Byte packetBuffer[PACKET_BUFFER_SIZE];
209 MIDIPacketList *packetList; /* a pointer to packetBuffer */
210 MIDIPacket *packet;
211 Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
212 MIDITimeStamp sysex_timestamp; /* host timestamp to use with sysex data */
213 /* allow for running status (is running status possible here? -rbd): -cpr */
214 UInt64 min_next_time; /* when can the next send take place? (host time) */
215 int isIACdevice;
216 Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
217 UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
218} coremidi_info_node, *coremidi_info_type;
219
220/* private function declarations */
221MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp); // returns host time
222PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp); // returns ms
223
224char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag);
225
226static PmError check_hosterror(OSStatus err, const char *msg)
227{
228 if (err != noErr) {
229 snprintf(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN, "Host error %ld: %s", (long) err, msg);
230 pm_hosterror = TRUE;
231 return pmHostError;
232 }
233 return pmNoError;
234}
235
236
237static PmTimestamp midi_synchronize(PmInternal *midi)
238{
239 coremidi_info_type info = (coremidi_info_type) midi->api_info;
240 UInt64 pm_stream_time_2 = // current time in ns
241 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
242 PmTimestamp real_time; // in ms
243 UInt64 pm_stream_time; // in ns
244 /* if latency is zero and this is an output, there is no
245 time reference and midi_synchronize should never be called */
246 assert(midi->time_proc);
247 assert(midi->is_input || midi->latency != 0);
248 do {
249 /* read real_time between two reads of stream time */
250 pm_stream_time = pm_stream_time_2;
251 real_time = (*midi->time_proc)(midi->time_info);
252 pm_stream_time_2 = AudioConvertHostTimeToNanos(
253 AudioGetCurrentHostTime());
254 /* repeat if more than 0.5 ms has elapsed */
255 } while (pm_stream_time_2 > pm_stream_time + 500000);
256 info->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
257 midi->sync_time = real_time;
258 return real_time;
259}
260
261
262/* called when MIDI packets are received */
263static void read_callback(const MIDIPacketList *newPackets, PmInternal *midi)
264{
265 PmTimestamp timestamp;
266 MIDIPacket *packet;
267 unsigned int packetIndex;
268 uint32_t now;
269 /* Retrieve the context for this connection */
270 coremidi_info_type info = (coremidi_info_type) midi->api_info;
271 assert(info);
272
273 CM_DEBUG printf("read_callback: numPackets %d: ", newPackets->numPackets);
274
275 /* synchronize time references every 100ms */
276 now = (*midi->time_proc)(midi->time_info);
277 if (midi->first_message || midi->sync_time + 100 /*ms*/ < now) {
278 /* time to resync */
279 now = midi_synchronize(midi);
280 midi->first_message = FALSE;
281 }
282
283 packet = (MIDIPacket *) &newPackets->packet[0];
284 /* hardware devices get untimed messages and apply timestamps. We
285 * want to preserve them because they should be more accurate than
286 * applying the current time here. virtual devices just pass on the
287 * packet->timeStamp, which could be anything. PortMidi says the
288 * PortMidi timestamp is the time the message is received. We do not
289 * know if we are receiving from a device driver or a virtual device.
290 * PortMidi sends to virtual devices get a current timestamp, so we
291 * can treat them as the receive time. If the timestamp is zero,
292 * suggested by CoreMIDI as the value to use for immediate delivery,
293 * then we plug in `now` which is obtained above. If another
294 * application sends bogus non-zero timestamps, we will convert them
295 * to this port's reference time and pass them as event.timestamp.
296 * Receiver beware.
297 */
298 CM_DEBUG printf("read_callback packet @ %lld ns (host %lld) "
299 "status %x length %d\n",
300 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
301 AudioGetCurrentHostTime(),
302 packet->data[0], packet->length);
303 for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
304 /* Set the timestamp and dispatch this message */
305 CM_DEBUG printf(" packet->timeStamp %lld ns %lld host\n",
306 packet->timeStamp,
307 AudioConvertHostTimeToNanos(packet->timeStamp));
308 if (packet->timeStamp == 0) {
309 timestamp = now;
310 } else {
311 timestamp = (PmTimestamp) /* explicit conversion */ (
312 (AudioConvertHostTimeToNanos(packet->timeStamp) - info->delta) /
313 (UInt64) 1000000);
314 }
315 pm_read_bytes(midi, packet->data, packet->length, timestamp);
316 packet = MIDIPacketNext(packet);
317 }
318}
319
320/* callback for real devices - redirects to read_callback */
321static void device_read_callback(const MIDIPacketList *newPackets,
322 void *refCon, void *connRefCon)
323{
324 read_callback(newPackets, (PmInternal *) connRefCon);
325}
326
327
328/* callback for virtual devices - redirects to read_callback */
329static void virtual_read_callback(const MIDIPacketList *newPackets,
330 void *refCon, void *connRefCon)
331{
332 /* this refCon is the device ID -- if there is a valid ID and
333 the pm_descriptors table has a non-null pointer to a PmInternal,
334 then then device is open and should receive this data */
335 PmDeviceID id = (PmDeviceID) (size_t) refCon;
336 if (id >= 0 && id < pm_descriptor_len) {
337 if (pm_descriptors[id].pub.opened) {
338 /* check for close request (7 reset status bytes): */
339 if (newPackets->numPackets == 1 &&
340 newPackets->packet[0].length == 8 &&
341 /* CoreMIDI declares packets with 4-byte alignment, so we
342 * should be safe to test for 8 0xFF's as 2 32-bit values: */
343 *(SInt32 *) &newPackets->packet[0].data[0] == -1 &&
344 *(SInt32 *) &newPackets->packet[0].data[4] == -1) {
345 CM_DEBUG printf("got close request packet\n");
346 pm_descriptors[id].pub.opened = FALSE;
347 return;
348 } else {
349 read_callback(newPackets, pm_descriptors[id].pm_internal);
350 }
351 }
352 }
353}
354
355
356/* allocate and initialize our internal coremidi connection info */
357static coremidi_info_type create_macosxcm_info(int is_virtual, int is_input)
358{
359 coremidi_info_type info = (coremidi_info_type)
360 pm_alloc(sizeof(coremidi_info_node));
361 if (!info) {
362 return NULL;
363 }
364 info->is_virtual = is_virtual;
365 info->delta = 0;
366 info->sysex_mode = FALSE;
367 info->sysex_word = 0;
368 info->sysex_byte_count = 0;
369 info->packet = NULL;
370 info->min_next_time = 0;
371 info->isIACdevice = FALSE;
372 info->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();
373 info->host_ticks_per_byte =
374 (UInt64) (1000000.0 / (info->us_per_host_tick * MAX_BYTES_PER_S));
375 info->packetList = (is_input ? NULL :
376 (MIDIPacketList *) info->packetBuffer);
377 return info;
378}
379
380
381static PmError midi_in_open(PmInternal *midi, void *driverInfo)
382{
383 MIDIEndpointRef endpoint;
384 coremidi_info_type info;
385 OSStatus macHostError;
386 int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
387
388 /* if this is an external device, descriptor is a MIDIEndpointRef.
389 * if this is a virtual device for this application, descriptor is NULL.
390 */
391 if (!is_virtual) {
392 endpoint = (MIDIEndpointRef) (intptr_t)
393 pm_descriptors[midi->device_id].descriptor;
394 if (endpoint == NULL_REF) {
395 return pmInvalidDeviceId;
396 }
397 }
398
399 info = create_macosxcm_info(is_virtual, TRUE);
400 midi->api_info = info;
401 if (!info) {
402 return pmInsufficientMemory;
403 }
404 if (!is_virtual) {
405 macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
406 if (macHostError != noErr) {
407 midi->api_info = NULL;
408 pm_free(info);
409 return check_hosterror(macHostError,
410 "MIDIPortConnectSource() in midi_in_open()");
411 }
412 }
413 return pmNoError;
414}
415
416static PmError midi_in_close(PmInternal *midi)
417{
418 MIDIEndpointRef endpoint;
419 OSStatus macHostError;
420 PmError err = pmNoError;
421
422 coremidi_info_type info = (coremidi_info_type) midi->api_info;
423
424 if (!info) return pmBadPtr;
425
426 endpoint = (MIDIEndpointRef) (intptr_t)
427 pm_descriptors[midi->device_id].descriptor;
428 if (endpoint == NULL_REF) {
429 return pmBadPtr;
430 }
431
432 if (!info->is_virtual) {
433 /* shut off the incoming messages before freeing data structures */
434 macHostError = MIDIPortDisconnectSource(portIn, endpoint);
435 /* If the source closes, you get paramErr == -50 here. It seems
436 * possible to monitor changes like sources closing by getting
437 * notifications ALL changes, but the CoreMIDI documentation is
438 * really terrible overall, and it seems easier to just ignore
439 * this host error.
440 */
441 if (macHostError != noErr && macHostError != -50) {
442 pm_hosterror = TRUE;
443 err = check_hosterror(macHostError,
444 "MIDIPortDisconnectSource() in midi_in_close()");
445 }
446 } else {
447 /* make "close virtual port" message */
448 SInt64 close_port_bytes = 0xFFFFFFFFFFFFFFFF;
449 /* memory requirements: packet count (4), timestamp (8), length (2),
450 * data (8). Total: 22, but we allocate plenty more:
451 */
452 Byte packetBuffer[64];
453 MIDIPacketList *plist = (MIDIPacketList *) packetBuffer;
454 MIDIPacket *packet = MIDIPacketListInit(plist);
455 MIDIPacketListAdd(plist, 64, packet, 0, 8,
456 (const Byte *) &close_port_bytes);
457 macHostError = MIDISend(portOut, endpoint, plist);
458 if (macHostError != noErr) {
459 err = check_hosterror(macHostError, "MIDISend() (PortMidi close "
460 "port packet) in midi_in_close()");
461 }
462 /* when packet is delivered, callback thread will clear opened;
463 * we must wait for that before removing the input queues etc.
464 * Maybe this could use signals of some kind, but if signals use
465 * locks, locks can cause priority inversion problems, so we will
466 * just sleep as needed. On the MIDI timescale, inserting a 0.5ms
467 * latency should be OK, as the application has no business
468 * opening/closing devices during time-critical moments.
469 *
470 * We expect the MIDI thread to close the device quickly (<0.5ms),
471 * but we wait up to 50ms in case something terrible happens like
472 * getting paged out in the middle of deliving packets to this
473 * virtual device. If there is still no response, we time out and
474 * force the close without the MIDI thread (even this will probably
475 * succeed - the problem would be: this thread proceeds to delete
476 * the input queues, and the freed memory is reallocated and
477 * overwritten so that queues are no longer usable. Meanwhile,
478 * the MIDI thread has already begun to deliver packets, so the
479 * check for opened == TRUE passed, but MIDI thread does not insert
480 * into queue until queue is freed, reallocated and overwritten.
481 */
482 for (int i = 0; i < 100; i++) { /* up to 50ms delay */
483 if (!pm_descriptors[midi->device_id].pub.opened) {
484 break;
485 }
486 usleep(500); /* 0.5ms */
487 }
488 pm_descriptors[midi->device_id].pub.opened = FALSE; /* force it */
489 }
490 midi->api_info = NULL;
491 pm_free(info);
492 return err;
493}
494
495
496static PmError midi_out_open(PmInternal *midi, void *driverInfo)
497{
498 coremidi_info_type info;
499 int is_virtual = pm_descriptors[midi->device_id].pub.is_virtual;
500
501 info = create_macosxcm_info(is_virtual, FALSE);
502 if (midi->device_id <= MAX_IAC_NUM) {
503 info->isIACdevice = isIAC[midi->device_id];
504 CM_DEBUG printf("midi_out_open isIACdevice %d\n", info->isIACdevice);
505 }
506 midi->api_info = info;
507 if (!info) {
508 return pmInsufficientMemory;
509 }
510 return pmNoError;
511}
512
513
514static PmError midi_out_close(PmInternal *midi)
515{
516 coremidi_info_type info = (coremidi_info_type) midi->api_info;
517 if (!info) return pmBadPtr;
518 midi->api_info = NULL;
519 pm_free(info);
520 return pmNoError;
521}
522
523
524/* MIDIDestinationCreate apparently cannot create a virtual device
525 * without a callback and a "refCon" parameter, but when we create
526 * a virtual device, we do not want a PortMidi stream yet -- that
527 * should wait for the user to open the stream. So, for the refCon,
528 * use the PortMidi device ID. The callback will check if the
529 * device is opened within PortMidi, and if so, use the pm_descriptors
530 * table to locate the corresponding PmStream.
531 */
532static PmError midi_create_virtual(int is_input, const char *name,
533 void *device_info)
534{
535 OSStatus macHostError;
536 MIDIEndpointRef endpoint;
537 CFStringRef nameRef;
538 PmDeviceID id = pm_add_device("CoreMIDI", name, is_input, TRUE, NULL,
539 (is_input ? &pm_macosx_in_dictionary :
540 &pm_macosx_out_dictionary));
541 if (id < 0) { /* error -- out of memory or name conflict? */
542 return id;
543 }
544
545 nameRef = CFStringCreateWithCString(NULL, name, kCFStringEncodingUTF8);
546 if (is_input) {
547 macHostError = MIDIDestinationCreate(client, nameRef,
548 virtual_read_callback, (void *) (intptr_t) id, &endpoint);
549 } else {
550 macHostError = MIDISourceCreate(client, nameRef, &endpoint);
551 }
552 CFRelease(nameRef);
553
554 if (macHostError != noErr) {
555 /* undo the device we just allocated */
556 pm_undo_add_device(id);
557 return check_hosterror(macHostError, (is_input ?
558 "MIDIDestinationCreateWithProtocol() in midi_create_virtual()" :
559 "MIDISourceCreateWithProtocol() in midi_create_virtual()"));
560 }
561
562 /* Do we have a manufacturer name? If not, set to "PortMidi" */
563 const char *mfr_name = "PortMidi";
564 PmSysDepInfo *info = (PmSysDepInfo *) device_info;
565 /* the version where pmKeyCoreMidiManufacturer was introduced is 210 */
566 if (info && info->structVersion >= 210) {
567 int i;
568 for (i = 0; i < info->length; i++) { /* search for key */
569 if (info->properties[i].key == pmKeyCoreMidiManufacturer) {
570 mfr_name = info->properties[i].value;
571 break;
572 } /* no other keys are recognized; they are ignored */
573 }
574 }
575 nameRef = CFStringCreateWithCString(NULL, mfr_name, kCFStringEncodingUTF8);
576 MIDIObjectSetStringProperty(endpoint, kMIDIPropertyManufacturer, nameRef);
577 CFRelease(nameRef);
578
579 pm_descriptors[id].descriptor = (void *) (intptr_t) endpoint;
580 return id;
581}
582
583
584static PmError midi_delete_virtual(PmDeviceID id)
585{
586 MIDIEndpointRef endpoint;
587 OSStatus macHostError;
588
589 endpoint = (MIDIEndpointRef) (long) pm_descriptors[id].descriptor;
590 if (endpoint == NULL_REF) {
591 return pmBadPtr;
592 }
593 macHostError = MIDIEndpointDispose(endpoint);
594 return check_hosterror(macHostError,
595 "MIDIEndpointDispose() in midi_in_close()");
596}
597
598
599static PmError midi_abort(PmInternal *midi)
600{
601 OSStatus macHostError;
602 MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
603 pm_descriptors[midi->device_id].descriptor;
604 macHostError = MIDIFlushOutput(endpoint);
605 return check_hosterror(macHostError,
606 "MIDIFlushOutput() in midi_abort()");
607}
608
609
610static PmError midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
611{
612 OSStatus macHostError = 0;
613 coremidi_info_type info = (coremidi_info_type) midi->api_info;
614 MIDIEndpointRef endpoint = (MIDIEndpointRef) (intptr_t)
615 pm_descriptors[midi->device_id].descriptor;
616 assert(info);
617 assert(endpoint);
618 if (info->packet != NULL) {
619 /* out of space, send the buffer and start refilling it */
620 /* update min_next_time each flush to support rate limit */
621 UInt64 host_now = AudioGetCurrentHostTime();
622 if (host_now > info->min_next_time)
623 info->min_next_time = host_now;
624 if (info->is_virtual) {
625 macHostError = MIDIReceived(endpoint, info->packetList);
626 } else {
627 macHostError = MIDISend(portOut, endpoint, info->packetList);
628 }
629 info->packet = NULL; /* indicate no data in packetList now */
630 }
631 return check_hosterror(macHostError, (info->is_virtual ?
632 "MIDIReceived() in midi_write()" :
633 "MIDISend() in midi_write()"));
634}
635
636
637static PmError send_packet(PmInternal *midi, Byte *message,
638 unsigned int messageLength, MIDITimeStamp timestamp)
639{
640 PmError err;
641 coremidi_info_type info = (coremidi_info_type) midi->api_info;
642 assert(info);
643
644 CM_DEBUG printf("add %d to packet %p len %d timestamp %lld @ %lld ns "
645 "(host %lld)\n",
646 message[0], info->packet, messageLength, timestamp,
647 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()),
648 AudioGetCurrentHostTime());
649 info->packet = MIDIPacketListAdd(info->packetList,
650 sizeof(info->packetBuffer), info->packet,
651 timestamp, messageLength, message);
652#if LIMIT_SEND_RATE
653 info->byte_count += messageLength;
654#endif
655 if (info->packet == NULL) {
656 /* out of space, send the buffer and start refilling it */
657 /* make midi->packet non-null to fool midi_write_flush into sending */
658 info->packet = (MIDIPacket *) 4;
659 /* timestamp is 0 because midi_write_flush ignores timestamp since
660 * timestamps are already in packets. The timestamp parameter is here
661 * because other API's need it. midi_write_flush can be called
662 * from system-independent code that must be cross-API.
663 */
664 if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;
665 info->packet = MIDIPacketListInit(info->packetList);
666 assert(info->packet); /* if this fails, it's a programming error */
667 info->packet = MIDIPacketListAdd(info->packetList,
668 sizeof(info->packetBuffer), info->packet,
669 timestamp, messageLength, message);
670 assert(info->packet); /* can't run out of space on first message */
671 }
672 return pmNoError;
673}
674
675
676static PmError midi_write_short(PmInternal *midi, PmEvent *event)
677{
678 PmTimestamp when = event->timestamp;
679 PmMessage what = event->message;
680 MIDITimeStamp timestamp;
681 coremidi_info_type info = (coremidi_info_type) midi->api_info;
682 Byte message[4];
683 unsigned int messageLength;
684
685 if (info->packet == NULL) {
686 info->packet = MIDIPacketListInit(info->packetList);
687 /* this can never fail, right? failure would indicate something
688 unrecoverable */
689 assert(info->packet);
690 }
691
692 /* PortMidi specifies that incoming timestamps are the receive
693 * time. Devices attach their receive times, but virtual devices
694 * do not. Instead, they pass along whatever timestamp was sent to
695 * them. We do not know if we are connected to real or virtual
696 * device. To avoid wild timestamps on the receiving end, we
697 * consider 2 cases: PortMidi timestamp is zero or latency is
698 * zero. Both mean send immediately, so we attach the current time
699 * which will go out immediately and arrive with a sensible
700 * timestamp (not zero and not zero mapped to the client's local
701 * time). Otherwise, we assume the timestamp is reasonable. It
702 * might be slighly in the past, but we pass it along after
703 * translation to MIDITimeStamp units.
704 *
705 * Compute timestamp: use current time if timestamp is zero or
706 * latency is zero. Both mean no timing and send immediately.
707 */
708 if (when == 0 || midi->latency == 0) {
709 timestamp = AudioGetCurrentHostTime();
710 } else { /* translate PortMidi time + latency to CoreMIDI time */
711 timestamp = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
712 info->delta;
713 timestamp = AudioConvertNanosToHostTime(timestamp);
714 }
715
716 message[0] = Pm_MessageStatus(what);
717 message[1] = Pm_MessageData1(what);
718 message[2] = Pm_MessageData2(what);
719 messageLength = pm_midi_length((int32_t) what);
720
721#ifdef LIMIT_RATE
722 /* Make sure we go forward in time. */
723 if (timestamp < info->min_next_time) {
724 timestamp = info->min_next_time;
725 }
726 /* Note that if application is way behind and slowly catching up, then
727 * timestamps could be increasing faster than real time, and since
728 * timestamps are used to estimate data rate, our estimate could be
729 * low, causing CoreMIDI to drop packets. This seems very unlikely.
730 */
731 if (info->isIACdevice || info->is_virtual) {
732 info->min_next_time = timestamp + messageLength *
733 info->host_ticks_per_byte;
734 }
735#endif
736 /* Add this message to the packet list */
737 return send_packet(midi, message, messageLength, timestamp);
738}
739
740
741static PmError midi_begin_sysex(PmInternal *midi, PmTimestamp when)
742{
743 UInt64 when_ns;
744 coremidi_info_type info = (coremidi_info_type) midi->api_info;
745 assert(info);
746 info->sysex_byte_count = 0;
747
748 /* compute timestamp */
749 if (when == 0) when = midi->now;
750 /* if latency == 0, midi->now is not valid. We will just set it to zero */
751 if (midi->latency == 0) when = 0;
752 when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) +
753 info->delta;
754 info->sysex_timestamp =
755 (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
756 UInt64 now; /* only make system time call when writing a virtual port */
757 if (info->is_virtual && info->sysex_timestamp <
758 (now = AudioGetCurrentHostTime())) {
759 info->sysex_timestamp = now;
760 }
761
762 if (info->packet == NULL) {
763 info->packet = MIDIPacketListInit(info->packetList);
764 /* this can never fail, right? failure would indicate something
765 unrecoverable */
766 assert(info->packet);
767 }
768 return pmNoError;
769}
770
771
772static PmError midi_end_sysex(PmInternal *midi, PmTimestamp when)
773{
774 PmError err;
775 coremidi_info_type info = (coremidi_info_type) midi->api_info;
776 assert(info);
777
778#ifdef LIMIT_RATE
779 /* make sure we go foreward in time */
780 if (info->sysex_timestamp < info->min_next_time)
781 info->sysex_timestamp = info->min_next_time;
782
783 if (info->isIACdevice) {
784 info->min_next_time = info->sysex_timestamp + info->sysex_byte_count *
785 info->host_ticks_per_byte;
786 }
787#endif
788
789 /* now send what's in the buffer */
790 err = send_packet(midi, info->sysex_buffer, info->sysex_byte_count,
791 info->sysex_timestamp);
792 info->sysex_byte_count = 0;
793 if (err != pmNoError) {
794 info->packet = NULL; /* flush everything in the packet list */
795 }
796 return err;
797}
798
799
800static PmError midi_write_byte(PmInternal *midi, unsigned char byte,
801 PmTimestamp timestamp)
802{
803 coremidi_info_type info = (coremidi_info_type) midi->api_info;
804 assert(info);
805 if (info->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
806 PmError err = midi_end_sysex(midi, timestamp);
807 if (err != pmNoError) return err;
808 }
809 info->sysex_buffer[info->sysex_byte_count++] = byte;
810 return pmNoError;
811}
812
813
814static PmError midi_write_realtime(PmInternal *midi, PmEvent *event)
815{
816 /* to send a realtime message during a sysex message, first
817 flush all pending sysex bytes into packet list */
818 PmError err = midi_end_sysex(midi, 0);
819 if (err != pmNoError) return err;
820 /* then we can just do a normal midi_write_short */
821 return midi_write_short(midi, event);
822}
823
824
825static unsigned int midi_check_host_error(PmInternal *midi)
826{
827 return FALSE;
828}
829
830
831MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
832{
833 UInt64 nanos;
834 if (timestamp <= 0) {
835 return (MIDITimeStamp)0;
836 } else {
837 nanos = (UInt64)timestamp * (UInt64)1000000;
838 return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
839 }
840}
841
842
843PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
844{
845 UInt64 nanos;
846 nanos = AudioConvertHostTimeToNanos(timestamp);
847 return (PmTimestamp)(nanos / (UInt64)1000000);
848}
849
850
851//
852// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
853//////////////////////////////////////
854// Obtain the name of an endpoint without regard for whether it has connections.
855// The result should be released by the caller.
856CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal,
857 int *iac_flag)
858{
859 CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
860 CFStringRef str;
861 *iac_flag = FALSE;
862
863 // begin with the endpoint's name
864 str = NULL;
865 MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
866 if (str != NULL) {
867 CFStringAppend(result, str);
868 CFRelease(str);
869 }
870 MIDIEntityRef entity = NULL_REF;
871 MIDIEndpointGetEntity(endpoint, &entity);
872 if (entity == NULL_REF) {
873 // probably virtual
874 return result;
875 }
876 if (!isExternal) { /* detect IAC devices */
877 //extern const CFStringRef kMIDIPropertyDriverOwner;
878 MIDIObjectGetStringProperty(entity, kMIDIPropertyDriverOwner, &str);
879 if (str != NULL) {
880 char s[32]; /* driver name may truncate, but that's OK */
881 CFStringGetCString(str, s, 31, kCFStringEncodingUTF8);
882 s[31] = 0; /* make sure it is terminated just to be safe */
883 CM_DEBUG printf("driver %s\n", s);
884 *iac_flag = (strcmp(s, "com.apple.AppleMIDIIACDriver") == 0);
885 }
886 }
887
888 if (CFStringGetLength(result) == 0) {
889 // endpoint name has zero length -- try the entity
890 str = NULL;
891 MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
892 if (str != NULL) {
893 CFStringAppend(result, str);
894 CFRelease(str);
895 }
896 }
897 // now consider the device's name
898 MIDIDeviceRef device = NULL_REF;
899 MIDIEntityGetDevice(entity, &device);
900 if (device == NULL_REF)
901 return result;
902
903 str = NULL;
904 MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
905 if (CFStringGetLength(result) == 0) {
906 CFRelease(result);
907 return str;
908 }
909 if (str != NULL) {
910 // if an external device has only one entity, throw away
911 // the endpoint name and just use the device name
912 if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
913 CFRelease(result);
914 return str;
915 } else {
916 if (CFStringGetLength(str) == 0) {
917 CFRelease(str);
918 return result;
919 }
920 // does the entity name already start with the device name?
921 // (some drivers do this though they shouldn't)
922 // if so, do not prepend
923 if (CFStringCompareWithOptions(result, /* endpoint name */
924 str, /* device name */
925 CFRangeMake(0, CFStringGetLength(str)), 0) !=
926 kCFCompareEqualTo) {
927 // prepend the device name to the entity name
928 if (CFStringGetLength(result) > 0)
929 CFStringInsert(result, 0, CFSTR(" "));
930 CFStringInsert(result, 0, str);
931 }
932 CFRelease(str);
933 }
934 }
935 return result;
936}
937
938
939// Obtain the name of an endpoint, following connections.
940// The result should be released by the caller.
941static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint,
942 int *iac_flag)
943{
944 CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
945 CFStringRef str;
946 OSStatus err;
947 long i;
948
949 // Does the endpoint have connections?
950 CFDataRef connections = NULL;
951 long nConnected = 0;
952 bool anyStrings = false;
953 err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID,
954 &connections);
955 if (connections != NULL) {
956 // It has connections, follow them
957 // Concatenate the names of all connected devices
958 nConnected = CFDataGetLength(connections) /
959 (int32_t) sizeof(MIDIUniqueID);
960 if (nConnected) {
961 const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
962 for (i = 0; i < nConnected; ++i, ++pid) {
963 MIDIUniqueID id = EndianS32_BtoN(*pid);
964 MIDIObjectRef connObject;
965 MIDIObjectType connObjectType;
966 err = MIDIObjectFindByUniqueID(id, &connObject,
967 &connObjectType);
968 if (err == noErr) {
969 if (connObjectType == kMIDIObjectType_ExternalSource ||
970 connObjectType == kMIDIObjectType_ExternalDestination) {
971 // Connected to an external device's endpoint (>=10.3)
972 str = EndpointName((MIDIEndpointRef)(connObject), true,
973 iac_flag);
974 } else {
975 // Connected to an external device (10.2)
976 // (or something else, catch-all)
977 str = NULL;
978 MIDIObjectGetStringProperty(connObject,
979 kMIDIPropertyName, &str);
980 }
981 if (str != NULL) {
982 if (anyStrings)
983 CFStringAppend(result, CFSTR(", "));
984 else anyStrings = true;
985 CFStringAppend(result, str);
986 CFRelease(str);
987 }
988 }
989 }
990 }
991 CFRelease(connections);
992 }
993 if (anyStrings)
994 return result; // caller should release result
995
996 CFRelease(result);
997
998 // Here, either the endpoint had no connections, or we failed to
999 // obtain names for any of them.
1000 return EndpointName(endpoint, false, iac_flag);
1001}
1002
1003
1004char *cm_get_full_endpoint_name(MIDIEndpointRef endpoint, int *iac_flag)
1005{
1006 /* Thanks to Dan Wilcox for fixes for Unicode handling */
1007 CFStringRef fullName = ConnectedEndpointName(endpoint, iac_flag);
1008 CFIndex utf16_len = CFStringGetLength(fullName) + 1;
1009 CFIndex max_byte_len = CFStringGetMaximumSizeForEncoding(
1010 utf16_len, kCFStringEncodingUTF8) + 1;
1011 char* pmname = (char *) pm_alloc(CFStringGetLength(fullName) + 1);
1012
1013 /* copy the string into our buffer; note that there may be some wasted
1014 space, but the total waste is not large */
1015 CFStringGetCString(fullName, pmname, max_byte_len, kCFStringEncodingUTF8);
1016
1017 /* clean up */
1018 if (fullName) CFRelease(fullName);
1019 return pmname;
1020}
1021
1022
1023pm_fns_node pm_macosx_in_dictionary = {
1024 none_write_short,
1025 none_sysex,
1026 none_sysex,
1027 none_write_byte,
1028 none_write_short,
1029 none_write_flush,
1030 none_synchronize,
1031 midi_in_open,
1032 midi_abort,
1033 midi_in_close,
1034 success_poll,
1035 midi_check_host_error
1036};
1037
1038pm_fns_node pm_macosx_out_dictionary = {
1039 midi_write_short,
1040 midi_begin_sysex,
1041 midi_end_sysex,
1042 midi_write_byte,
1043 midi_write_realtime,
1044 midi_write_flush,
1045 midi_synchronize,
1046 midi_out_open,
1047 midi_abort,
1048 midi_out_close,
1049 success_poll,
1050 midi_check_host_error
1051};
1052
1053
1054/* We do nothing with callbacks, but generating the callbacks also
1055 * updates CoreMIDI state. Callback may not be essential, but calling
1056 * the CFRunLoopRunInMode is necessary.
1057 */
1058void cm_notify(const MIDINotification *msg, void *refCon)
1059{
1060 /* for debugging, trace change notifications:
1061 const char *descr[] = {
1062 "undefined (0)",
1063 "kMIDIMsgSetupChanged",
1064 "kMIDIMsgObjectAdded",
1065 "kMIDIMsgObjectRemoved",
1066 "kMIDIMsgPropertyChanged",
1067 "kMIDIMsgThruConnectionsChanged",
1068 "kMIDIMsgSerialPortOwnerChanged",
1069 "kMIDIMsgIOError"};
1070
1071 printf("MIDI Notify, messageID %d (%s)\n", (int) msg->messageID,
1072 descr[(int) msg->messageID]);
1073 */
1074 return;
1075}
1076
1077
1078PmError pm_macosxcm_init(void)
1079{
1080 ItemCount numInputs, numOutputs, numDevices;
1081 MIDIEndpointRef endpoint;
1082 OSStatus macHostError = noErr;
1083 char *error_text;
1084
1085 memset(isIAC, 0, sizeof(isIAC)); /* initialize all FALSE */
1086
1087 /* Register interface CoreMIDI with create_virtual fn */
1088 pm_add_interf("CoreMIDI", &midi_create_virtual, &midi_delete_virtual);
1089 /* no check for error return because this always succeeds */
1090
1091 /* Determine the number of MIDI devices on the system */
1092 numDevices = MIDIGetNumberOfDevices();
1093
1094 /* Return prematurely if no devices exist on the system
1095 Note that this is not an error. There may be no devices.
1096 Pm_CountDevices() will return zero, which is correct and
1097 useful information
1098 */
1099 if (numDevices <= 0) {
1100 return pmNoError;
1101 }
1102
1103 /* Initialize the client handle */
1104 if (client == NULL_REF) {
1105 macHostError = MIDIClientCreate(CFSTR("PortMidi"), &cm_notify, NULL,
1106 &client);
1107 } else { /* see notes above on device scanning */
1108 for (int i = 0; i < 100; i++) {
1109 // look for any changes before scanning for devices
1110 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
1111 if (i % 5 == 0) Pt_Sleep(1); /* insert 20 delays */
1112 }
1113 }
1114 if (macHostError != noErr) {
1115 error_text = "MIDIClientCreate() in pm_macosxcm_init()";
1116 goto error_return;
1117 }
1118 numInputs = MIDIGetNumberOfSources();
1119 numOutputs = MIDIGetNumberOfDestinations();
1120
1121 /* Create the input port */
1122 macHostError = MIDIInputPortCreate(client, CFSTR("Input port"),
1123 device_read_callback, NULL, &portIn);
1124 if (macHostError != noErr) {
1125 error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
1126 goto error_return;
1127 }
1128
1129 /* Create the output port */
1130 macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
1131 if (macHostError != noErr) {
1132 error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
1133 goto error_return;
1134 }
1135
1136 /* Iterate over the MIDI input devices */
1137 for (int i = 0; i < numInputs; i++) {
1138 int iac_flag;
1139 endpoint = MIDIGetSource(i);
1140 if (endpoint == NULL_REF) {
1141 continue;
1142 }
1143 /* Register this device with PortMidi */
1144 pm_add_device("CoreMIDI",
1145 cm_get_full_endpoint_name(endpoint, &iac_flag), TRUE, FALSE,
1146 (void *) (intptr_t) endpoint, &pm_macosx_in_dictionary);
1147 }
1148
1149 /* Iterate over the MIDI output devices */
1150 for (int i = 0; i < numOutputs; i++) {
1151 int iac_flag;
1152 PmDeviceID id;
1153 endpoint = MIDIGetDestination(i);
1154 if (endpoint == NULL_REF) {
1155 continue;
1156 }
1157 /* Register this device with PortMidi */
1158 id = pm_add_device("CoreMIDI",
1159 cm_get_full_endpoint_name(endpoint, &iac_flag), FALSE, FALSE,
1160 (void *) (intptr_t) endpoint, &pm_macosx_out_dictionary);
1161 /* if this is an IAC device, tuck that info away for write functions */
1162 if (iac_flag && id <= MAX_IAC_NUM) {
1163 isIAC[id] = TRUE;
1164 }
1165 }
1166 return pmNoError;
1167
1168error_return:
1169 pm_macosxcm_term(); /* clear out any opened ports */
1170 return check_hosterror(macHostError, error_text);
1171}
1172
1173void pm_macosxcm_term(void)
1174{
1175 /* docs say do not explicitly dispose of client
1176 if (client != NULL_REF) MIDIClientDispose(client); */
1177 if (portIn != NULL_REF) MIDIPortDispose(portIn);
1178 if (portOut != NULL_REF) MIDIPortDispose(portOut);
1179}