From c58cd283e572aeb577eb380677aa67f72ce186b5 Mon Sep 17 00:00:00 2001 From: Elias Aebi Date: Thu, 12 Sep 2024 15:08:29 +0200 Subject: [PATCH] MIDI --- CMakeLists.txt | 3 +- demos/events.c | 24 +++++++++++++-- gral.h | 15 ++++++++++ gral_linux.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ gral_macos.m | 74 ++++++++++++++++++++++++++++++++++++++++++++++ gral_windows.cpp | 14 +++++++++ 6 files changed, 204 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index efd2bb1..b225643 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,8 +9,9 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") find_library(COCOA Cocoa) find_library(CARBON Carbon) find_library(AUDIO_TOOLBOX AudioToolbox) + find_library(CORE_MIDI CoreMIDI) add_library(gral gral_macos.m) - target_link_libraries(gral ${COCOA} ${CARBON} ${AUDIO_TOOLBOX}) + target_link_libraries(gral ${COCOA} ${CARBON} ${AUDIO_TOOLBOX} ${CORE_MIDI}) target_compile_definitions(gral PUBLIC GRAL_MACOS) elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_package(PkgConfig REQUIRED) diff --git a/demos/events.c b/demos/events.c index 99415ec..8617392 100644 --- a/demos/events.c +++ b/demos/events.c @@ -5,6 +5,7 @@ struct demo { struct gral_application *application; struct gral_window *window; struct gral_timer *timer; + struct gral_midi *midi; }; static int close(void *user_data) { @@ -105,9 +106,21 @@ static void timer(void *user_data) { printf("timer %f\n", gral_time_get_monotonic()); } +static void note_on(unsigned char note, unsigned char velocity, void *user_data) { + printf("note on: %u (%u)\n", note, velocity); +} + +static void note_off(unsigned char note, void *user_data) { + printf("note off: %u\n", note); +} + +static void control_change(unsigned char controller, unsigned char value, void *user_data) { + printf("control change: %u, %u\n", controller, value); +} + static void create_window(void *user_data) { struct demo *demo = user_data; - struct gral_window_interface interface = { + struct gral_window_interface window_interface = { &close, &draw, &resize, @@ -125,8 +138,14 @@ static void create_window(void *user_data) { &focus_enter, &focus_leave }; - demo->window = gral_window_create(demo->application, 600, 400, "gral events demo", &interface, demo); + demo->window = gral_window_create(demo->application, 600, 400, "gral events demo", &window_interface, demo); demo->timer = gral_timer_create(1000, &timer, demo); + struct gral_midi_interface midi_interface = { + ¬e_on, + ¬e_off, + &control_change + }; + demo->midi = gral_midi_create(demo->application, "gral events demo", &midi_interface, demo); } static void open_empty(void *user_data) { @@ -148,6 +167,7 @@ int main(int argc, char **argv) { struct gral_application_interface interface = {&open_empty, &open_file, &quit}; demo.application = gral_application_create("com.github.eyelash.libgral.demos.events", &interface, &demo); int result = gral_application_run(demo.application, argc, argv); + gral_midi_delete(demo.midi); gral_window_delete(demo.window); gral_application_delete(demo.application); return result; diff --git a/gral.h b/gral.h index 3739290..6639ba4 100644 --- a/gral.h +++ b/gral.h @@ -98,6 +98,12 @@ struct gral_window_interface { struct gral_timer; struct gral_file; struct gral_directory_watcher; +struct gral_midi; +struct gral_midi_interface { + void (*note_on)(unsigned char note, unsigned char velocity, void *user_data); + void (*note_off)(unsigned char note, void *user_data); + void (*control_change)(unsigned char controller, unsigned char value, void *user_data); +}; /*================ @@ -207,6 +213,15 @@ double gral_time_get_monotonic(void); void gral_audio_play(int (*callback)(float *buffer, int frames, void *user_data), void *user_data); + +/*========= + MIDI + =========*/ + +struct gral_midi *gral_midi_create(struct gral_application *application, char const *name, struct gral_midi_interface const *interface, void *user_data); +void gral_midi_delete(struct gral_midi *midi); + + #ifdef __cplusplus } #endif diff --git a/gral_linux.c b/gral_linux.c index 1668b0b..be46762 100644 --- a/gral_linux.c +++ b/gral_linux.c @@ -860,3 +860,80 @@ void gral_audio_play(int (*callback)(float *buffer, int frames, void *user_data) snd_pcm_drain(pcm); snd_pcm_close(pcm); } + + +/*========= + MIDI + =========*/ + +struct gral_midi { + struct gral_midi_interface interface; + void *user_data; + snd_seq_t *seq; + int port; + guint source_id; +}; + +static void connect_midi() { + +} + +static gboolean midi_callback(gint fd, GIOCondition condition, gpointer user_data) { + struct gral_midi *midi = user_data; + snd_seq_event_t *event; + while (snd_seq_event_input(midi->seq, &event) > 0) { + switch (event->type) { + case SND_SEQ_EVENT_NOTEON: + midi->interface.note_on(event->data.note.note, event->data.note.velocity, midi->user_data); + break; + case SND_SEQ_EVENT_NOTEOFF: + midi->interface.note_off(event->data.note.note, midi->user_data); + break; + case SND_SEQ_EVENT_CONTROLLER: + midi->interface.control_change(event->data.control.param, event->data.control.value, midi->user_data); + break; + default: + break; + } + } + return G_SOURCE_CONTINUE; +} + +struct gral_midi *gral_midi_create(struct gral_application *application, char const *name, struct gral_midi_interface const *interface, void *user_data) { + struct gral_midi *midi = malloc(sizeof(struct gral_midi)); + midi->interface = *interface; + midi->user_data = user_data; + snd_seq_open(&midi->seq, "default", SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK); + snd_seq_set_client_name(midi->seq, name); + int client_id = snd_seq_client_id(midi->seq); + midi->port = snd_seq_create_simple_port(midi->seq, name, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC); + snd_seq_client_info_t *client_info; + snd_seq_client_info_alloca(&client_info); + snd_seq_port_info_t* port_info; + snd_seq_port_info_alloca(&port_info); + snd_seq_client_info_set_client (client_info, -1); + while (snd_seq_query_next_client(midi->seq, client_info) >= 0) { + int client = snd_seq_client_info_get_client(client_info); + snd_seq_port_info_set_client(port_info, client); + snd_seq_port_info_set_port(port_info, -1); + while (snd_seq_query_next_port(midi->seq, port_info) >= 0) { + int port = snd_seq_port_info_get_port(port_info); + unsigned int capability = snd_seq_port_info_get_capability(port_info); + //int direction = snd_seq_port_info_get_direction(port_info); + if ((capability & SND_SEQ_PORT_CAP_READ) && (capability & SND_SEQ_PORT_CAP_SUBS_READ) && !(capability & SND_SEQ_PORT_CAP_NO_EXPORT)) { + g_print("connecting to %s - %s\n", snd_seq_client_info_get_name(client_info), snd_seq_port_info_get_name(port_info)); + snd_seq_connect_from(midi->seq, midi->port, client, port); + } + } + } + struct pollfd pfd; + snd_seq_poll_descriptors(midi->seq, &pfd, 1, POLLIN); + midi->source_id = g_unix_fd_add(pfd.fd, pfd.events, &midi_callback, midi); + return midi; +} + +void gral_midi_delete(struct gral_midi *midi) { + g_source_remove(midi->source_id); + snd_seq_close(midi->seq); + free(midi); +} diff --git a/gral_macos.m b/gral_macos.m index ce6a33f..98c8a8e 100644 --- a/gral_macos.m +++ b/gral_macos.m @@ -921,3 +921,77 @@ void gral_audio_play(int (*callback)(float *buffer, int frames, void *user_data) CFRunLoopRun(); AudioQueueDispose(queue, NO); } + + +/*========= + MIDI + =========*/ + +struct gral_midi { + struct gral_midi_interface interface; + void *user_data; + MIDIClientRef client; +}; + +static void midi_callback(MIDINotification const *message, void *user_data) { + struct gral_midi *midi = user_data; + switch (message->messageID) { + case kMIDIMsgObjectAdded: + break; + default: + break; + } +} + +static void midi_read_callback(MIDIPacketList const *packet_list, void *user_data, void *srcConnRefCon) { + struct gral_midi *midi = user_data; + MIDIPacket const *packet = packet_list->packet; + for (UInt32 i = 0; i < packet_list->numPackets; i++) { + for (UInt16 j = 0; j < packet->length; j++) { + if ((packet->data[j] & 0xF0) == 0x80 && j + 2 < packet->length) { + Byte note = packet->data[j+1]; + Byte velocity = packet->data[j+2]; + midi->interface.note_off(note, midi->user_data); + j += 2; + } + else if ((packet->data[j] & 0xF0) == 0x90 && j + 2 < packet->length) { + Byte note = packet->data[j+1]; + Byte velocity = packet->data[j+2]; + midi->interface.note_on(note, velocity, midi->user_data); + j += 2; + } + else if ((packet->data[j] & 0xF0) == 0xB0 && j + 2 < packet->length) { + Byte controller = packet->data[j+1]; + Byte value = packet->data[j+2]; + midi->interface.control_change(controller, value, midi->user_data); + j += 2; + } + } + packet = MIDIPacketNext(packet); + } +} + +struct gral_midi *gral_midi_create(struct gral_application *application, char const *name, struct gral_midi_interface const *interface, void *user_data) { + struct gral_midi *midi = malloc(sizeof(struct gral_midi)); + midi->interface = *interface; + midi->user_data = user_data; + CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingUTF8); + MIDIClientCreate(string, &midi_callback, midi, &midi->client); + MIDIPortRef port; + MIDIInputPortCreate(midi->client, string, &midi_read_callback, midi, &port); + CFRelease(string); + ItemCount count = MIDIGetNumberOfSources(); + for (ItemCount i = 0; i < count; i++) { + MIDIEndpointRef source = MIDIGetSource(i); + CFStringRef name = NULL; + MIDIObjectGetStringProperty(source, kMIDIPropertyName, &name); + MIDIPortConnectSource(port, source, NULL); + } + return midi; +} + +void gral_midi_delete(struct gral_midi *midi) { + // MIDIPortDispose(); + MIDIClientDispose(midi->client); + free(midi); +} diff --git a/gral_windows.cpp b/gral_windows.cpp index 6c2125b..2043db7 100644 --- a/gral_windows.cpp +++ b/gral_windows.cpp @@ -1390,3 +1390,17 @@ void gral_audio_play(int (*callback)(float *buffer, int frames, void *user_data) render_client->Release(); CoTaskMemFree(format); } + + +/*========= + MIDI + =========*/ + +gral_midi *gral_midi_create(gral_application *application, char const *name, gral_midi_interface const *iface, void *user_data) { + // TODO + return NULL; +} + +void gral_midi_delete(gral_midi *midi) { + // TODO +}