diff --git a/midi/drivers/coremidi.c b/midi/drivers/coremidi.c index b15666e8c8..1f183610f6 100644 --- a/midi/drivers/coremidi.c +++ b/midi/drivers/coremidi.c @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include "../midi_driver.h" #include "../../verbosity.h" @@ -36,95 +36,25 @@ typedef struct MIDIEndpointRef input_endpoint; /* Selected input endpoint */ MIDIEndpointRef output_endpoint; /* Selected output endpoint */ coremidi_queue_t input_queue; /* Receive thread to read(), lock-free */ - volatile bool is_shutting_down; /* Flag to prevent callbacks during shutdown */ + /* The removal count each endpoint was last found at (read and write's) */ + int input_removals; + int output_removals; + retro_atomic_int_t shutting_down; /* the receive callback stops */ } coremidi_t; -/* Global list of active driver instances for notification handling */ -#define MAX_MIDI_INSTANCES 8 -static coremidi_t *active_instances[MAX_MIDI_INSTANCES]; -static slock_t *instances_lock = NULL; - -/* Register a driver instance in the global list */ -static bool register_instance(coremidi_t *d) -{ - int i; - if (!instances_lock) - return false; - - slock_lock(instances_lock); - for (i = 0; i < MAX_MIDI_INSTANCES; i++) - { - if (!active_instances[i]) - { - active_instances[i] = d; - slock_unlock(instances_lock); - return true; - } - } - slock_unlock(instances_lock); - - RARCH_ERR("[MIDI] Too many MIDI instances (max %d).\n", MAX_MIDI_INSTANCES); - return false; -} - -/* Unregister a driver instance from the global list */ -static void unregister_instance(coremidi_t *d) -{ - int i; - if (!instances_lock || !d) - return; - - slock_lock(instances_lock); - for (i = 0; i < MAX_MIDI_INSTANCES; i++) - { - if (active_instances[i] == d) - { - active_instances[i] = NULL; - break; - } - } - slock_unlock(instances_lock); -} +/* Every object CoreMIDI reports removed moves this on; an instance + * looks for its endpoints again when it has moved, on its own thread, + * so the notification touches no instance. */ +static retro_atomic_int_t coremidi_removals; /* MIDI notification callback for device changes */ static void midi_notify_callback(const MIDINotification *message, void *refCon) { - int i; - - if (!instances_lock) - return; - switch (message->messageID) { case kMIDIMsgObjectRemoved: - { - const MIDIObjectAddRemoveNotification *notify = - (const MIDIObjectAddRemoveNotification *)message; - - /* Check all active instances for matching endpoints */ - slock_lock(instances_lock); - for (i = 0; i < MAX_MIDI_INSTANCES; i++) - { - coremidi_t *d = active_instances[i]; - if (!d || d->is_shutting_down) - continue; - - /* Check if this instance's endpoints were removed */ - if (notify->child == d->input_endpoint) - { - RARCH_LOG("[MIDI] Input endpoint removed, clearing.\n"); - d->input_endpoint = 0; - coremidi_queue_clear(&d->input_queue); - } - if (notify->child == d->output_endpoint) - { - RARCH_LOG("[MIDI] Output endpoint removed, clearing.\n"); - d->output_endpoint = 0; - } - } - slock_unlock(instances_lock); + retro_atomic_fetch_add_int(&coremidi_removals, 1); break; - } case kMIDIMsgSetupChanged: RARCH_LOG("[MIDI] MIDI setup changed.\n"); break; @@ -163,7 +93,18 @@ static bool coremidi_validate_endpoint(MIDIEndpointRef endpoint, bool is_source) return false; } -/* Write to the queue */ +/* Whether endpoint outlived every removal since *seen: looked for only + * when something was removed. */ +static bool coremidi_endpoint_kept(MIDIEndpointRef endpoint, int *seen, + bool is_source) +{ + int removals = retro_atomic_load_acquire_int(&coremidi_removals); + if (removals == *seen) + return true; + *seen = removals; + return coremidi_validate_endpoint(endpoint, is_source); +} + /* MIDIReadProc callback function */ static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) @@ -173,7 +114,7 @@ static void midi_read_callback(const MIDIPacketList *pktlist, const MIDIPacket *packet; /* CRITICAL: Check shutdown flag immediately to prevent use-after-free */ - if (!d || d->is_shutting_down) + if (!d || retro_atomic_load_acquire_int(&d->shutting_down)) return; packet = &pktlist->packet[0]; @@ -207,17 +148,6 @@ static void *coremidi_init(const char *input, const char *output) OSStatus err; coremidi_t *d; - /* Initialize global instance lock on first call */ - if (!instances_lock) - { - instances_lock = slock_new(); - if (!instances_lock) - { - RARCH_ERR("[MIDI] Failed to create instances lock.\n"); - return NULL; - } - } - /* Create persistent shared client on first call */ if (!shared_midi_client) { @@ -240,19 +170,11 @@ static void *coremidi_init(const char *input, const char *output) coremidi_queue_init(&d->input_queue); - d->is_shutting_down = false; + retro_atomic_int_init(&d->shutting_down, 0); /* Store reference to shared client */ d->client = shared_midi_client; - /* Register this instance in the global list */ - if (!register_instance(d)) - { - RARCH_ERR("[MIDI] Failed to register MIDI instance.\n"); - free(d); - return NULL; - } - /* Create input port if specified */ if (input) { @@ -261,7 +183,6 @@ static void *coremidi_init(const char *input, const char *output) if (err != noErr) { RARCH_ERR("[MIDI] MIDIInputPortCreate failed: %d.\n", err); - unregister_instance(d); free(d); return NULL; } @@ -278,7 +199,6 @@ static void *coremidi_init(const char *input, const char *output) RARCH_ERR("[MIDI] MIDIOutputPortCreate failed: %d.\n", err); if (d->input_port) MIDIPortDispose(d->input_port); - unregister_instance(d); free(d); return NULL; } @@ -363,6 +283,7 @@ static bool coremidi_get_avail_outputs(struct string_list *outputs) static bool coremidi_set_input(void *p, const char *input) { ItemCount count; + int removals; coremidi_t *d = (coremidi_t *)p; if (!d || !d->input_port) { @@ -388,6 +309,8 @@ static bool coremidi_set_input(void *p, const char *input) return true; } + /* A removal from here on is looked for at the next read */ + removals = retro_atomic_load_acquire_int(&coremidi_removals); /* Find the input endpoint by name */ count = MIDIGetNumberOfSources(); for (ItemCount i = 0; i < count; i++) @@ -406,6 +329,7 @@ static bool coremidi_set_input(void *p, const char *input) if (err == noErr) { d->input_endpoint = endpoint; + d->input_removals = removals; RARCH_LOG("[MIDI] Input endpoint set to %s.\n", buf); return true; } @@ -424,6 +348,7 @@ static bool coremidi_set_input(void *p, const char *input) static bool coremidi_set_output(void *p, const char *output) { ItemCount i, count; + int removals; coremidi_t *d = (coremidi_t *)p; if (!d || !d->output_port) { @@ -439,6 +364,8 @@ static bool coremidi_set_output(void *p, const char *output) return true; } + /* A removal from here on is looked for at the next write */ + removals = retro_atomic_load_acquire_int(&coremidi_removals); /* Find the output endpoint by name */ count = MIDIGetNumberOfDestinations(); for (i = 0; i < count; i++) @@ -453,6 +380,7 @@ static bool coremidi_set_output(void *p, const char *output) && string_is_equal(output, buf)) { d->output_endpoint = endpoint; + d->output_removals = removals; RARCH_LOG("[MIDI] Output endpoint set to %s.\n", buf); CFRelease(name); return true; @@ -484,11 +412,11 @@ static bool coremidi_read(void *p, midi_event_t *event) return false; } - /* Validate endpoint still exists */ - if (!coremidi_validate_endpoint(d->input_endpoint, true)) + if (!coremidi_endpoint_kept(d->input_endpoint, &d->input_removals, true)) { RARCH_WARN("[MIDI] Input endpoint no longer valid.\n"); d->input_endpoint = 0; + coremidi_queue_clear(&d->input_queue); return false; } @@ -525,8 +453,8 @@ static bool coremidi_write(void *p, const midi_event_t *event) return false; } - /* Validate endpoint still exists */ - if (!coremidi_validate_endpoint(d->output_endpoint, false)) + if (!coremidi_endpoint_kept(d->output_endpoint, &d->output_removals, + false)) { RARCH_WARN("[MIDI] Output endpoint no longer valid.\n"); d->output_endpoint = 0; @@ -595,10 +523,8 @@ static void coremidi_free(void *p) return; } - /* CRITICAL: Set shutdown flag and unregister from global list FIRST. - * This prevents callbacks from accessing this instance. */ - d->is_shutting_down = true; - unregister_instance(d); + /* CRITICAL: Set shutdown flag FIRST: the receive callback stops. */ + retro_atomic_store_release_int(&d->shutting_down, 1); /* Now safe to tear down MIDI resources */ if (d->input_port) diff --git a/samples/midi/coremidi_driver/Makefile b/samples/midi/coremidi_driver/Makefile new file mode 100644 index 0000000000..4971b3cfb9 --- /dev/null +++ b/samples/midi/coremidi_driver/Makefile @@ -0,0 +1,40 @@ +# midi/drivers/coremidi.c over a stand-in for CoreMIDI (stub/), so the +# macOS driver's device removal handling builds and runs anywhere. +# Includes the driver. + +include ../../sample_objdir.mk + +TARGET := coremidi_driver_test +CORE_DIR := ../../.. +LRC := $(CORE_DIR)/libretro-common + +SOURCES := coremidi_driver_test.c \ + $(LRC)/rthreads/rthreads.c +CFLAGS += -Wall -std=gnu99 -g1 -O1 -D_GNU_SOURCE -DHAVE_THREADS \ + -Istub -I$(CORE_DIR) -I$(CORE_DIR)/midi -I$(LRC)/include +LDFLAGS += -lpthread -lm +OBJS := $(call sample_objs,$(SOURCES:.c=.o)) + +ifneq ($(SANITIZER),) + CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS) + LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS) +endif + +all: $(TARGET) +%.o: %.c + $(CC) -c -o $@ $< $(CFLAGS) + +$(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c + @mkdir -p $(@D) + $(CC) -c -o $@ $< $(CFLAGS) +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) + +check: $(TARGET) + ./$(TARGET) + +clean: + rm -rf $(SAMPLE_OBJDIR) + rm -f $(TARGET) $(TARGET).exe coremidi_driver_test.o + +.PHONY: all check clean diff --git a/samples/midi/coremidi_driver/coremidi_driver_test.c b/samples/midi/coremidi_driver/coremidi_driver_test.c new file mode 100644 index 0000000000..cb2a2bde86 --- /dev/null +++ b/samples/midi/coremidi_driver/coremidi_driver_test.c @@ -0,0 +1,345 @@ +/* Copyright (C) 2010-2026 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (coremidi_driver_test.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* The CoreMIDI driver over a stand-in for CoreMIDI (stub/): reads and + * writes look for their endpoints only after a device was removed; a + * removal - an input with events queued, an output, a device neither + * uses - is found by each instance on its next call, however many + * there are; and removals notified from another thread while reads + * go on touch no instance. Built under ThreadSanitizer too. */ + +#include +#include +#include +#include + +#include + +#include "../../../midi/drivers/coremidi.c" + +void RARCH_LOG(const char *fmt, ...) { (void)fmt; } +void RARCH_WARN(const char *fmt, ...) { (void)fmt; } +void RARCH_ERR(const char *fmt, ...) { (void)fmt; } +bool string_list_append(struct string_list *list, const char *elem, + union string_list_elem_attr attr) +{ (void)list; (void)elem; (void)attr; return true; } +size_t midi_driver_get_event_size(uint8_t status) +{ + if (status < 0x80) + return 0; + switch (status & 0xF0) + { + case 0xC0: + case 0xD0: + return 2; + default: + return 3; + } +} + +static unsigned failures; +#define CHECK(cond, ...) \ + do { if (!(cond)) { printf("FAIL line %d: ", __LINE__); \ + printf(__VA_ARGS__); printf("\n"); failures++; } } while (0) + +/* ---- the stand-in ---- */ +#define T_MAX 16 + +struct t_ep +{ + const char *name; + MIDIEndpointRef ref; +}; + +static struct t_ep t_src[T_MAX], t_dst[T_MAX]; +static unsigned t_nsrc, t_ndst; +static unsigned t_enumerations; +static unsigned t_sends; +static MIDINotifyProc t_notify; +static MIDIReadProc t_read; +static void *t_read_ref[T_MAX + 1]; +static MIDIPortRef t_ports; + +static void t_add(struct t_ep *eps, unsigned *n, MIDIEndpointRef ref, + const char *name) +{ + eps[*n].ref = ref; + eps[*n].name = name; + (*n)++; +} + +static void t_drop(struct t_ep *eps, unsigned *n, MIDIEndpointRef ref) +{ + unsigned i; + for (i = 0; i < *n; i++) + if (eps[i].ref == ref) + { + eps[i] = eps[--(*n)]; + return; + } +} + +OSStatus MIDIClientCreate(CFStringRef name, MIDINotifyProc notify, + void *refCon, MIDIClientRef *out) +{ + (void)name; (void)refCon; + t_notify = notify; + *out = 1; + return noErr; +} +OSStatus MIDIInputPortCreate(MIDIClientRef client, CFStringRef name, + MIDIReadProc read, void *refCon, MIDIPortRef *out) +{ + (void)client; (void)name; + if (t_ports >= T_MAX) + return -1; + t_read = read; + *out = ++t_ports; + t_read_ref[t_ports] = refCon; + return noErr; +} +OSStatus MIDIOutputPortCreate(MIDIClientRef client, CFStringRef name, + MIDIPortRef *out) +{ + (void)client; (void)name; + *out = 1000 + (++t_ports); + return noErr; +} +OSStatus MIDIPortDispose(MIDIPortRef port) { (void)port; return noErr; } +OSStatus MIDIPortConnectSource(MIDIPortRef port, MIDIEndpointRef source, + void *connRefCon) +{ (void)port; (void)source; (void)connRefCon; return noErr; } +OSStatus MIDIPortDisconnectSource(MIDIPortRef port, MIDIEndpointRef source) +{ (void)port; (void)source; return noErr; } +ItemCount MIDIGetNumberOfSources(void) { t_enumerations++; return t_nsrc; } +MIDIEndpointRef MIDIGetSource(ItemCount i) { return t_src[i].ref; } +ItemCount MIDIGetNumberOfDestinations(void) { t_enumerations++; return t_ndst; } +MIDIEndpointRef MIDIGetDestination(ItemCount i) { return t_dst[i].ref; } +OSStatus MIDIObjectGetStringProperty(MIDIObjectRef obj, CFStringRef prop, + CFStringRef *out) +{ + unsigned i; + (void)prop; + for (i = 0; i < t_nsrc; i++) + if (t_src[i].ref == obj) + return (*out = t_src[i].name), noErr; + for (i = 0; i < t_ndst; i++) + if (t_dst[i].ref == obj) + return (*out = t_dst[i].name), noErr; + return -1; +} +int CFStringGetCString(CFStringRef s, char *buf, long size, int encoding) +{ + (void)encoding; + if (!s || (long)strlen(s) >= size) + return 0; + strcpy(buf, s); + return 1; +} +void CFRelease(const void *obj) { (void)obj; } +MIDIPacket *MIDIPacketListInit(MIDIPacketList *list) +{ + list->numPackets = 0; + return &list->packet[0]; +} +MIDIPacket *MIDIPacketListAdd(MIDIPacketList *list, unsigned long size, + MIDIPacket *cur, MIDITimeStamp time, unsigned long n, const Byte *data) +{ + (void)size; + if (n > sizeof(cur->data)) + return NULL; + cur->timeStamp = time; + cur->length = (UInt16)n; + memcpy(cur->data, data, n); + list->numPackets++; + return cur; +} +const MIDIPacket *MIDIPacketNext(const MIDIPacket *pkt) { return pkt + 1; } +OSStatus MIDISend(MIDIPortRef port, MIDIEndpointRef dest, + const MIDIPacketList *list) +{ + unsigned i; + (void)port; (void)list; + for (i = 0; i < t_ndst; i++) + if (t_dst[i].ref == dest) + return t_sends++, noErr; + return -1; +} + +/* CoreMIDI's notification of an object gone, as its own thread sends it. */ +static void t_removed(MIDIObjectRef child) +{ + MIDIObjectAddRemoveNotification n; + memset(&n, 0, sizeof(n)); + n.messageID = kMIDIMsgObjectRemoved; + n.messageSize = sizeof(n); + n.child = child; + t_notify((const MIDINotification*)&n, NULL); +} + +static MIDIObjectRef t_removed_child; +static void t_removed_thread(void *arg) +{ + (void)arg; + t_removed(t_removed_child); +} +static void t_removed_elsewhere(MIDIObjectRef child) +{ + sthread_t *t; + t_removed_child = child; + if ((t = sthread_create(t_removed_thread, NULL))) + sthread_join(t); +} + +/* A note from the device, through the instance's read proc. */ +static void t_note(coremidi_t *d, uint8_t key) +{ + MIDIPacketList list; + const Byte note[3] = { 0x90, 0, 100 }; + unsigned port; + MIDIPacketListInit(&list); + MIDIPacketListAdd(&list, sizeof(list), &list.packet[0], 0, 3, note); + list.packet[0].data[1] = key; + for (port = 1; port <= t_ports && port <= T_MAX; port++) + if (t_read_ref[port] == d) + t_read(&list, d, NULL); +} + +static retro_atomic_int_t t_spam_done; +static void t_spam_thread(void *arg) +{ + unsigned i; + (void)arg; + for (i = 0; i < 2000; i++) + t_removed(0x999); + retro_atomic_store_release_int(&t_spam_done, 1); +} + +int main(void) +{ + midi_event_t ev; + coremidi_t *a, *c, *many[10]; + unsigned i, e, reads; + sthread_t *spam; + + t_add(t_src, &t_nsrc, 0x101, "Keys"); + t_add(t_src, &t_nsrc, 0x102, "Pads"); + t_add(t_dst, &t_ndst, 0x201, "Synth"); + + a = (coremidi_t*)coremidi_init("Keys", "Synth"); + CHECK(a && coremidi_set_input(a, "Keys") && coremidi_set_output(a, "Synth"), + "instance"); + if (!a) + return 1; + + /* No device gone: no device looked for, read or write. */ + e = t_enumerations; + for (i = 0; i < 100; i++) + { + t_note(a, (uint8_t)(i & 0x7f)); + CHECK(coremidi_read(a, &ev) && ev.data[1] == (i & 0x7f), + "read %u", i); + CHECK(coremidi_write(a, &ev), "write %u", i); + } + CHECK(t_enumerations == e, "%u device lists for 100 reads and writes", + t_enumerations - e); + + /* A device neither uses, removed: each side looks once, and keeps + * its endpoint. */ + t_drop(t_src, &t_nsrc, 0x102); + t_removed_elsewhere(0x102); + t_note(a, 1); + CHECK(coremidi_read(a, &ev) && coremidi_write(a, &ev), + "an unrelated removal broke the endpoints"); + t_note(a, 2); + CHECK(coremidi_read(a, &ev) && coremidi_write(a, &ev), "after it"); + CHECK(t_enumerations == e + 2, "%u device lists for one removal, want 2", + t_enumerations - e); + + /* The input removed with notes queued: the next read finds it gone + * and none of them come through, then or from a new device. */ + t_note(a, 3); + t_note(a, 4); + t_drop(t_src, &t_nsrc, 0x101); + t_removed_elsewhere(0x101); + CHECK(!coremidi_read(a, &ev) && !a->input_endpoint, + "a read from a removed input"); + t_add(t_src, &t_nsrc, 0x103, "Keys"); + CHECK(coremidi_set_input(a, "Keys"), "the input again"); + CHECK(!coremidi_read(a, &ev), "a note from before the removal came through"); + t_note(a, 5); + CHECK(coremidi_read(a, &ev) && ev.data[1] == 5, "the new input's note"); + + /* The output removed: the next write finds it gone. */ + t_drop(t_dst, &t_ndst, 0x201); + t_removed_elsewhere(0x201); + CHECK(!coremidi_write(a, &ev) && !a->output_endpoint, + "a write to a removed output"); + + /* More instances than a fixed registry held, each finding a + * removal on its own call. */ + for (i = 0; i < 10; i++) + { + many[i] = (coremidi_t*)coremidi_init("Keys", NULL); + CHECK(many[i] && coremidi_set_input(many[i], "Keys"), "instance %u", i); + } + t_drop(t_src, &t_nsrc, 0x103); + t_removed_elsewhere(0x103); + for (i = 0; i < 10; i++) + if (many[i]) + { + CHECK(!coremidi_read(many[i], &ev) && !many[i]->input_endpoint, + "instance %u read from a removed input", i); + coremidi_free(many[i]); + } + coremidi_free(a); + + /* Removals notified from another thread while reads go on. */ + t_add(t_src, &t_nsrc, 0x104, "Strings"); + c = (coremidi_t*)coremidi_init("Strings", NULL); + CHECK(c && coremidi_set_input(c, "Strings"), "instance"); + retro_atomic_store_release_int(&t_spam_done, 0); + spam = sthread_create(t_spam_thread, NULL); + reads = 0; + while (spam && !retro_atomic_load_acquire_int(&t_spam_done)) + { + t_note(c, 7); + if (coremidi_read(c, &ev) && ev.data[1] == 7) + reads++; + else + failures++; + } + if (spam) + sthread_join(spam); + CHECK(c->input_endpoint == 0x104, "a removal of another device dropped the input"); + coremidi_free(c); + + if (failures) + { + printf("FAIL %u checks\n", failures); + return 1; + } + printf("ok reads and writes list no devices until one is removed; " + "removed inputs and outputs found by every instance; %u reads " + "beside removals on another thread\n", reads); + return 0; +} diff --git a/samples/midi/coremidi_driver/stub/CoreMIDI/CoreMIDI.h b/samples/midi/coremidi_driver/stub/CoreMIDI/CoreMIDI.h new file mode 100644 index 0000000000..c945b3a8b0 --- /dev/null +++ b/samples/midi/coremidi_driver/stub/CoreMIDI/CoreMIDI.h @@ -0,0 +1,94 @@ +/* The part of CoreMIDI and CoreFoundation midi/drivers/coremidi.c calls, + * for coremidi_driver_test: endpoints the test makes and removes, and a + * client whose notifications the test sends. Implemented by the test. */ +#ifndef COREMIDI_DRIVER_TEST_STUB_H +#define COREMIDI_DRIVER_TEST_STUB_H + +#include + +typedef uint8_t Byte; +typedef uint16_t UInt16; +typedef uint32_t UInt32; +typedef int32_t SInt32; +typedef uint64_t UInt64; +typedef SInt32 OSStatus; +typedef unsigned long ItemCount; +typedef UInt64 MIDITimeStamp; +typedef UInt32 MIDIObjectRef; +typedef UInt32 MIDIClientRef; +typedef UInt32 MIDIPortRef; +typedef UInt32 MIDIEndpointRef; +typedef const char *CFStringRef; + +#define noErr 0 +#define CFSTR(s) (s) +#define kCFStringEncodingUTF8 0 +#define kMIDIPropertyDisplayName ((CFStringRef)"name") + +typedef SInt32 MIDINotificationMessageID; +enum +{ + kMIDIMsgSetupChanged = 1, + kMIDIMsgObjectAdded = 2, + kMIDIMsgObjectRemoved = 3 +}; + +typedef struct MIDINotification +{ + MIDINotificationMessageID messageID; + UInt32 messageSize; +} MIDINotification; + +typedef struct MIDIObjectAddRemoveNotification +{ + MIDINotificationMessageID messageID; + UInt32 messageSize; + MIDIObjectRef parent; + SInt32 parentType; + MIDIObjectRef child; + SInt32 childType; +} MIDIObjectAddRemoveNotification; + +typedef struct MIDIPacket +{ + MIDITimeStamp timeStamp; + UInt16 length; + Byte data[256]; +} MIDIPacket; + +typedef struct MIDIPacketList +{ + UInt32 numPackets; + MIDIPacket packet[1]; +} MIDIPacketList; + +typedef void (*MIDINotifyProc)(const MIDINotification *message, void *refCon); +typedef void (*MIDIReadProc)(const MIDIPacketList *pktlist, + void *readProcRefCon, void *srcConnRefCon); + +OSStatus MIDIClientCreate(CFStringRef name, MIDINotifyProc notify, + void *refCon, MIDIClientRef *out); +OSStatus MIDIInputPortCreate(MIDIClientRef client, CFStringRef name, + MIDIReadProc read, void *refCon, MIDIPortRef *out); +OSStatus MIDIOutputPortCreate(MIDIClientRef client, CFStringRef name, + MIDIPortRef *out); +OSStatus MIDIPortDispose(MIDIPortRef port); +OSStatus MIDIPortConnectSource(MIDIPortRef port, MIDIEndpointRef source, + void *connRefCon); +OSStatus MIDIPortDisconnectSource(MIDIPortRef port, MIDIEndpointRef source); +ItemCount MIDIGetNumberOfSources(void); +MIDIEndpointRef MIDIGetSource(ItemCount i); +ItemCount MIDIGetNumberOfDestinations(void); +MIDIEndpointRef MIDIGetDestination(ItemCount i); +OSStatus MIDIObjectGetStringProperty(MIDIObjectRef obj, CFStringRef prop, + CFStringRef *out); +int CFStringGetCString(CFStringRef s, char *buf, long size, int encoding); +void CFRelease(const void *obj); +MIDIPacket *MIDIPacketListInit(MIDIPacketList *list); +MIDIPacket *MIDIPacketListAdd(MIDIPacketList *list, unsigned long size, + MIDIPacket *cur, MIDITimeStamp time, unsigned long n, const Byte *data); +const MIDIPacket *MIDIPacketNext(const MIDIPacket *pkt); +OSStatus MIDISend(MIDIPortRef port, MIDIEndpointRef dest, + const MIDIPacketList *list); + +#endif