From cadfafb61e03fc38ce8aac849de4b892f880bb24 Mon Sep 17 00:00:00 2001
From: Tasos Sahanidis <[EMAIL REDACTED]>
Date: Mon, 17 Mar 2025 03:01:11 +0200
Subject: [PATCH] native_midi_linux_alsa: Add support for System Exclusive
messages
---
.../native_midi/native_midi_linux_alsa.c | 46 ++++++++++++++++---
1 file changed, 39 insertions(+), 7 deletions(-)
diff --git a/src/codecs/native_midi/native_midi_linux_alsa.c b/src/codecs/native_midi/native_midi_linux_alsa.c
index e7d482312..219838170 100644
--- a/src/codecs/native_midi/native_midi_linux_alsa.c
+++ b/src/codecs/native_midi/native_midi_linux_alsa.c
@@ -42,6 +42,8 @@
#include <string.h>
#include <sys/stat.h>
+#include <assert.h>
+
//#define SDL_NATIVE_MIDI_ALSA_DYNAMIC "libasound.so.2"
static int load_alsa_syms(void);
@@ -438,7 +440,7 @@ static NativeMidiSong *currentsong = NULL;
NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
{
NativeMidiSong *song;
- MIDIEvent *end;
+ MIDIEvent *event;
int sv[2];
if (!(song = SDL_calloc(1, sizeof(NativeMidiSong)))) {
@@ -455,7 +457,7 @@ NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
song->mainsock = sv[0];
song->threadsock = sv[1];
- end = song->evtlist = CreateMIDIEventList(src, &song->ppqn);
+ event = song->evtlist = CreateMIDIEventList(src, &song->ppqn);
if (!song->evtlist) {
close_sockpair(song);
@@ -464,6 +466,37 @@ NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
return NULL;
}
+ /* Since ALSA requires the starting F0 for SysEx, but MIDIEvent.extraData doesn't contain it, we must preprocess the list */
+ /* In addition, since we're going through the list, store the last event's time for looping purposes */
+ do {
+ /* Is this a SysEx? */
+ if (event->status == MIDI_CMD_COMMON_SYSEX && event->extraLen) {
+ /* Sanity check in case something changes in the future */
+ /* This is safe to do since we can't have an F0 manufacturer ID */
+ assert(event->extraData[0] != MIDI_CMD_COMMON_SYSEX);
+
+ /* Resize by + 1 */
+ Uint8 *newData = SDL_realloc(event->extraData, event->extraLen + 1);
+ if (newData == NULL) {
+ close_sockpair(song);
+ /* Original allocation is still valid on failure */
+ FreeMIDIEventList(song->evtlist);
+ SDL_free(song);
+ MIDI_SET_ERROR("Failed to preprocess MIDIEventList SysEx");
+ return NULL;
+ }
+
+ /* Prepend the F0 */
+ event->extraData = newData;
+ SDL_memmove(event->extraData + 1, event->extraData, event->extraLen);
+ event->extraData[0] = MIDI_CMD_COMMON_SYSEX;
+ event->extraLen++;
+ }
+
+ /* Store the end time */
+ song->endtime = event->time;
+ } while ((event = event->next));
+
if (!(song->seq = open_seq(&song->srcport))) {
FreeMIDIEventList(song->evtlist);
close_sockpair(song);
@@ -478,11 +511,6 @@ NativeMidiSong *native_midi_loadsong_IO(SDL_IOStream *src, bool closeio)
SDL_SetAtomicInt(&song->playerstate, NATIVE_MIDI_STOPPED);
- /* Find the last event to get its time */
- while (end->next)
- end = end->next;
-
- song->endtime = end->time;
/* Since there's no reliable volume control solution it's better to leave the music playing instead of having hanging notes */
song->allow_pause = SDL_GetHintBoolean("SDL_NATIVE_MUSIC_ALLOW_PAUSE", false);
@@ -692,6 +720,7 @@ static int native_midi_player_thread(void *d)
const unsigned char channel = event->status & 0x0F;
snd_seq_ev_set_dest(&evt, song->dstaddr.client, song->dstaddr.port);
+ snd_seq_ev_set_fixed(&evt);
snd_seq_ev_schedule_tick(&evt, queue, 0, event->time);
bool unhandled = false;
@@ -733,6 +762,9 @@ static int native_midi_player_thread(void *d)
snd_seq_ev_set_queue_tempo(&evt, queue, t);
break;
}
+ } else if (event->status == MIDI_CMD_COMMON_SYSEX) {
+ snd_seq_ev_set_sysex(&evt, event->extraLen, event->extraData);
+ break;
}
unhandled = true;