Skip to content

Commit

Permalink
MIDI
Browse files Browse the repository at this point in the history
  • Loading branch information
eyelash committed Sep 13, 2024
1 parent a681f01 commit 531591c
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 4 deletions.
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ project(libgral)

if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
add_library(gral gral_windows.cpp)
target_link_libraries(gral d2d1 dwrite mfplat mfuuid)
target_link_libraries(gral d2d1 dwrite mfplat mfuuid winmm)
target_compile_definitions(gral PUBLIC GRAL_WINDOWS)
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)
Expand Down
24 changes: 22 additions & 2 deletions demos/events.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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 = {
&note_on,
&note_off,
&control_change
};
demo->midi = gral_midi_create(demo->application, "gral events demo", &midi_interface, demo);
}

static void open_empty(void *user_data) {
Expand All @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions gral.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};


/*================
Expand Down Expand Up @@ -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
Expand Down
88 changes: 88 additions & 0 deletions gral_linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -860,3 +860,91 @@ 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 midi_connect_port(struct gral_midi *midi, int client, snd_seq_port_info_t *port_info) {
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_NO_EXPORT) {
return;
}
if ((capability & SND_SEQ_PORT_CAP_READ) && (capability & SND_SEQ_PORT_CAP_SUBS_READ)) {
//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);
}
}

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;
case SND_SEQ_EVENT_PORT_START:
{
snd_seq_port_info_t *port_info;
snd_seq_port_info_alloca(&port_info);
snd_seq_get_any_port_info(midi->seq, event->data.addr.client, event->data.addr.port, port_info);
midi_connect_port(midi, event->data.addr.client, port_info);
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) {
midi_connect_port(midi, client, port_info);
}
}
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);
}
74 changes: 74 additions & 0 deletions gral_macos.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
MIDIPortRef port;
};

static void midi_callback(MIDINotification const *notification, void *user_data) {
struct gral_midi *midi = user_data;
if (notification->messageID == kMIDIMsgObjectAdded) {
MIDIObjectAddRemoveNotification *add_remove_notification = (MIDIObjectAddRemoveNotification *)notification;
if (add_remove_notification->childType == kMIDIObjectType_Source) {
MIDIPortConnectSource(midi->port, add_remove_notification->child, NULL);
}
}
}

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);
MIDIInputPortCreate(midi->client, string, &midi_read_callback, midi, &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(midi->port, source, NULL);
}
return midi;
}

void gral_midi_delete(struct gral_midi *midi) {
// MIDIPortDispose();
MIDIClientDispose(midi->client);
free(midi);
}
52 changes: 52 additions & 0 deletions gral_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1390,3 +1390,55 @@ void gral_audio_play(int (*callback)(float *buffer, int frames, void *user_data)
render_client->Release();
CoTaskMemFree(format);
}


/*=========
MIDI
=========*/

struct gral_midi {
gral_midi_interface iface;
void *user_data;
HMIDIIN midi;
};

static void CALLBACK midi_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
// TODO: call the callbacks on the main thread
gral_midi *midi = (gral_midi *)dwInstance;
switch (wMsg) {
case MIM_DATA:
{
if ((LOBYTE(LOWORD(dwParam1)) & 0xF0) == 0x80) {
midi->iface.note_off(HIBYTE(LOWORD(dwParam1)), midi->user_data);
}
else if ((LOBYTE(LOWORD(dwParam1)) & 0xF0) == 0x90) {
midi->iface.note_on(HIBYTE(LOWORD(dwParam1)), LOBYTE(HIWORD(dwParam1)), midi->user_data);
}
else if ((LOBYTE(LOWORD(dwParam1)) & 0xF0) == 0xB0) {
midi->iface.control_change(HIBYTE(LOWORD(dwParam1)), LOBYTE(HIWORD(dwParam1)), midi->user_data);
}
return;
}
default:
return;
}
}

gral_midi *gral_midi_create(gral_application *application, char const *name, gral_midi_interface const *iface, void *user_data) {
gral_midi *midi = new gral_midi();
midi->iface = *iface;
midi->user_data = user_data;
midi->midi = NULL;
if (midiInGetNumDevs() > 0) {
midiInOpen(&midi->midi, 0, (DWORD_PTR)&midi_callback, (DWORD_PTR)midi, CALLBACK_FUNCTION);
midiInStart(midi->midi);
}
return midi;
}

void gral_midi_delete(gral_midi *midi) {
if (midi->midi) {
midiInClose(midi->midi);
}
delete midi;
}

0 comments on commit 531591c

Please sign in to comment.