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 }