2 * Platform interface to the MacOS X CoreMIDI framework
4 * Jon Parise <jparise at cmu.edu>
5 * and subsequent work by Andrew Zeldis and Zico Kolter
6 * and Roger B. Dannenberg
8 * $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $
12 since the input and output streams are represented by MIDIEndpointRef
13 values and almost no other state, we store the MIDIEndpointRef on
14 descriptors[midi->device_id].descriptor. The only other state we need
15 is for errors: we need to know if there is an error and if so, what is
16 the error text. We use a structure with two kinds of
17 host error: "error" and "callback_error". That way, asynchronous callbacks
18 do not interfere with other error information.
20 OS X does not seem to have an error-code-to-text function, so we will
21 just use text messages instead of error codes.
30 #include "pminternal.h"
33 #include "pmmacosxcm.h"
38 #include <CoreServices/CoreServices.h>
39 #include <CoreMIDI/MIDIServices.h>
40 #include <CoreAudio/HostTime.h>
43 #define PACKET_BUFFER_SIZE 1024
44 /* maximum overall data rate (OS X limit is 15000 bytes/second) */
45 #define MAX_BYTES_PER_S 14000
47 /* Apple reports that packets are dropped when the MIDI bytes/sec
48 exceeds 15000. This is computed by "tracking the number of MIDI
49 bytes scheduled into 1-second buckets over the last six seconds
50 and averaging these counts."
52 This is apparently based on timestamps, not on real time, so
53 we have to avoid constructing packets that schedule high speed
54 output even if the actual writes are delayed (which was my first
57 The LIMIT_RATE symbol, if defined, enables code to modify
58 timestamps as follows:
59 After each packet is formed, the next allowable timestamp is
60 computed as this_packet_time + this_packet_len * delay_per_byte
62 This is the minimum timestamp allowed in the next packet.
64 Note that this distorts accurate timestamps somewhat.
68 #define SYSEX_BUFFER_SIZE 128
71 #define VERBOSE if (VERBOSE_ON)
73 #define MIDI_SYSEX 0xf0
75 #define MIDI_STATUS_MASK 0x80
77 // "Ref"s are pointers on 32-bit machines and ints on 64 bit machines
78 // NULL_REF is our representation of either 0 or NULL
85 static MIDIClientRef client = NULL_REF; /* Client handle to the MIDI server */
86 static MIDIPortRef portIn = NULL_REF; /* Input port handle */
87 static MIDIPortRef portOut = NULL_REF; /* Output port handle */
89 extern pm_fns_node pm_macosx_in_dictionary;
90 extern pm_fns_node pm_macosx_out_dictionary;
92 typedef struct midi_macosxcm_struct {
93 PmTimestamp sync_time; /* when did we last determine delta? */
94 UInt64 delta; /* difference between stream time and real time in ns */
95 UInt64 last_time; /* last output time in host units*/
96 int first_message; /* tells midi_write to sychronize timestamps */
97 int sysex_mode; /* middle of sending sysex */
98 uint32_t sysex_word; /* accumulate data when receiving sysex */
99 uint32_t sysex_byte_count; /* count how many received */
100 char error[PM_HOST_ERROR_MSG_LEN];
101 char callback_error[PM_HOST_ERROR_MSG_LEN];
102 Byte packetBuffer[PACKET_BUFFER_SIZE];
103 MIDIPacketList *packetList; /* a pointer to packetBuffer */
105 Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
106 MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */
107 /* allow for running status (is running status possible here? -rbd): -cpr */
108 unsigned char last_command;
109 int32_t last_msg_length;
110 /* limit midi data rate (a CoreMidi requirement): */
111 UInt64 min_next_time; /* when can the next send take place? */
112 int byte_count; /* how many bytes in the next packet list? */
113 Float64 us_per_host_tick; /* host clock frequency, units of min_next_time */
114 UInt64 host_ticks_per_byte; /* host clock units per byte at maximum rate */
115 } midi_macosxcm_node, *midi_macosxcm_type;
117 /* private function declarations */
118 MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
119 PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
121 char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
125 midi_length(int32_t msg)
127 int status, high, low;
128 static int high_lengths[] = {
129 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
130 3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
132 static int low_lengths[] = {
133 1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
134 1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
141 return (high != 0xF) ? high_lengths[high] : low_lengths[low];
144 static PmTimestamp midi_synchronize(PmInternal *midi)
146 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
147 UInt64 pm_stream_time_2 =
148 AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
149 PmTimestamp real_time;
150 UInt64 pm_stream_time;
151 /* if latency is zero and this is an output, there is no
152 time reference and midi_synchronize should never be called */
153 assert(midi->time_proc);
154 assert(!(midi->write_flag && midi->latency == 0));
156 /* read real_time between two reads of stream time */
157 pm_stream_time = pm_stream_time_2;
158 real_time = (*midi->time_proc)(midi->time_info);
159 pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
160 /* repeat if more than 0.5 ms has elapsed */
161 } while (pm_stream_time_2 > pm_stream_time + 500000);
162 m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
163 m->sync_time = real_time;
169 process_packet(MIDIPacket *packet, PmEvent *event,
170 PmInternal *midi, midi_macosxcm_type m)
172 /* handle a packet of MIDI messages from CoreMIDI */
173 /* there may be multiple short messages in one packet (!) */
174 unsigned int remaining_length = packet->length;
175 unsigned char *cur_packet_data = packet->data;
176 while (remaining_length > 0) {
177 if (cur_packet_data[0] == MIDI_SYSEX ||
178 /* are we in the middle of a sysex message? */
179 (m->last_command == 0 &&
180 !(cur_packet_data[0] & MIDI_STATUS_MASK))) {
181 m->last_command = 0; /* no running status */
182 unsigned int amt = pm_read_bytes(midi, cur_packet_data,
185 remaining_length -= amt;
186 cur_packet_data += amt;
187 } else if (cur_packet_data[0] == MIDI_EOX) {
188 /* this should never happen, because pm_read_bytes should
189 * get and read all EOX bytes*/
190 midi->sysex_in_progress = FALSE;
192 } else if (cur_packet_data[0] & MIDI_STATUS_MASK) {
193 /* compute the length of the next (short) msg in packet */
194 unsigned int cur_message_length = midi_length(cur_packet_data[0]);
195 if (cur_message_length > remaining_length) {
197 printf("PortMidi debug msg: not enough data");
199 /* since there's no more data, we're done */
202 m->last_msg_length = cur_message_length;
203 m->last_command = cur_packet_data[0];
204 switch (cur_message_length) {
206 event->message = Pm_Message(cur_packet_data[0], 0, 0);
209 event->message = Pm_Message(cur_packet_data[0],
210 cur_packet_data[1], 0);
213 event->message = Pm_Message(cur_packet_data[0],
218 /* PortMIDI internal error; should never happen */
219 assert(cur_message_length == 1);
220 return; /* give up on packet if continued after assert */
222 pm_read_short(midi, event);
223 remaining_length -= m->last_msg_length;
224 cur_packet_data += m->last_msg_length;
225 } else if (m->last_msg_length > remaining_length + 1) {
226 /* we have running status, but not enough data */
228 printf("PortMidi debug msg: not enough data in CoreMIDI packet");
230 /* since there's no more data, we're done */
232 } else { /* output message using running status */
233 switch (m->last_msg_length) {
235 event->message = Pm_Message(m->last_command, 0, 0);
238 event->message = Pm_Message(m->last_command,
239 cur_packet_data[0], 0);
242 event->message = Pm_Message(m->last_command,
247 /* last_msg_length is invalid -- internal PortMIDI error */
248 assert(m->last_msg_length == 1);
250 pm_read_short(midi, event);
251 remaining_length -= (m->last_msg_length - 1);
252 cur_packet_data += (m->last_msg_length - 1);
259 /* called when MIDI packets are received */
261 readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
264 midi_macosxcm_type m;
267 unsigned int packetIndex;
272 printf("readProc: numPackets %d: ", newPackets->numPackets);
275 /* Retrieve the context for this connection */
276 midi = (PmInternal *) connRefCon;
277 m = (midi_macosxcm_type) midi->descriptor;
280 /* synchronize time references every 100ms */
281 now = (*midi->time_proc)(midi->time_info);
282 if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
284 now = midi_synchronize(midi);
285 m->first_message = FALSE;
288 packet = (MIDIPacket *) &newPackets->packet[0];
289 /* printf("readproc packet status %x length %d\n", packet->data[0],
291 for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
292 /* Set the timestamp and dispatch this message */
293 event.timestamp = (PmTimestamp) /* explicit conversion */ (
294 (AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) /
296 status = packet->data[0];
297 /* process packet as sysex data if it begins with MIDI_SYSEX, or
298 MIDI_EOX or non-status byte with no running status */
300 printf(" %d", packet->length);
302 if (status == MIDI_SYSEX || status == MIDI_EOX ||
303 ((!(status & MIDI_STATUS_MASK)) && !m->last_command)) {
304 /* previously was: !(status & MIDI_STATUS_MASK)) {
305 * but this could mistake running status for sysex data
307 /* reset running status data -cpr */
309 m->last_msg_length = 0;
310 /* printf("sysex packet length: %d\n", packet->length); */
311 pm_read_bytes(midi, packet->data, packet->length, event.timestamp);
313 process_packet(packet, &event, midi, m);
315 packet = MIDIPacketNext(packet);
323 midi_in_open(PmInternal *midi, void *driverInfo)
325 MIDIEndpointRef endpoint;
326 midi_macosxcm_type m;
327 OSStatus macHostError;
329 /* insure that we have a time_proc for timing */
330 if (midi->time_proc == NULL) {
333 /* time_get does not take a parameter, so coerce */
334 midi->time_proc = (PmTimeProcPtr) Pt_Time;
336 endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
337 if (endpoint == NULL_REF) {
338 return pmInvalidDeviceId;
341 m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
342 midi->descriptor = m;
344 return pmInsufficientMemory;
347 m->callback_error[0] = 0;
351 m->first_message = TRUE;
352 m->sysex_mode = FALSE;
354 m->sysex_byte_count = 0;
355 m->packetList = NULL;
358 m->last_msg_length = 0;
360 macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
361 if (macHostError != noErr) {
362 pm_hosterror = macHostError;
363 sprintf(pm_hosterror_text,
364 "Host error %ld: MIDIPortConnectSource() in midi_in_open()",
365 (long) macHostError);
366 midi->descriptor = NULL;
375 midi_in_close(PmInternal *midi)
377 MIDIEndpointRef endpoint;
378 OSStatus macHostError;
379 PmError err = pmNoError;
381 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
383 if (!m) return pmBadPtr;
385 endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
386 if (endpoint == NULL_REF) {
387 pm_hosterror = pmBadPtr;
390 /* shut off the incoming messages before freeing data structures */
391 macHostError = MIDIPortDisconnectSource(portIn, endpoint);
392 if (macHostError != noErr) {
393 pm_hosterror = macHostError;
394 sprintf(pm_hosterror_text,
395 "Host error %ld: MIDIPortDisconnectSource() in midi_in_close()",
396 (long) macHostError);
400 midi->descriptor = NULL;
401 pm_free(midi->descriptor);
408 midi_out_open(PmInternal *midi, void *driverInfo)
410 midi_macosxcm_type m;
412 m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
413 midi->descriptor = m;
415 return pmInsufficientMemory;
418 m->callback_error[0] = 0;
422 m->first_message = TRUE;
423 m->sysex_mode = FALSE;
425 m->sysex_byte_count = 0;
426 m->packetList = (MIDIPacketList *) m->packetBuffer;
429 m->last_msg_length = 0;
430 m->min_next_time = 0;
432 m->us_per_host_tick = 1000000.0 / AudioGetHostClockFrequency();
433 m->host_ticks_per_byte = (UInt64) (1000000.0 /
434 (m->us_per_host_tick * MAX_BYTES_PER_S));
440 midi_out_close(PmInternal *midi)
442 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
443 if (!m) return pmBadPtr;
445 midi->descriptor = NULL;
446 pm_free(midi->descriptor);
452 midi_abort(PmInternal *midi)
454 PmError err = pmNoError;
455 OSStatus macHostError;
456 MIDIEndpointRef endpoint =
457 (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
458 macHostError = MIDIFlushOutput(endpoint);
459 if (macHostError != noErr) {
460 pm_hosterror = macHostError;
461 sprintf(pm_hosterror_text,
462 "Host error %ld: MIDIFlushOutput()", (long) macHostError);
470 midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
472 OSStatus macHostError;
473 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
474 MIDIEndpointRef endpoint =
475 (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
478 if (m->packet != NULL) {
479 /* out of space, send the buffer and start refilling it */
480 /* before we can send, maybe delay to limit data rate. OS X allows
482 UInt64 now = AudioGetCurrentHostTime();
483 if (now < m->min_next_time) {
485 ((m->min_next_time - now) * m->us_per_host_tick));
487 macHostError = MIDISend(portOut, endpoint, m->packetList);
488 m->packet = NULL; /* indicate no data in packetList now */
489 m->min_next_time = now + m->byte_count * m->host_ticks_per_byte;
491 if (macHostError != noErr) goto send_packet_error;
496 pm_hosterror = macHostError;
497 sprintf(pm_hosterror_text,
498 "Host error %ld: MIDISend() in midi_write()",
499 (long) macHostError);
506 send_packet(PmInternal *midi, Byte *message, unsigned int messageLength,
507 MIDITimeStamp timestamp)
510 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
513 /* printf("add %d to packet %p len %d\n", message[0], m->packet, messageLength); */
514 m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
515 m->packet, timestamp, messageLength,
517 m->byte_count += messageLength;
518 if (m->packet == NULL) {
519 /* out of space, send the buffer and start refilling it */
520 /* make midi->packet non-null to fool midi_write_flush into sending */
521 m->packet = (MIDIPacket *) 4;
522 /* timestamp is 0 because midi_write_flush ignores timestamp since
523 * timestamps are already in packets. The timestamp parameter is here
524 * because other API's need it. midi_write_flush can be called
525 * from system-independent code that must be cross-API.
527 if ((err = midi_write_flush(midi, 0)) != pmNoError) return err;
528 m->packet = MIDIPacketListInit(m->packetList);
529 assert(m->packet); /* if this fails, it's a programming error */
530 m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
531 m->packet, timestamp, messageLength,
533 assert(m->packet); /* can't run out of space on first message */
540 midi_write_short(PmInternal *midi, PmEvent *event)
542 PmTimestamp when = event->timestamp;
543 PmMessage what = event->message;
544 MIDITimeStamp timestamp;
546 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
548 unsigned int messageLength;
550 if (m->packet == NULL) {
551 m->packet = MIDIPacketListInit(m->packetList);
552 /* this can never fail, right? failure would indicate something
557 /* compute timestamp */
558 if (when == 0) when = midi->now;
559 /* if latency == 0, midi->now is not valid. We will just set it to zero */
560 if (midi->latency == 0) when = 0;
561 when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
562 timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
564 message[0] = Pm_MessageStatus(what);
565 message[1] = Pm_MessageData1(what);
566 message[2] = Pm_MessageData2(what);
567 messageLength = midi_length(what);
569 /* make sure we go foreward in time */
570 if (timestamp < m->min_next_time) timestamp = m->min_next_time;
573 if (timestamp < m->last_time)
574 timestamp = m->last_time;
575 m->last_time = timestamp + messageLength * m->host_ticks_per_byte;
578 /* Add this message to the packet list */
579 return send_packet(midi, message, messageLength, timestamp);
584 midi_begin_sysex(PmInternal *midi, PmTimestamp when)
587 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
589 m->sysex_byte_count = 0;
591 /* compute timestamp */
592 if (when == 0) when = midi->now;
593 /* if latency == 0, midi->now is not valid. We will just set it to zero */
594 if (midi->latency == 0) when = 0;
595 when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
596 m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
598 if (m->packet == NULL) {
599 m->packet = MIDIPacketListInit(m->packetList);
600 /* this can never fail, right? failure would indicate something
609 midi_end_sysex(PmInternal *midi, PmTimestamp when)
612 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
615 /* make sure we go foreward in time */
616 if (m->sysex_timestamp < m->min_next_time)
617 m->sysex_timestamp = m->min_next_time;
620 if (m->sysex_timestamp < m->last_time)
621 m->sysex_timestamp = m->last_time;
622 m->last_time = m->sysex_timestamp + m->sysex_byte_count *
623 m->host_ticks_per_byte;
626 /* now send what's in the buffer */
627 err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
629 m->sysex_byte_count = 0;
630 if (err != pmNoError) {
631 m->packet = NULL; /* flush everything in the packet list */
639 midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
641 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
643 if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
644 PmError err = midi_end_sysex(midi, timestamp);
645 if (err != pmNoError) return err;
647 m->sysex_buffer[m->sysex_byte_count++] = byte;
653 midi_write_realtime(PmInternal *midi, PmEvent *event)
655 /* to send a realtime message during a sysex message, first
656 flush all pending sysex bytes into packet list */
657 PmError err = midi_end_sysex(midi, 0);
658 if (err != pmNoError) return err;
659 /* then we can just do a normal midi_write_short */
660 return midi_write_short(midi, event);
663 static unsigned int midi_has_host_error(PmInternal *midi)
665 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
666 return (m->callback_error[0] != 0) || (m->error[0] != 0);
670 static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)
672 midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
673 msg[0] = 0; /* initialize to empty string */
674 if (m) { /* make sure there is an open device to examine */
676 strncpy(msg, m->error, len);
677 m->error[0] = 0; /* clear the error */
678 } else if (m->callback_error[0]) {
679 strncpy(msg, m->callback_error, len);
680 m->callback_error[0] = 0; /* clear the error */
682 msg[len - 1] = 0; /* make sure string is terminated */
687 MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
690 if (timestamp <= 0) {
691 return (MIDITimeStamp)0;
693 nanos = (UInt64)timestamp * (UInt64)1000000;
694 return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
698 PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
701 nanos = AudioConvertHostTimeToNanos(timestamp);
702 return (PmTimestamp)(nanos / (UInt64)1000000);
707 // Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
708 //////////////////////////////////////
709 // Obtain the name of an endpoint without regard for whether it has connections.
710 // The result should be released by the caller.
711 CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
713 CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
716 // begin with the endpoint's name
718 MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
720 CFStringAppend(result, str);
724 MIDIEntityRef entity = NULL_REF;
725 MIDIEndpointGetEntity(endpoint, &entity);
726 if (entity == NULL_REF)
730 if (CFStringGetLength(result) == 0) {
731 // endpoint name has zero length -- try the entity
733 MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
735 CFStringAppend(result, str);
739 // now consider the device's name
740 MIDIDeviceRef device = NULL_REF;
741 MIDIEntityGetDevice(entity, &device);
742 if (device == NULL_REF)
746 MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
747 if (CFStringGetLength(result) == 0) {
752 // if an external device has only one entity, throw away
753 // the endpoint name and just use the device name
754 if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
758 if (CFStringGetLength(str) == 0) {
762 // does the entity name already start with the device name?
763 // (some drivers do this though they shouldn't)
764 // if so, do not prepend
765 if (CFStringCompareWithOptions( result, /* endpoint name */
766 str /* device name */,
767 CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) {
768 // prepend the device name to the entity name
769 if (CFStringGetLength(result) > 0)
770 CFStringInsert(result, 0, CFSTR(" "));
771 CFStringInsert(result, 0, str);
780 // Obtain the name of an endpoint, following connections.
781 // The result should be released by the caller.
782 static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)
784 CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
789 // Does the endpoint have connections?
790 CFDataRef connections = NULL;
792 bool anyStrings = false;
793 err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);
794 if (connections != NULL) {
795 // It has connections, follow them
796 // Concatenate the names of all connected devices
797 nConnected = CFDataGetLength(connections) / (int32_t) sizeof(MIDIUniqueID);
799 const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
800 for (i = 0; i < nConnected; ++i, ++pid) {
801 MIDIUniqueID id = EndianS32_BtoN(*pid);
802 MIDIObjectRef connObject;
803 MIDIObjectType connObjectType;
804 err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);
806 if (connObjectType == kMIDIObjectType_ExternalSource ||
807 connObjectType == kMIDIObjectType_ExternalDestination) {
808 // Connected to an external device's endpoint (10.3 and later).
809 str = EndpointName((MIDIEndpointRef)(connObject), true);
811 // Connected to an external device (10.2) (or something else, catch-all)
813 MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);
817 CFStringAppend(result, CFSTR(", "));
818 else anyStrings = true;
819 CFStringAppend(result, str);
825 CFRelease(connections);
830 // Here, either the endpoint had no connections, or we failed to obtain names for any of them.
831 return EndpointName(endpoint, false);
835 char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
838 MIDIEntityRef entity;
839 MIDIDeviceRef device;
841 CFStringRef endpointName = NULL;
842 CFStringRef deviceName = NULL;
844 CFStringRef fullName = NULL;
845 CFStringEncoding defaultEncoding;
848 /* get the default string encoding */
849 defaultEncoding = CFStringGetSystemEncoding();
851 fullName = ConnectedEndpointName(endpoint);
854 /* get the entity and device info */
855 MIDIEndpointGetEntity(endpoint, &entity);
856 MIDIEntityGetDevice(entity, &device);
858 /* create the nicely formated name */
859 MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);
860 MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);
861 if (deviceName != NULL) {
862 fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),
863 deviceName, endpointName);
865 fullName = endpointName;
868 /* copy the string into our buffer */
869 newName = (char *) malloc(CFStringGetLength(fullName) + 1);
870 CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
875 if (endpointName) CFRelease(endpointName);
876 if (deviceName) CFRelease(deviceName);
878 if (fullName) CFRelease(fullName);
885 pm_fns_node pm_macosx_in_dictionary = {
901 pm_fns_node pm_macosx_out_dictionary = {
918 PmError pm_macosxcm_init(void)
920 ItemCount numInputs, numOutputs, numDevices;
921 MIDIEndpointRef endpoint;
923 OSStatus macHostError;
926 /* Determine the number of MIDI devices on the system */
927 numDevices = MIDIGetNumberOfDevices();
928 numInputs = MIDIGetNumberOfSources();
929 numOutputs = MIDIGetNumberOfDestinations();
931 /* Return prematurely if no devices exist on the system
932 Note that this is not an error. There may be no devices.
933 Pm_CountDevices() will return zero, which is correct and
936 if (numDevices <= 0) {
941 /* Initialize the client handle */
942 macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);
943 if (macHostError != noErr) {
944 error_text = "MIDIClientCreate() in pm_macosxcm_init()";
948 /* Create the input port */
949 macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
951 if (macHostError != noErr) {
952 error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
956 /* Create the output port */
957 macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
958 if (macHostError != noErr) {
959 error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
963 /* Iterate over the MIDI input devices */
964 for (i = 0; i < numInputs; i++) {
965 endpoint = MIDIGetSource(i);
966 if (endpoint == NULL_REF) {
970 /* set the first input we see to the default */
971 if (pm_default_input_device_id == -1)
972 pm_default_input_device_id = pm_descriptor_index;
974 /* Register this device with PortMidi */
975 pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
976 TRUE, (void *) (long) endpoint, &pm_macosx_in_dictionary);
979 /* Iterate over the MIDI output devices */
980 for (i = 0; i < numOutputs; i++) {
981 endpoint = MIDIGetDestination(i);
982 if (endpoint == NULL_REF) {
986 /* set the first output we see to the default */
987 if (pm_default_output_device_id == -1)
988 pm_default_output_device_id = pm_descriptor_index;
990 /* Register this device with PortMidi */
991 pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
992 FALSE, (void *) (long) endpoint,
993 &pm_macosx_out_dictionary);
998 pm_hosterror = macHostError;
999 sprintf(pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError,
1001 pm_macosxcm_term(); /* clear out any opened ports */
1005 void pm_macosxcm_term(void)
1007 if (client != NULL_REF) MIDIClientDispose(client);
1008 if (portIn != NULL_REF) MIDIPortDispose(portIn);
1009 if (portOut != NULL_REF) MIDIPortDispose(portOut);