new audio engine backend for native CoreAudio audio I/O, and PortMIDI for MIDI.
[ardour.git] / libs / backends / wavesaudio / portmidi / src / pm_mac / pmmacosxcm.c
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     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.
19     
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.
22  */
23
24 #include <stdlib.h>
25
26 //#define CM_DEBUG 1
27
28 #include "portmidi.h"
29 #include "pmutil.h"
30 #include "pminternal.h"
31 #include "porttime.h"
32 #include "pmmac.h"
33 #include "pmmacosxcm.h"
34
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <CoreServices/CoreServices.h>
39 #include <CoreMIDI/MIDIServices.h>
40 #include <CoreAudio/HostTime.h>
41 #include <unistd.h>
42
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
46
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." 
51
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
55    solution).
56
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
61
62      This is the minimum timestamp allowed in the next packet. 
63
64      Note that this distorts accurate timestamps somewhat.
65  */
66 #define LIMIT_RATE 1
67
68 #define SYSEX_BUFFER_SIZE 128
69
70 #define VERBOSE_ON 1
71 #define VERBOSE if (VERBOSE_ON)
72
73 #define MIDI_SYSEX      0xf0
74 #define MIDI_EOX        0xf7
75 #define MIDI_STATUS_MASK 0x80
76
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
79 #ifdef __LP64__
80 #define NULL_REF 0
81 #else
82 #define NULL_REF NULL
83 #endif
84
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 */
88
89 extern pm_fns_node pm_macosx_in_dictionary;
90 extern pm_fns_node pm_macosx_out_dictionary;
91
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 */
104     MIDIPacket *packet;
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;
116
117 /* private function declarations */
118 MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
119 PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
120
121 char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
122
123
124 static int
125 midi_length(int32_t msg)
126 {
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 */
131     };
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 */
135     };
136
137     status = msg & 0xFF;
138     high = status >> 4;
139     low = status & 15;
140
141     return (high != 0xF) ? high_lengths[high] : low_lengths[low];
142 }
143
144 static PmTimestamp midi_synchronize(PmInternal *midi)
145 {
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));
155     do {
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;
164     return real_time;
165 }
166
167
168 static void
169 process_packet(MIDIPacket *packet, PmEvent *event, 
170                PmInternal *midi, midi_macosxcm_type m)
171 {
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, 
183                                              remaining_length, 
184                                              event->timestamp);
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;
191             m->last_command = 0;
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) {
196 #ifdef DEBUG
197                 printf("PortMidi debug msg: not enough data");
198 #endif
199                 /* since there's no more data, we're done */
200                 return;
201             }
202             m->last_msg_length = cur_message_length;
203             m->last_command = cur_packet_data[0];
204             switch (cur_message_length) {
205             case 1:
206                 event->message = Pm_Message(cur_packet_data[0], 0, 0);
207                 break; 
208             case 2:
209                 event->message = Pm_Message(cur_packet_data[0], 
210                                             cur_packet_data[1], 0);
211                 break;
212             case 3:
213                 event->message = Pm_Message(cur_packet_data[0],
214                                             cur_packet_data[1], 
215                                             cur_packet_data[2]);
216                 break;
217             default:
218                 /* PortMIDI internal error; should never happen */
219                 assert(cur_message_length == 1);
220                 return; /* give up on packet if continued after assert */
221             }
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 */
227 #ifdef DEBUG
228             printf("PortMidi debug msg: not enough data in CoreMIDI packet");
229 #endif
230             /* since there's no more data, we're done */
231             return;
232         } else { /* output message using running status */
233             switch (m->last_msg_length) {
234             case 1:
235                 event->message = Pm_Message(m->last_command, 0, 0);
236                 break;
237             case 2:
238                 event->message = Pm_Message(m->last_command, 
239                                             cur_packet_data[0], 0);
240                 break;
241             case 3:
242                 event->message = Pm_Message(m->last_command, 
243                                             cur_packet_data[0], 
244                                             cur_packet_data[1]);
245                 break;
246             default:
247                 /* last_msg_length is invalid -- internal PortMIDI error */
248                 assert(m->last_msg_length == 1);
249             }
250             pm_read_short(midi, event);
251             remaining_length -= (m->last_msg_length - 1);
252             cur_packet_data += (m->last_msg_length - 1);
253         }
254     }
255 }
256
257
258
259 /* called when MIDI packets are received */
260 static void
261 readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
262 {
263     PmInternal *midi;
264     midi_macosxcm_type m;
265     PmEvent event;
266     MIDIPacket *packet;
267     unsigned int packetIndex;
268     uint32_t now;
269     unsigned int status;
270     
271 #ifdef CM_DEBUG
272     printf("readProc: numPackets %d: ", newPackets->numPackets);
273 #endif
274
275     /* Retrieve the context for this connection */
276     midi = (PmInternal *) connRefCon;
277     m = (midi_macosxcm_type) midi->descriptor;
278     assert(m);
279     
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) { 
283         /* time to resync */
284         now = midi_synchronize(midi);
285         m->first_message = FALSE;
286     }
287     
288     packet = (MIDIPacket *) &newPackets->packet[0];
289     /* printf("readproc packet status %x length %d\n", packet->data[0], 
290                packet->length); */
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) / 
295                 (UInt64) 1000000);
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 */
299 #ifdef CM_DEBUG
300         printf(" %d", packet->length);
301 #endif
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
306              */
307             /* reset running status data -cpr */
308             m->last_command = 0;
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);
312         } else {
313             process_packet(packet, &event, midi, m);
314         }
315         packet = MIDIPacketNext(packet);
316     }
317 #ifdef CM_DEBUG
318     printf("\n");
319 #endif
320 }
321
322 static PmError
323 midi_in_open(PmInternal *midi, void *driverInfo)
324 {
325     MIDIEndpointRef endpoint;
326     midi_macosxcm_type m;
327     OSStatus macHostError;
328     
329     /* insure that we have a time_proc for timing */
330     if (midi->time_proc == NULL) {
331         if (!Pt_Started()) 
332             Pt_Start(1, 0, 0);
333         /* time_get does not take a parameter, so coerce */
334         midi->time_proc = (PmTimeProcPtr) Pt_Time;
335     }
336     endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
337     if (endpoint == NULL_REF) {
338         return pmInvalidDeviceId;
339     }
340
341     m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
342     midi->descriptor = m;
343     if (!m) {
344         return pmInsufficientMemory;
345     }
346     m->error[0] = 0;
347     m->callback_error[0] = 0;
348     m->sync_time = 0;
349     m->delta = 0;
350     m->last_time = 0;
351     m->first_message = TRUE;
352     m->sysex_mode = FALSE;
353     m->sysex_word = 0;
354     m->sysex_byte_count = 0;
355     m->packetList = NULL;
356     m->packet = NULL;
357     m->last_command = 0;
358     m->last_msg_length = 0;
359
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;
367         pm_free(m);
368         return pmHostError;
369     }
370     
371     return pmNoError;
372 }
373
374 static PmError
375 midi_in_close(PmInternal *midi)
376 {
377     MIDIEndpointRef endpoint;
378     OSStatus macHostError;
379     PmError err = pmNoError;
380     
381     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
382     
383     if (!m) return pmBadPtr;
384
385     endpoint = (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
386     if (endpoint == NULL_REF) {
387         pm_hosterror = pmBadPtr;
388     }
389     
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);
397         err = pmHostError;
398     }
399     
400     midi->descriptor = NULL;
401     pm_free(midi->descriptor);
402     
403     return err;
404 }
405
406
407 static PmError
408 midi_out_open(PmInternal *midi, void *driverInfo)
409 {
410     midi_macosxcm_type m;
411
412     m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
413     midi->descriptor = m;
414     if (!m) {
415         return pmInsufficientMemory;
416     }
417     m->error[0] = 0;
418     m->callback_error[0] = 0;
419     m->sync_time = 0;
420     m->delta = 0;
421     m->last_time = 0;
422     m->first_message = TRUE;
423     m->sysex_mode = FALSE;
424     m->sysex_word = 0;
425     m->sysex_byte_count = 0;
426     m->packetList = (MIDIPacketList *) m->packetBuffer;
427     m->packet = NULL;
428     m->last_command = 0;
429     m->last_msg_length = 0;
430     m->min_next_time = 0;
431     m->byte_count = 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));
435     return pmNoError;
436 }
437
438
439 static PmError
440 midi_out_close(PmInternal *midi)
441 {
442     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
443     if (!m) return pmBadPtr;
444     
445     midi->descriptor = NULL;
446     pm_free(midi->descriptor);
447     
448     return pmNoError;
449 }
450
451 static PmError
452 midi_abort(PmInternal *midi)
453 {
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);
463         err = pmHostError;
464     }
465     return err;
466 }
467
468
469 static PmError
470 midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
471 {
472     OSStatus macHostError;
473     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
474     MIDIEndpointRef endpoint = 
475             (MIDIEndpointRef) (long) descriptors[midi->device_id].descriptor;
476     assert(m);
477     assert(endpoint);
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
481          * 15KB/s. */
482         UInt64 now = AudioGetCurrentHostTime();
483         if (now < m->min_next_time) {
484             usleep((useconds_t) 
485                    ((m->min_next_time - now) * m->us_per_host_tick));
486         }
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;
490         m->byte_count = 0;
491         if (macHostError != noErr) goto send_packet_error;
492     }
493     return pmNoError;
494     
495 send_packet_error:
496     pm_hosterror = macHostError;
497     sprintf(pm_hosterror_text, 
498             "Host error %ld: MIDISend() in midi_write()",
499             (long) macHostError);
500     return pmHostError;
501
502 }
503
504
505 static PmError
506 send_packet(PmInternal *midi, Byte *message, unsigned int messageLength, 
507             MIDITimeStamp timestamp)
508 {
509     PmError err;
510     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
511     assert(m);
512     
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, 
516                                   message);
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.
526          */
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, 
532                                       message);
533         assert(m->packet); /* can't run out of space on first message */           
534     }
535     return pmNoError;
536 }    
537
538
539 static PmError
540 midi_write_short(PmInternal *midi, PmEvent *event)
541 {
542     PmTimestamp when = event->timestamp;
543     PmMessage what = event->message;
544     MIDITimeStamp timestamp;
545     UInt64 when_ns;
546     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
547     Byte message[4];
548     unsigned int messageLength;
549
550     if (m->packet == NULL) {
551         m->packet = MIDIPacketListInit(m->packetList);
552         /* this can never fail, right? failure would indicate something 
553            unrecoverable */
554         assert(m->packet);
555     }
556     
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);
563
564     message[0] = Pm_MessageStatus(what);
565     message[1] = Pm_MessageData1(what);
566     message[2] = Pm_MessageData2(what);
567     messageLength = midi_length(what);
568         
569     /* make sure we go foreward in time */
570     if (timestamp < m->min_next_time) timestamp = m->min_next_time;
571
572     #ifdef LIMIT_RATE
573         if (timestamp < m->last_time)
574             timestamp = m->last_time;
575         m->last_time = timestamp + messageLength * m->host_ticks_per_byte;
576     #endif
577
578     /* Add this message to the packet list */
579     return send_packet(midi, message, messageLength, timestamp);
580 }
581
582
583 static PmError 
584 midi_begin_sysex(PmInternal *midi, PmTimestamp when)
585 {
586     UInt64 when_ns;
587     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
588     assert(m);
589     m->sysex_byte_count = 0;
590     
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);
597
598     if (m->packet == NULL) {
599         m->packet = MIDIPacketListInit(m->packetList);
600         /* this can never fail, right? failure would indicate something 
601            unrecoverable */
602         assert(m->packet);
603     }
604     return pmNoError;
605 }
606
607
608 static PmError
609 midi_end_sysex(PmInternal *midi, PmTimestamp when)
610 {
611     PmError err;
612     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
613     assert(m);
614     
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;
618
619     #ifdef LIMIT_RATE
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;
624     #endif
625     
626     /* now send what's in the buffer */
627     err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
628                       m->sysex_timestamp);
629     m->sysex_byte_count = 0;
630     if (err != pmNoError) {
631         m->packet = NULL; /* flush everything in the packet list */
632         return err;
633     }
634     return pmNoError;
635 }
636
637
638 static PmError
639 midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
640 {
641     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
642     assert(m);
643     if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
644         PmError err = midi_end_sysex(midi, timestamp);
645         if (err != pmNoError) return err;
646     }
647     m->sysex_buffer[m->sysex_byte_count++] = byte;
648     return pmNoError;
649 }
650
651
652 static PmError
653 midi_write_realtime(PmInternal *midi, PmEvent *event)
654 {
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);
661 }
662
663 static unsigned int midi_has_host_error(PmInternal *midi)
664 {
665     midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
666     return (m->callback_error[0] != 0) || (m->error[0] != 0);
667 }
668
669
670 static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)
671 {
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 */
675         if (m->error[0]) {
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 */
681         }
682         msg[len - 1] = 0; /* make sure string is terminated */
683     }
684 }
685
686
687 MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
688 {
689     UInt64 nanos;
690     if (timestamp <= 0) {
691         return (MIDITimeStamp)0;
692     } else {
693         nanos = (UInt64)timestamp * (UInt64)1000000;
694         return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
695     }
696 }
697
698 PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
699 {
700     UInt64 nanos;
701     nanos = AudioConvertHostTimeToNanos(timestamp);
702     return (PmTimestamp)(nanos / (UInt64)1000000);
703 }
704
705
706 //
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)
712 {
713   CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
714   CFStringRef str;
715   
716   // begin with the endpoint's name
717   str = NULL;
718   MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
719   if (str != NULL) {
720     CFStringAppend(result, str);
721     CFRelease(str);
722   }
723   
724   MIDIEntityRef entity = NULL_REF;
725   MIDIEndpointGetEntity(endpoint, &entity);
726   if (entity == NULL_REF)
727     // probably virtual
728     return result;
729   
730   if (CFStringGetLength(result) == 0) {
731     // endpoint name has zero length -- try the entity
732     str = NULL;
733     MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
734     if (str != NULL) {
735       CFStringAppend(result, str);
736       CFRelease(str);
737     }
738   }
739   // now consider the device's name
740   MIDIDeviceRef device = NULL_REF;
741   MIDIEntityGetDevice(entity, &device);
742   if (device == NULL_REF)
743     return result;
744   
745   str = NULL;
746   MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
747   if (CFStringGetLength(result) == 0) {
748       CFRelease(result);
749       return str;
750   }
751   if (str != NULL) {
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) {
755       CFRelease(result);
756       return str;
757     } else {
758       if (CFStringGetLength(str) == 0) {
759         CFRelease(str);
760         return result;
761       }
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);
772       }
773       CFRelease(str);
774     }
775   }
776   return result;
777 }
778
779
780 // Obtain the name of an endpoint, following connections.
781 // The result should be released by the caller.
782 static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)
783 {
784   CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
785   CFStringRef str;
786   OSStatus err;
787   long i;
788   
789   // Does the endpoint have connections?
790   CFDataRef connections = NULL;
791   long nConnected = 0;
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);
798     if (nConnected) {
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);
805         if (err == noErr) {
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);
810           } else {
811             // Connected to an external device (10.2) (or something else, catch-all)
812             str = NULL;
813             MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);
814           }
815           if (str != NULL) {
816             if (anyStrings)
817               CFStringAppend(result, CFSTR(", "));
818             else anyStrings = true;
819             CFStringAppend(result, str);
820             CFRelease(str);
821           }
822         }
823       }
824     }
825     CFRelease(connections);
826   }
827   if (anyStrings)
828     return result;
829   
830   // Here, either the endpoint had no connections, or we failed to obtain names for any of them.
831   return EndpointName(endpoint, false);
832 }
833
834
835 char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
836 {
837 #ifdef OLDCODE
838     MIDIEntityRef entity;
839     MIDIDeviceRef device;
840
841     CFStringRef endpointName = NULL;
842     CFStringRef deviceName = NULL;
843 #endif
844     CFStringRef fullName = NULL;
845     CFStringEncoding defaultEncoding;
846     char* newName;
847
848     /* get the default string encoding */
849     defaultEncoding = CFStringGetSystemEncoding();
850
851     fullName = ConnectedEndpointName(endpoint);
852     
853 #ifdef OLDCODE
854     /* get the entity and device info */
855     MIDIEndpointGetEntity(endpoint, &entity);
856     MIDIEntityGetDevice(entity, &device);
857
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);
864     } else {
865         fullName = endpointName;
866     }
867 #endif    
868     /* copy the string into our buffer */
869     newName = (char *) malloc(CFStringGetLength(fullName) + 1);
870     CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
871                        defaultEncoding);
872
873     /* clean up */
874 #ifdef OLDCODE
875     if (endpointName) CFRelease(endpointName);
876     if (deviceName) CFRelease(deviceName);
877 #endif
878     if (fullName) CFRelease(fullName);
879
880     return newName;
881 }
882
883  
884
885 pm_fns_node pm_macosx_in_dictionary = {
886     none_write_short,
887     none_sysex,
888     none_sysex,
889     none_write_byte,
890     none_write_short,
891     none_write_flush,
892     none_synchronize,
893     midi_in_open,
894     midi_abort,
895     midi_in_close,
896     success_poll,
897     midi_has_host_error,
898     midi_get_host_error,
899 };
900
901 pm_fns_node pm_macosx_out_dictionary = {
902     midi_write_short,
903     midi_begin_sysex,
904     midi_end_sysex,
905     midi_write_byte,
906     midi_write_realtime,
907     midi_write_flush,
908     midi_synchronize,
909     midi_out_open,
910     midi_abort,
911     midi_out_close,
912     success_poll,
913     midi_has_host_error,
914     midi_get_host_error,
915 };
916
917
918 PmError pm_macosxcm_init(void)
919 {
920     ItemCount numInputs, numOutputs, numDevices;
921     MIDIEndpointRef endpoint;
922     int i;
923     OSStatus macHostError;
924     char *error_text;
925
926     /* Determine the number of MIDI devices on the system */
927     numDevices = MIDIGetNumberOfDevices();
928     numInputs = MIDIGetNumberOfSources();
929     numOutputs = MIDIGetNumberOfDestinations();
930
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
934        useful information
935      */
936     if (numDevices <= 0) {
937         return pmNoError;
938     }
939
940
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()";
945         goto error_return;
946     }
947
948     /* Create the input port */
949     macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
950                                           NULL, &portIn);
951     if (macHostError != noErr) {
952         error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
953         goto error_return;
954     }
955         
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()";
960         goto error_return;
961     }
962
963     /* Iterate over the MIDI input devices */
964     for (i = 0; i < numInputs; i++) {
965         endpoint = MIDIGetSource(i);
966         if (endpoint == NULL_REF) {
967             continue;
968         }
969
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;
973         
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);
977     }
978
979     /* Iterate over the MIDI output devices */
980     for (i = 0; i < numOutputs; i++) {
981         endpoint = MIDIGetDestination(i);
982         if (endpoint == NULL_REF) {
983             continue;
984         }
985
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;
989
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);
994     }
995     return pmNoError;
996     
997 error_return:
998     pm_hosterror = macHostError;
999     sprintf(pm_hosterror_text, "Host error %ld: %s\n", (long) macHostError, 
1000             error_text);
1001     pm_macosxcm_term(); /* clear out any opened ports */
1002     return pmHostError;
1003 }
1004
1005 void pm_macosxcm_term(void)
1006 {
1007     if (client != NULL_REF) MIDIClientDispose(client);
1008     if (portIn != NULL_REF) MIDIPortDispose(portIn);
1009     if (portOut != NULL_REF) MIDIPortDispose(portOut);
1010 }