1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
|
/*
* Platform interface to the MacOS X CoreMIDI framework
*
* Jon Parise <jparise at cmu.edu>
* and subsequent work by Andrew Zeldis and Zico Kolter
* and Roger B. Dannenberg
*
* $Id: pmmacosx.c,v 1.17 2002/01/27 02:40:40 jon Exp $
*/
/* Notes:
since the input and output streams are represented by MIDIEndpointRef
values and almost no other state, we store the MIDIEndpointRef on
descriptors[midi->device_id].descriptor. The only other state we need
is for errors: we need to know if there is an error and if so, what is
the error text. We use a structure with two kinds of
host error: "error" and "callback_error". That way, asynchronous callbacks
do not interfere with other error information.
OS X does not seem to have an error-code-to-text function, so we will
just use text messages instead of error codes.
*/
#include <stdlib.h>
//#define CM_DEBUG 1
#include "portmidi.h"
#include "pmutil.h"
#include "pminternal.h"
#include "porttime.h"
#include "pmmac.h"
#include "pmmacosxcm.h"
#include <stdio.h>
#include <string.h>
#include <CoreServices/CoreServices.h>
#include <CoreMIDI/MIDIServices.h>
#include <CoreAudio/HostTime.h>
#define PACKET_BUFFER_SIZE 1024
/* this is very strange: if I put in a reasonable
number here, e.g. 128, which would allow sysex data
to be sent 128 bytes at a time, then I lose sysex
data in my loopback test. With a buffer size of 4,
we put at most 4 bytes in a packet (but maybe many
packets in a packetList), and everything works fine.
*/
#define SYSEX_BUFFER_SIZE 4
#define VERBOSE_ON 1
#define VERBOSE if (VERBOSE_ON)
#define MIDI_SYSEX 0xf0
#define MIDI_EOX 0xf7
#define MIDI_STATUS_MASK 0x80
static MIDIClientRef client = NULL; /* Client handle to the MIDI server */
static MIDIPortRef portIn = NULL; /* Input port handle */
static MIDIPortRef portOut = NULL; /* Output port handle */
extern pm_fns_node pm_macosx_in_dictionary;
extern pm_fns_node pm_macosx_out_dictionary;
typedef struct midi_macosxcm_struct {
unsigned long sync_time; /* when did we last determine delta? */
UInt64 delta; /* difference between stream time and real time in ns */
UInt64 last_time; /* last output time */
int first_message; /* tells midi_write to sychronize timestamps */
int sysex_mode; /* middle of sending sysex */
unsigned long sysex_word; /* accumulate data when receiving sysex */
unsigned int sysex_byte_count; /* count how many received */
char error[PM_HOST_ERROR_MSG_LEN];
char callback_error[PM_HOST_ERROR_MSG_LEN];
Byte packetBuffer[PACKET_BUFFER_SIZE];
MIDIPacketList *packetList; /* a pointer to packetBuffer */
MIDIPacket *packet;
Byte sysex_buffer[SYSEX_BUFFER_SIZE]; /* temp storage for sysex data */
MIDITimeStamp sysex_timestamp; /* timestamp to use with sysex data */
/* allow for running status (is running status possible here? -rbd): -cpr */
unsigned char last_command;
long last_msg_length;
} midi_macosxcm_node, *midi_macosxcm_type;
/* private function declarations */
MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp);
PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp);
char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint);
static int
midi_length(long msg)
{
int status, high, low;
static int high_lengths[] = {
1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 through 0x70 */
3, 3, 3, 3, 2, 2, 3, 1 /* 0x80 through 0xf0 */
};
static int low_lengths[] = {
1, 2, 3, 2, 1, 1, 1, 1, /* 0xf0 through 0xf8 */
1, 1, 1, 1, 1, 1, 1, 1 /* 0xf9 through 0xff */
};
status = msg & 0xFF;
high = status >> 4;
low = status & 15;
return (high != 0xF) ? high_lengths[high] : low_lengths[low];
}
static PmTimestamp midi_synchronize(PmInternal *midi)
{
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
UInt64 pm_stream_time_2 =
AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
PmTimestamp real_time;
UInt64 pm_stream_time;
/* if latency is zero and this is an output, there is no
time reference and midi_synchronize should never be called */
assert(midi->time_proc);
assert(!(midi->write_flag && midi->latency == 0));
do {
/* read real_time between two reads of stream time */
pm_stream_time = pm_stream_time_2;
real_time = (*midi->time_proc)(midi->time_info);
pm_stream_time_2 = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
/* repeat if more than 0.5 ms has elapsed */
} while (pm_stream_time_2 > pm_stream_time + 500000);
m->delta = pm_stream_time - ((UInt64) real_time * (UInt64) 1000000);
m->sync_time = real_time;
return real_time;
}
static void
process_packet(MIDIPacket *packet, PmEvent *event,
PmInternal *midi, midi_macosxcm_type m)
{
/* handle a packet of MIDI messages from CoreMIDI */
/* there may be multiple short messages in one packet (!) */
unsigned int remaining_length = packet->length;
unsigned char *cur_packet_data = packet->data;
while (remaining_length > 0) {
if (cur_packet_data[0] == MIDI_SYSEX ||
/* are we in the middle of a sysex message? */
(m->last_command == 0 &&
!(cur_packet_data[0] & MIDI_STATUS_MASK))) {
m->last_command = 0; /* no running status */
unsigned int amt = pm_read_bytes(midi, cur_packet_data,
remaining_length,
event->timestamp);
remaining_length -= amt;
cur_packet_data += amt;
} else if (cur_packet_data[0] == MIDI_EOX) {
/* this should never happen, because pm_read_bytes should
* get and read all EOX bytes*/
midi->sysex_in_progress = FALSE;
m->last_command = 0;
} else if (cur_packet_data[0] & MIDI_STATUS_MASK) {
/* compute the length of the next (short) msg in packet */
unsigned int cur_message_length = midi_length(cur_packet_data[0]);
if (cur_message_length > remaining_length) {
#ifdef DEBUG
printf("PortMidi debug msg: not enough data");
#endif
/* since there's no more data, we're done */
return;
}
m->last_msg_length = cur_message_length;
m->last_command = cur_packet_data[0];
switch (cur_message_length) {
case 1:
event->message = Pm_Message(cur_packet_data[0], 0, 0);
break;
case 2:
event->message = Pm_Message(cur_packet_data[0],
cur_packet_data[1], 0);
break;
case 3:
event->message = Pm_Message(cur_packet_data[0],
cur_packet_data[1],
cur_packet_data[2]);
break;
default:
/* PortMIDI internal error; should never happen */
assert(cur_message_length == 1);
return; /* give up on packet if continued after assert */
}
pm_read_short(midi, event);
remaining_length -= m->last_msg_length;
cur_packet_data += m->last_msg_length;
} else if (m->last_msg_length > remaining_length + 1) {
/* we have running status, but not enough data */
#ifdef DEBUG
printf("PortMidi debug msg: not enough data in CoreMIDI packet");
#endif
/* since there's no more data, we're done */
return;
} else { /* output message using running status */
switch (m->last_msg_length) {
case 1:
event->message = Pm_Message(m->last_command, 0, 0);
break;
case 2:
event->message = Pm_Message(m->last_command,
cur_packet_data[0], 0);
break;
case 3:
event->message = Pm_Message(m->last_command,
cur_packet_data[0],
cur_packet_data[1]);
break;
default:
/* last_msg_length is invalid -- internal PortMIDI error */
assert(m->last_msg_length == 1);
}
pm_read_short(midi, event);
remaining_length -= (m->last_msg_length - 1);
cur_packet_data += (m->last_msg_length - 1);
}
}
}
/* called when MIDI packets are received */
static void
readProc(const MIDIPacketList *newPackets, void *refCon, void *connRefCon)
{
PmInternal *midi;
midi_macosxcm_type m;
PmEvent event;
MIDIPacket *packet;
unsigned int packetIndex;
unsigned long now;
unsigned int status;
#ifdef CM_DEBUG
printf("readProc: numPackets %d: ", newPackets->numPackets);
#endif
/* Retrieve the context for this connection */
midi = (PmInternal *) connRefCon;
m = (midi_macosxcm_type) midi->descriptor;
assert(m);
/* synchronize time references every 100ms */
now = (*midi->time_proc)(midi->time_info);
if (m->first_message || m->sync_time + 100 /*ms*/ < now) {
/* time to resync */
now = midi_synchronize(midi);
m->first_message = FALSE;
}
packet = (MIDIPacket *) &newPackets->packet[0];
/* printf("readproc packet status %x length %d\n", packet->data[0],
packet->length); */
for (packetIndex = 0; packetIndex < newPackets->numPackets; packetIndex++) {
/* Set the timestamp and dispatch this message */
event.timestamp =
(AudioConvertHostTimeToNanos(packet->timeStamp) - m->delta) /
(UInt64) 1000000;
status = packet->data[0];
/* process packet as sysex data if it begins with MIDI_SYSEX, or
MIDI_EOX or non-status byte with no running status */
#ifdef CM_DEBUG
printf(" %d", packet->length);
#endif
if (status == MIDI_SYSEX || status == MIDI_EOX ||
((!(status & MIDI_STATUS_MASK)) && !m->last_command)) {
/* previously was: !(status & MIDI_STATUS_MASK)) {
* but this could mistake running status for sysex data
*/
/* reset running status data -cpr */
m->last_command = 0;
m->last_msg_length = 0;
/* printf("sysex packet length: %d\n", packet->length); */
pm_read_bytes(midi, packet->data, packet->length, event.timestamp);
} else {
process_packet(packet, &event, midi, m);
}
packet = MIDIPacketNext(packet);
}
#ifdef CM_DEBUG
printf("\n");
#endif
}
static PmError
midi_in_open(PmInternal *midi, void *driverInfo)
{
MIDIEndpointRef endpoint;
midi_macosxcm_type m;
OSStatus macHostError;
/* insure that we have a time_proc for timing */
if (midi->time_proc == NULL) {
if (!Pt_Started())
Pt_Start(1, 0, 0);
/* time_get does not take a parameter, so coerce */
midi->time_proc = (PmTimeProcPtr) Pt_Time;
}
endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
if (!endpoint) {
return pmInvalidDeviceId;
}
m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
midi->descriptor = m;
if (!m) {
return pmInsufficientMemory;
}
m->error[0] = 0;
m->callback_error[0] = 0;
m->sync_time = 0;
m->delta = 0;
m->last_time = 0;
m->first_message = TRUE;
m->sysex_mode = FALSE;
m->sysex_word = 0;
m->sysex_byte_count = 0;
m->packetList = NULL;
m->packet = NULL;
m->last_command = 0;
m->last_msg_length = 0;
macHostError = MIDIPortConnectSource(portIn, endpoint, midi);
if (macHostError != noErr) {
pm_hosterror = macHostError;
sprintf(pm_hosterror_text,
"Host error %d: MIDIPortConnectSource() in midi_in_open()",
macHostError);
midi->descriptor = NULL;
pm_free(m);
return pmHostError;
}
return pmNoError;
}
static PmError
midi_in_close(PmInternal *midi)
{
MIDIEndpointRef endpoint;
OSStatus macHostError;
PmError err = pmNoError;
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
if (!m) return pmBadPtr;
endpoint = (MIDIEndpointRef) descriptors[midi->device_id].descriptor;
if (!endpoint) {
pm_hosterror = pmBadPtr;
}
/* shut off the incoming messages before freeing data structures */
macHostError = MIDIPortDisconnectSource(portIn, endpoint);
if (macHostError != noErr) {
pm_hosterror = macHostError;
sprintf(pm_hosterror_text,
"Host error %d: MIDIPortDisconnectSource() in midi_in_close()",
macHostError);
err = pmHostError;
}
midi->descriptor = NULL;
pm_free(midi->descriptor);
return err;
}
static PmError
midi_out_open(PmInternal *midi, void *driverInfo)
{
midi_macosxcm_type m;
m = (midi_macosxcm_type) pm_alloc(sizeof(midi_macosxcm_node)); /* create */
midi->descriptor = m;
if (!m) {
return pmInsufficientMemory;
}
m->error[0] = 0;
m->callback_error[0] = 0;
m->sync_time = 0;
m->delta = 0;
m->last_time = 0;
m->first_message = TRUE;
m->sysex_mode = FALSE;
m->sysex_word = 0;
m->sysex_byte_count = 0;
m->packetList = (MIDIPacketList *) m->packetBuffer;
m->packet = NULL;
m->last_command = 0;
m->last_msg_length = 0;
return pmNoError;
}
static PmError
midi_out_close(PmInternal *midi)
{
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
if (!m) return pmBadPtr;
midi->descriptor = NULL;
pm_free(midi->descriptor);
return pmNoError;
}
static PmError
midi_abort(PmInternal *midi)
{
return pmNoError;
}
static PmError
midi_write_flush(PmInternal *midi, PmTimestamp timestamp)
{
OSStatus macHostError;
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
MIDIEndpointRef endpoint =
(MIDIEndpointRef) descriptors[midi->device_id].descriptor;
assert(m);
assert(endpoint);
if (m->packet != NULL) {
/* out of space, send the buffer and start refilling it */
macHostError = MIDISend(portOut, endpoint, m->packetList);
m->packet = NULL; /* indicate no data in packetList now */
if (macHostError != noErr) goto send_packet_error;
}
return pmNoError;
send_packet_error:
pm_hosterror = macHostError;
sprintf(pm_hosterror_text,
"Host error %d: MIDISend() in midi_write()",
macHostError);
return pmHostError;
}
static PmError
send_packet(PmInternal *midi, Byte *message, unsigned int messageLength,
MIDITimeStamp timestamp)
{
PmError err;
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
assert(m);
/* printf("add %d to packet %lx len %d\n", message[0], m->packet, messageLength); */
m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
m->packet, timestamp, messageLength,
message);
if (m->packet == NULL) {
/* out of space, send the buffer and start refilling it */
/* make midi->packet non-null to fool midi_write_flush into sending */
m->packet = (MIDIPacket *) 4;
if ((err = midi_write_flush(midi, timestamp)) != pmNoError) return err;
m->packet = MIDIPacketListInit(m->packetList);
assert(m->packet); /* if this fails, it's a programming error */
m->packet = MIDIPacketListAdd(m->packetList, sizeof(m->packetBuffer),
m->packet, timestamp, messageLength,
message);
assert(m->packet); /* can't run out of space on first message */
}
return pmNoError;
}
static PmError
midi_write_short(PmInternal *midi, PmEvent *event)
{
long when = event->timestamp;
long what = event->message;
MIDITimeStamp timestamp;
UInt64 when_ns;
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
Byte message[4];
unsigned int messageLength;
if (m->packet == NULL) {
m->packet = MIDIPacketListInit(m->packetList);
/* this can never fail, right? failure would indicate something
unrecoverable */
assert(m->packet);
}
/* compute timestamp */
if (when == 0) when = midi->now;
/* if latency == 0, midi->now is not valid. We will just set it to zero */
if (midi->latency == 0) when = 0;
when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
/* make sure we don't go backward in time */
if (when_ns < m->last_time) when_ns = m->last_time;
m->last_time = when_ns;
timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
message[0] = Pm_MessageStatus(what);
message[1] = Pm_MessageData1(what);
message[2] = Pm_MessageData2(what);
messageLength = midi_length(what);
/* Add this message to the packet list */
return send_packet(midi, message, messageLength, timestamp);
}
static PmError
midi_begin_sysex(PmInternal *midi, PmTimestamp when)
{
UInt64 when_ns;
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
assert(m);
m->sysex_byte_count = 0;
/* compute timestamp */
if (when == 0) when = midi->now;
/* if latency == 0, midi->now is not valid. We will just set it to zero */
if (midi->latency == 0) when = 0;
when_ns = ((UInt64) (when + midi->latency) * (UInt64) 1000000) + m->delta;
m->sysex_timestamp = (MIDITimeStamp) AudioConvertNanosToHostTime(when_ns);
if (m->packet == NULL) {
m->packet = MIDIPacketListInit(m->packetList);
/* this can never fail, right? failure would indicate something
unrecoverable */
assert(m->packet);
}
return pmNoError;
}
static PmError
midi_end_sysex(PmInternal *midi, PmTimestamp when)
{
PmError err;
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
assert(m);
/* make sure we don't go backward in time */
if (m->sysex_timestamp < m->last_time) m->sysex_timestamp = m->last_time;
/* now send what's in the buffer */
err = send_packet(midi, m->sysex_buffer, m->sysex_byte_count,
m->sysex_timestamp);
m->sysex_byte_count = 0;
if (err != pmNoError) {
m->packet = NULL; /* flush everything in the packet list */
return err;
}
return pmNoError;
}
static PmError
midi_write_byte(PmInternal *midi, unsigned char byte, PmTimestamp timestamp)
{
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
assert(m);
if (m->sysex_byte_count >= SYSEX_BUFFER_SIZE) {
PmError err = midi_end_sysex(midi, timestamp);
if (err != pmNoError) return err;
}
m->sysex_buffer[m->sysex_byte_count++] = byte;
return pmNoError;
}
static PmError
midi_write_realtime(PmInternal *midi, PmEvent *event)
{
/* to send a realtime message during a sysex message, first
flush all pending sysex bytes into packet list */
PmError err = midi_end_sysex(midi, 0);
if (err != pmNoError) return err;
/* then we can just do a normal midi_write_short */
return midi_write_short(midi, event);
}
static unsigned int midi_has_host_error(PmInternal *midi)
{
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
return (m->callback_error[0] != 0) || (m->error[0] != 0);
}
static void midi_get_host_error(PmInternal *midi, char *msg, unsigned int len)
{
midi_macosxcm_type m = (midi_macosxcm_type) midi->descriptor;
msg[0] = 0; /* initialize to empty string */
if (m) { /* make sure there is an open device to examine */
if (m->error[0]) {
strncpy(msg, m->error, len);
m->error[0] = 0; /* clear the error */
} else if (m->callback_error[0]) {
strncpy(msg, m->callback_error, len);
m->callback_error[0] = 0; /* clear the error */
}
msg[len - 1] = 0; /* make sure string is terminated */
}
}
MIDITimeStamp timestamp_pm_to_cm(PmTimestamp timestamp)
{
UInt64 nanos;
if (timestamp <= 0) {
return (MIDITimeStamp)0;
} else {
nanos = (UInt64)timestamp * (UInt64)1000000;
return (MIDITimeStamp)AudioConvertNanosToHostTime(nanos);
}
}
PmTimestamp timestamp_cm_to_pm(MIDITimeStamp timestamp)
{
UInt64 nanos;
nanos = AudioConvertHostTimeToNanos(timestamp);
return (PmTimestamp)(nanos / (UInt64)1000000);
}
//
// Code taken from http://developer.apple.com/qa/qa2004/qa1374.html
//////////////////////////////////////
// Obtain the name of an endpoint without regard for whether it has connections.
// The result should be released by the caller.
CFStringRef EndpointName(MIDIEndpointRef endpoint, bool isExternal)
{
CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
CFStringRef str;
// begin with the endpoint's name
str = NULL;
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &str);
if (str != NULL) {
CFStringAppend(result, str);
CFRelease(str);
}
MIDIEntityRef entity = NULL;
MIDIEndpointGetEntity(endpoint, &entity);
if (!entity)
// probably virtual
return result;
if (CFStringGetLength(result) == 0) {
// endpoint name has zero length -- try the entity
str = NULL;
MIDIObjectGetStringProperty(entity, kMIDIPropertyName, &str);
if (str != NULL) {
CFStringAppend(result, str);
CFRelease(str);
}
}
// now consider the device's name
MIDIDeviceRef device = NULL;
MIDIEntityGetDevice(entity, &device);
if (!device)
return result;
str = NULL;
MIDIObjectGetStringProperty(device, kMIDIPropertyName, &str);
if (CFStringGetLength(result) == 0) {
CFRelease(result);
return str;
}
if (str != NULL) {
// if an external device has only one entity, throw away
// the endpoint name and just use the device name
if (isExternal && MIDIDeviceGetNumberOfEntities(device) < 2) {
CFRelease(result);
return str;
} else {
if (CFStringGetLength(str) == 0) {
CFRelease(str);
return result;
}
// does the entity name already start with the device name?
// (some drivers do this though they shouldn't)
// if so, do not prepend
if (CFStringCompareWithOptions( result, /* endpoint name */
str /* device name */,
CFRangeMake(0, CFStringGetLength(str)), 0) != kCFCompareEqualTo) {
// prepend the device name to the entity name
if (CFStringGetLength(result) > 0)
CFStringInsert(result, 0, CFSTR(" "));
CFStringInsert(result, 0, str);
}
CFRelease(str);
}
}
return result;
}
// Obtain the name of an endpoint, following connections.
// The result should be released by the caller.
static CFStringRef ConnectedEndpointName(MIDIEndpointRef endpoint)
{
CFMutableStringRef result = CFStringCreateMutable(NULL, 0);
CFStringRef str;
OSStatus err;
int i;
// Does the endpoint have connections?
CFDataRef connections = NULL;
int nConnected = 0;
bool anyStrings = false;
err = MIDIObjectGetDataProperty(endpoint, kMIDIPropertyConnectionUniqueID, &connections);
if (connections != NULL) {
// It has connections, follow them
// Concatenate the names of all connected devices
nConnected = CFDataGetLength(connections) / sizeof(MIDIUniqueID);
if (nConnected) {
const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections));
for (i = 0; i < nConnected; ++i, ++pid) {
MIDIUniqueID id = EndianS32_BtoN(*pid);
MIDIObjectRef connObject;
MIDIObjectType connObjectType;
err = MIDIObjectFindByUniqueID(id, &connObject, &connObjectType);
if (err == noErr) {
if (connObjectType == kMIDIObjectType_ExternalSource ||
connObjectType == kMIDIObjectType_ExternalDestination) {
// Connected to an external device's endpoint (10.3 and later).
str = EndpointName((MIDIEndpointRef)(connObject), true);
} else {
// Connected to an external device (10.2) (or something else, catch-all)
str = NULL;
MIDIObjectGetStringProperty(connObject, kMIDIPropertyName, &str);
}
if (str != NULL) {
if (anyStrings)
CFStringAppend(result, CFSTR(", "));
else anyStrings = true;
CFStringAppend(result, str);
CFRelease(str);
}
}
}
}
CFRelease(connections);
}
if (anyStrings)
return result;
// Here, either the endpoint had no connections, or we failed to obtain names for any of them.
return EndpointName(endpoint, false);
}
char* cm_get_full_endpoint_name(MIDIEndpointRef endpoint)
{
#ifdef OLDCODE
MIDIEntityRef entity;
MIDIDeviceRef device;
CFStringRef endpointName = NULL;
CFStringRef deviceName = NULL;
#endif
CFStringRef fullName = NULL;
CFStringEncoding defaultEncoding;
char* newName;
/* get the default string encoding */
defaultEncoding = CFStringGetSystemEncoding();
fullName = ConnectedEndpointName(endpoint);
#ifdef OLDCODE
/* get the entity and device info */
MIDIEndpointGetEntity(endpoint, &entity);
MIDIEntityGetDevice(entity, &device);
/* create the nicely formated name */
MIDIObjectGetStringProperty(endpoint, kMIDIPropertyName, &endpointName);
MIDIObjectGetStringProperty(device, kMIDIPropertyName, &deviceName);
if (deviceName != NULL) {
fullName = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@: %@"),
deviceName, endpointName);
} else {
fullName = endpointName;
}
#endif
/* copy the string into our buffer */
newName = (char *) malloc(CFStringGetLength(fullName) + 1);
CFStringGetCString(fullName, newName, CFStringGetLength(fullName) + 1,
defaultEncoding);
/* clean up */
#ifdef OLDCODE
if (endpointName) CFRelease(endpointName);
if (deviceName) CFRelease(deviceName);
#endif
if (fullName) CFRelease(fullName);
return newName;
}
pm_fns_node pm_macosx_in_dictionary = {
none_write_short,
none_sysex,
none_sysex,
none_write_byte,
none_write_short,
none_write_flush,
none_synchronize,
midi_in_open,
midi_abort,
midi_in_close,
success_poll,
midi_has_host_error,
midi_get_host_error,
};
pm_fns_node pm_macosx_out_dictionary = {
midi_write_short,
midi_begin_sysex,
midi_end_sysex,
midi_write_byte,
midi_write_realtime,
midi_write_flush,
midi_synchronize,
midi_out_open,
midi_abort,
midi_out_close,
success_poll,
midi_has_host_error,
midi_get_host_error,
};
PmError pm_macosxcm_init(void)
{
ItemCount numInputs, numOutputs, numDevices;
MIDIEndpointRef endpoint;
int i;
OSStatus macHostError;
char *error_text;
/* Determine the number of MIDI devices on the system */
numDevices = MIDIGetNumberOfDevices();
numInputs = MIDIGetNumberOfSources();
numOutputs = MIDIGetNumberOfDestinations();
/* Return prematurely if no devices exist on the system
Note that this is not an error. There may be no devices.
Pm_CountDevices() will return zero, which is correct and
useful information
*/
if (numDevices <= 0) {
return pmNoError;
}
/* Initialize the client handle */
macHostError = MIDIClientCreate(CFSTR("PortMidi"), NULL, NULL, &client);
if (macHostError != noErr) {
error_text = "MIDIClientCreate() in pm_macosxcm_init()";
goto error_return;
}
/* Create the input port */
macHostError = MIDIInputPortCreate(client, CFSTR("Input port"), readProc,
NULL, &portIn);
if (macHostError != noErr) {
error_text = "MIDIInputPortCreate() in pm_macosxcm_init()";
goto error_return;
}
/* Create the output port */
macHostError = MIDIOutputPortCreate(client, CFSTR("Output port"), &portOut);
if (macHostError != noErr) {
error_text = "MIDIOutputPortCreate() in pm_macosxcm_init()";
goto error_return;
}
/* Iterate over the MIDI input devices */
for (i = 0; i < numInputs; i++) {
endpoint = MIDIGetSource(i);
if (!endpoint) {
continue;
}
/* set the first input we see to the default */
if (pm_default_input_device_id == -1)
pm_default_input_device_id = pm_descriptor_index;
/* Register this device with PortMidi */
pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
TRUE, (void*)(long)endpoint, &pm_macosx_in_dictionary);
}
/* Iterate over the MIDI output devices */
for (i = 0; i < numOutputs; i++) {
endpoint = MIDIGetDestination(i);
if (!endpoint) {
continue;
}
/* set the first output we see to the default */
if (pm_default_output_device_id == -1)
pm_default_output_device_id = pm_descriptor_index;
/* Register this device with PortMidi */
pm_add_device("CoreMIDI", cm_get_full_endpoint_name(endpoint),
FALSE, (void*)(long) endpoint, &pm_macosx_out_dictionary);
}
return pmNoError;
error_return:
pm_hosterror = macHostError;
sprintf(pm_hosterror_text, "Host error %d: %s\n", macHostError, error_text);
pm_macosxcm_term(); /* clear out any opened ports */
return pmHostError;
}
void pm_macosxcm_term(void)
{
if (client) MIDIClientDispose(client);
if (portIn) MIDIPortDispose(portIn);
if (portOut) MIDIPortDispose(portOut);
}
|