gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

pmmacosxcm.c (32921B)


      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 }