mirror of
https://github.com/libretro/RetroArch.git
synced 2025-04-02 10:51:52 -04:00
CoreMIDI add driver (#17569)
CoreMIDI install into build Signed-off-by: Joseph Mattiello <git@joemattiello.com>
This commit is contained in:
parent
83c0c1fc43
commit
eb2928eb0c
6 changed files with 434 additions and 0 deletions
|
@ -982,6 +982,12 @@ ifeq ($(HAVE_WINMM), 1)
|
|||
LIBS += -lwinmm
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_COREMIDI), 1)
|
||||
OBJ += midi/drivers/coremidi.o
|
||||
DEFINES += -DHAVE_COREMIDI
|
||||
LIBS += -framework CoreMIDI
|
||||
endif
|
||||
|
||||
# Audio Resamplers
|
||||
|
||||
ifeq ($(HAVE_NEON),1)
|
||||
|
|
|
@ -285,6 +285,7 @@ enum midi_driver_enum
|
|||
{
|
||||
MIDI_WINMM = RECORD_NULL + 1,
|
||||
MIDI_ALSA,
|
||||
MIDI_COREMIDI,
|
||||
MIDI_NULL
|
||||
};
|
||||
|
||||
|
@ -597,6 +598,8 @@ static const enum record_driver_enum RECORD_DEFAULT_DRIVER = RECORD_WAV;
|
|||
|
||||
#ifdef HAVE_WINMM
|
||||
static const enum midi_driver_enum MIDI_DEFAULT_DRIVER = MIDI_WINMM;
|
||||
#elif defined(HAVE_COREMIDI)
|
||||
static const enum midi_driver_enum MIDI_DEFAULT_DRIVER = MIDI_COREMIDI;
|
||||
#elif defined(HAVE_ALSA) && !defined(HAVE_HAKCHI) && !defined(HAVE_SEGAM) && !defined(DINGUX)
|
||||
static const enum midi_driver_enum MIDI_DEFAULT_DRIVER = MIDI_ALSA;
|
||||
#else
|
||||
|
@ -1455,6 +1458,8 @@ const char *config_get_default_midi(void)
|
|||
return "winmm";
|
||||
case MIDI_ALSA:
|
||||
return "alsa";
|
||||
case MIDI_COREMIDI:
|
||||
return "coremidi";
|
||||
case MIDI_NULL:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -961,6 +961,10 @@ MIDI
|
|||
#include "../midi/drivers/winmm_midi.c"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_COREMIDI
|
||||
#include "../midi/drivers/coremidi.c"
|
||||
#endif
|
||||
|
||||
/*============================================================
|
||||
DRIVERS
|
||||
============================================================ */
|
||||
|
|
415
midi/drivers/coremidi.c
Normal file
415
midi/drivers/coremidi.c
Normal file
|
@ -0,0 +1,415 @@
|
|||
/* RetroArch - A frontend for libretro.
|
||||
* Copyright (C) 2025 The RetroArch team
|
||||
*
|
||||
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with RetroArch.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
#include <libretro.h>
|
||||
#include <lists/string_list.h>
|
||||
#include <string/stdstring.h>
|
||||
|
||||
#include "../midi_driver.h"
|
||||
#include "../../verbosity.h"
|
||||
|
||||
#define CORE_MIDI_QUEUE_SIZE 1024
|
||||
|
||||
typedef struct {
|
||||
midi_event_t events[CORE_MIDI_QUEUE_SIZE]; ///< Event buffer
|
||||
int read_index; ///< Current read position
|
||||
int write_index; ///< Current write position
|
||||
} coremidi_queue_t;
|
||||
|
||||
typedef struct {
|
||||
MIDIClientRef client; ///< CoreMIDI client
|
||||
MIDIPortRef input_port; ///< Input port for receiving MIDI data
|
||||
MIDIPortRef output_port; ///< Output port for sending MIDI data
|
||||
MIDIEndpointRef input_endpoint; ///< Selected input endpoint
|
||||
MIDIEndpointRef output_endpoint; ///< Selected output endpoint
|
||||
coremidi_queue_t input_queue; ///< Queue for incoming MIDI events
|
||||
} coremidi_t;
|
||||
|
||||
/// Write to the queue
|
||||
static bool coremidi_queue_write(coremidi_queue_t *q, const midi_event_t *ev) {
|
||||
int next_write = (q->write_index + 1) % CORE_MIDI_QUEUE_SIZE;
|
||||
if (next_write == q->read_index) // Queue full
|
||||
return false;
|
||||
|
||||
memcpy(&q->events[q->write_index], ev, sizeof(*ev));
|
||||
q->write_index = next_write;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// MIDIReadProc callback function
|
||||
static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) {
|
||||
coremidi_t *d = (coremidi_t *)readProcRefCon;
|
||||
const MIDIPacket *packet = &pktlist->packet[0];
|
||||
midi_event_t event;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0; i < pktlist->numPackets; i++) {
|
||||
const uint8_t *data = packet->data;
|
||||
size_t length = packet->length;
|
||||
const MIDITimeStamp timestamp = packet->timeStamp;
|
||||
|
||||
while (length > 0) {
|
||||
size_t msg_size = midi_driver_get_event_size(data[0]);
|
||||
if (msg_size == 0 || msg_size > length)
|
||||
break;
|
||||
|
||||
event.data = (uint8_t *)data;
|
||||
event.data_size = msg_size;
|
||||
event.delta_time = (uint32_t)timestamp;
|
||||
|
||||
// Add to queue
|
||||
coremidi_queue_write(&d->input_queue, &event);
|
||||
|
||||
data += msg_size;
|
||||
length -= msg_size;
|
||||
}
|
||||
|
||||
packet = MIDIPacketNext(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the CoreMIDI client and ports
|
||||
static void *coremidi_init(const char *input, const char *output) {
|
||||
coremidi_t *d = (coremidi_t *)calloc(1, sizeof(coremidi_t));
|
||||
if (!d) {
|
||||
RARCH_ERR("[MIDI]: Out of memory.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
OSStatus err = MIDIClientCreate(CFSTR("RetroArch MIDI Client"), NULL, NULL, &d->client);
|
||||
if (err != noErr) {
|
||||
RARCH_ERR("[MIDI]: MIDIClientCreate failed: %d\n", err);
|
||||
free(d);
|
||||
return NULL;
|
||||
} else {
|
||||
RARCH_LOG("[MIDI]: CoreMIDI client created successfully\n");
|
||||
}
|
||||
|
||||
// Create input port if specified
|
||||
if (input) {
|
||||
err = MIDIInputPortCreate(d->client, CFSTR("Input Port"), midi_read_callback, d, &d->input_port);
|
||||
if (err != noErr) {
|
||||
RARCH_ERR("[MIDI]: MIDIInputPortCreate failed: %d\n", err);
|
||||
MIDIClientDispose(d->client);
|
||||
free(d);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
RARCH_LOG("[MIDI]: CoreMIDI input port created successfully\n");
|
||||
}
|
||||
|
||||
// Create output port if specified
|
||||
if (output) {
|
||||
err = MIDIOutputPortCreate(d->client, CFSTR("Output Port"), &d->output_port);
|
||||
if (err != noErr) {
|
||||
RARCH_ERR("[MIDI]: MIDIOutputPortCreate failed: %d\n", err);
|
||||
if (d->input_port)
|
||||
MIDIPortDispose(d->input_port);
|
||||
MIDIClientDispose(d->client);
|
||||
free(d);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
RARCH_LOG("[MIDI]: CoreMIDI output port created successfully\n");
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/// Get available input devices
|
||||
static bool coremidi_get_avail_inputs(struct string_list *inputs) {
|
||||
ItemCount count = MIDIGetNumberOfSources();
|
||||
union string_list_elem_attr attr = {0};
|
||||
|
||||
for (ItemCount i = 0; i < count; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetSource(i);
|
||||
CFStringRef name;
|
||||
char buf[256];
|
||||
|
||||
OSStatus err = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName, &name);
|
||||
if (err == noErr) {
|
||||
if (CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8)) {
|
||||
if (!string_list_append(inputs, buf, attr)) {
|
||||
RARCH_ERR("[MIDI]: Failed to append input device to list: %s\n", buf);
|
||||
CFRelease(name);
|
||||
return false;
|
||||
} else {
|
||||
RARCH_LOG("[MIDI]: Input device added to list: %s\n", buf);
|
||||
}
|
||||
}
|
||||
CFRelease(name);
|
||||
} else {
|
||||
RARCH_WARN("[MIDI]: Failed to get display name for input device %d: %d\n", i, err);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Get available output devices
|
||||
static bool coremidi_get_avail_outputs(struct string_list *outputs) {
|
||||
ItemCount count = MIDIGetNumberOfDestinations();
|
||||
union string_list_elem_attr attr = {0};
|
||||
|
||||
for (ItemCount i = 0; i < count; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetDestination(i);
|
||||
CFStringRef name;
|
||||
char buf[256];
|
||||
|
||||
OSStatus err = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName, &name);
|
||||
if (err == noErr) {
|
||||
if (CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8)) {
|
||||
if (!string_list_append(outputs, buf, attr)) {
|
||||
RARCH_ERR("[MIDI]: Failed to append output device to list: %s\n", buf);
|
||||
CFRelease(name);
|
||||
return false;
|
||||
} else {
|
||||
RARCH_LOG("[MIDI]: Output device added to list: %s\n", buf);
|
||||
}
|
||||
}
|
||||
CFRelease(name);
|
||||
} else {
|
||||
RARCH_WARN("[MIDI]: Failed to get display name for output device %d: %d\n", i, err);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Set the input device
|
||||
static bool coremidi_set_input(void *p, const char *input) {
|
||||
coremidi_t *d = (coremidi_t *)p;
|
||||
if (!d || !d->input_port) {
|
||||
RARCH_WARN("[MIDI]: Input port not initialized\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disconnect current input endpoint
|
||||
if (d->input_endpoint) {
|
||||
RARCH_LOG("[MIDI]: Disconnecting current input endpoint\n");
|
||||
MIDIPortDisconnectSource(d->input_port, d->input_endpoint);
|
||||
d->input_endpoint = 0;
|
||||
}
|
||||
|
||||
// If input is NULL or "Off", just return success
|
||||
if (!input || string_is_equal(input, "Off")) {
|
||||
RARCH_LOG("[MIDI]: Input set to Off\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the input endpoint by name
|
||||
ItemCount count = MIDIGetNumberOfSources();
|
||||
for (ItemCount i = 0; i < count; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetSource(i);
|
||||
CFStringRef name;
|
||||
char buf[256];
|
||||
|
||||
OSStatus err = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName, &name);
|
||||
if (err == noErr) {
|
||||
if (CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) && string_is_equal(input, buf)) {
|
||||
err = MIDIPortConnectSource(d->input_port, endpoint, NULL);
|
||||
CFRelease(name);
|
||||
if (err == noErr) {
|
||||
d->input_endpoint = endpoint;
|
||||
RARCH_LOG("[MIDI]: Input endpoint set to %s\n", buf);
|
||||
return true;
|
||||
} else {
|
||||
RARCH_WARN("[MIDI]: Failed to connect input endpoint: %d\n", err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
CFRelease(name);
|
||||
}
|
||||
}
|
||||
|
||||
RARCH_WARN("[MIDI]: Input device not found: %s\n", input);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Set the output device
|
||||
static bool coremidi_set_output(void *p, const char *output) {
|
||||
coremidi_t *d = (coremidi_t *)p;
|
||||
if (!d || !d->output_port) {
|
||||
RARCH_WARN("[MIDI]: Output port not initialized\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If output is NULL or "Off", just return success
|
||||
if (!output || string_is_equal(output, "Off")) {
|
||||
RARCH_LOG("[MIDI]: Output set to Off\n");
|
||||
d->output_endpoint = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the output endpoint by name
|
||||
ItemCount count = MIDIGetNumberOfDestinations();
|
||||
for (ItemCount i = 0; i < count; i++) {
|
||||
MIDIEndpointRef endpoint = MIDIGetDestination(i);
|
||||
CFStringRef name;
|
||||
char buf[256];
|
||||
|
||||
OSStatus err = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName, &name);
|
||||
if (err == noErr) {
|
||||
if (CFStringGetCString(name, buf, sizeof(buf), kCFStringEncodingUTF8) && string_is_equal(output, buf)) {
|
||||
d->output_endpoint = endpoint;
|
||||
RARCH_LOG("[MIDI]: Output endpoint set to %s\n", buf);
|
||||
CFRelease(name);
|
||||
return true;
|
||||
}
|
||||
CFRelease(name);
|
||||
}
|
||||
}
|
||||
|
||||
RARCH_WARN("[MIDI]: Output device not found: %s\n", output);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Read from the queue
|
||||
static bool coremidi_queue_read(coremidi_queue_t *q, midi_event_t *ev) {
|
||||
if (q->read_index == q->write_index) // Queue empty
|
||||
return false;
|
||||
|
||||
memcpy(ev, &q->events[q->read_index], sizeof(*ev));
|
||||
q->read_index = (q->read_index + 1) % CORE_MIDI_QUEUE_SIZE;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Read a MIDI event
|
||||
static bool coremidi_read(void *p, midi_event_t *event) {
|
||||
coremidi_t *d = (coremidi_t *)p;
|
||||
|
||||
if (!d || !event) {
|
||||
RARCH_WARN("[MIDI]: Invalid parameters in coremidi_read\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d->input_port || !d->input_endpoint) {
|
||||
RARCH_WARN("[MIDI]: Input not configured\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = coremidi_queue_read(&d->input_queue, event);
|
||||
// #if DEBUG
|
||||
RARCH_LOG("[MIDI]: Input queue read result: %d\n", result);
|
||||
// #endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Write a MIDI event
|
||||
static bool coremidi_write(void *p, const midi_event_t *event) {
|
||||
coremidi_t *d = (coremidi_t *)p;
|
||||
|
||||
if (!d || !event) {
|
||||
RARCH_WARN("[MIDI]: Invalid parameters in coremidi_write\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d->output_port || !d->output_endpoint) {
|
||||
RARCH_WARN("[MIDI]: Output not configured\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate event data
|
||||
if (!event->data || event->data_size == 0 || event->data_size > 65535) {
|
||||
RARCH_WARN("[MIDI]: Invalid event data in coremidi_write\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a MIDIPacketList with sufficient buffer size
|
||||
char packetListBuffer[sizeof(MIDIPacketList) + sizeof(MIDIPacket) + event->data_size];
|
||||
MIDIPacketList *packetList = (MIDIPacketList *)packetListBuffer;
|
||||
MIDIPacket *packet = MIDIPacketListInit(packetList);
|
||||
|
||||
// Add the event to the packet list
|
||||
packet = MIDIPacketListAdd(packetList, sizeof(packetListBuffer), packet, 0, event->data_size, event->data);
|
||||
if (!packet) {
|
||||
RARCH_WARN("[MIDI]: Failed to create MIDIPacketList\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the packet
|
||||
OSStatus err = MIDISend(d->output_port, d->output_endpoint, packetList);
|
||||
if (err != noErr) {
|
||||
RARCH_WARN("[MIDI]: MIDISend failed: %d\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Flush the output buffer
|
||||
static bool coremidi_flush(void *p) {
|
||||
coremidi_t *d = (coremidi_t *)p;
|
||||
|
||||
if (!d) {
|
||||
RARCH_WARN("[MIDI]: Invalid parameters in coremidi_flush\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!d->output_port || !d->output_endpoint) {
|
||||
RARCH_WARN("[MIDI]: Output not configured\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// CoreMIDI doesn't require explicit flushing, so we just return success
|
||||
// RARCH_LOG("[MIDI]: Output buffer flushed\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Free resources and cleanup
|
||||
static void coremidi_free(void *p) {
|
||||
coremidi_t *d = (coremidi_t *)p;
|
||||
|
||||
if (!d) {
|
||||
RARCH_WARN("[MIDI]: Invalid parameters in coremidi_free\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up MIDI resources
|
||||
if (d->input_port) {
|
||||
RARCH_LOG("[MIDI]: Disconnecting input port\n");
|
||||
MIDIPortDisconnectSource(d->input_port, d->input_endpoint);
|
||||
MIDIPortDispose(d->input_port);
|
||||
}
|
||||
|
||||
if (d->output_port) {
|
||||
RARCH_LOG("[MIDI]: Disconnecting output port\n");
|
||||
MIDIPortDisconnectSource(d->output_port, d->output_endpoint);
|
||||
MIDIPortDispose(d->output_port);
|
||||
}
|
||||
|
||||
if (d->client) {
|
||||
RARCH_LOG("[MIDI]: Disposing of CoreMIDI client\n");
|
||||
MIDIClientDispose(d->client);
|
||||
}
|
||||
|
||||
// Free the driver instance
|
||||
free(d);
|
||||
}
|
||||
|
||||
/// CoreMIDI driver API
|
||||
midi_driver_t midi_coremidi = {
|
||||
.ident = "coremidi",
|
||||
.init = coremidi_init,
|
||||
.free = coremidi_free,
|
||||
.set_input = coremidi_set_input,
|
||||
.set_output = coremidi_set_output,
|
||||
.read = coremidi_read,
|
||||
.write = coremidi_write,
|
||||
.flush = coremidi_flush,
|
||||
.get_avail_inputs = coremidi_get_avail_inputs,
|
||||
.get_avail_outputs = coremidi_get_avail_outputs,
|
||||
};
|
|
@ -236,6 +236,7 @@ bool midi_driver_set_all_sounds_off(void);
|
|||
|
||||
extern midi_driver_t midi_winmm;
|
||||
extern midi_driver_t midi_alsa;
|
||||
extern midi_driver_t midi_coremidi;
|
||||
|
||||
extern midi_driver_t *midi_drivers[];
|
||||
|
||||
|
|
|
@ -559,6 +559,9 @@ midi_driver_t *midi_drivers[] = {
|
|||
#endif
|
||||
#ifdef HAVE_WINMM
|
||||
&midi_winmm,
|
||||
#endif
|
||||
#ifdef HAVE_COREMIDI
|
||||
&midi_coremidi,
|
||||
#endif
|
||||
&midi_null
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue