mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
coremidi: device removals reach instances through one atomic count
CoreMIDI's notification of a removed object moves one atomic count on, and touches no instance; each instance's read and write look for its endpoint again only when the count has moved, on the thread that calls them, so a read or write lists no devices while none has gone. The instance registry and its lock go, and with them the cap of eight instances. The receive callback's shutdown flag is an atomic. samples/midi/coremidi_driver builds the driver against a stand-in for CoreMIDI: reads and writes list no devices until one is removed, a removed input drops the notes queued from it, every instance finds a removal, and removals notified from another thread run beside reads.
This commit is contained in:
1 parent
ef5a1293db
commit
8f5877bf6b
4 files changed
+517
-112
No files matched your search
+38
-112
@@ -17,7 +17,7 @@
|
||||
#include <libretro.h>
|
||||
#include <lists/string_list.h>
|
||||
#include <string/stdstring.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <retro_atomic.h>
|
||||
|
||||
#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)
|
||||
|
||||
@@ -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
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include <rthreads/rthreads.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
|
||||
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
|
||||
Reference in new issue
Block a user