fix: use persistent JACK client for MIDI injection to avoid race conditions

Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-10 13:05:42 +00:00
parent fe3fb7d873
commit de8202a0d2

View File

@@ -33,6 +33,10 @@ static jack_client_t *midi_inject_client = NULL;
static unsigned char midi_inject_note = 0; static unsigned char midi_inject_note = 0;
static unsigned char midi_inject_velocity = 0; static unsigned char midi_inject_velocity = 0;
/* Persistent MIDI injection client avoids race conditions of transient clients */
static jack_client_t *persistent_midi_client = NULL;
static jack_port_t *persistent_midi_port = NULL;
static void safe_usleep(unsigned int usec) { static void safe_usleep(unsigned int usec) {
struct timespec ts; struct timespec ts;
ts.tv_sec = usec / 1000000; ts.tv_sec = usec / 1000000;
@@ -56,6 +60,51 @@ static int midi_inject_process(jack_nframes_t nframes, void *arg) {
return 0; return 0;
} }
/* Initialise the persistent MIDI client (must be called once before any send) */
static int init_persistent_midi_client(void) {
if (persistent_midi_client) return 0; /* already initialised */
jack_status_t st;
persistent_midi_client = jack_client_open("test_midi_persistent", JackNoStartServer, &st);
if (!persistent_midi_client) return -1;
persistent_midi_port = jack_port_register(persistent_midi_client, "out",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsOutput, 0);
if (!persistent_midi_port) {
jack_client_close(persistent_midi_client);
persistent_midi_client = NULL;
return -1;
}
jack_set_process_callback(persistent_midi_client, midi_inject_process, NULL);
if (jack_activate(persistent_midi_client) != 0) {
jack_client_close(persistent_midi_client);
persistent_midi_client = NULL;
return -1;
}
/* Connect to looper control port */
if (jack_connect(persistent_midi_client, "test_midi_persistent:out", "looper:control") != 0) {
jack_deactivate(persistent_midi_client);
jack_client_close(persistent_midi_client);
persistent_midi_client = NULL;
return -1;
}
/* Use the persistent port for injection */
midi_inject_port = persistent_midi_port;
midi_inject_client = persistent_midi_client;
return 0;
}
/* Clean up the persistent MIDI client at the end */
static void cleanup_persistent_midi_client(void) {
if (persistent_midi_client) {
jack_deactivate(persistent_midi_client);
jack_client_close(persistent_midi_client);
persistent_midi_client = NULL;
persistent_midi_port = NULL;
midi_inject_port = NULL;
midi_inject_client = NULL;
}
}
/* The test code uses this callback in two ways: /* The test code uses this callback in two ways:
- For the audio passthrough test (existing function) it still works. - For the audio passthrough test (existing function) it still works.
- For the loop test we need a version that respects the static variables - For the loop test we need a version that respects the static variables
@@ -224,49 +273,22 @@ static int test_audio_pass_through(void) {
} }
/* Helper: open a transient JACK client, send a MIDI noteon, close */ /* Helper: send a MIDI noteon using the persistent client */
static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) { static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) {
(void)target_port; /* connection is already made to looper:control */
/* Ensure persistent client is initialised */
if (!persistent_midi_client && init_persistent_midi_client() != 0) {
return -1;
}
midi_inject_note = note; midi_inject_note = note;
midi_inject_velocity = velocity; midi_inject_velocity = velocity;
midi_inject_pending = 1;
jack_status_t st;
midi_inject_client = jack_client_open("test_midi_inject", JackNoStartServer, &st);
if (!midi_inject_client) return -1;
midi_inject_port = jack_port_register(midi_inject_client, "out",
JACK_DEFAULT_MIDI_TYPE,
JackPortIsOutput, 0);
if (!midi_inject_port) {
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
return -1;
}
char src[64];
snprintf(src, sizeof(src), "test_midi_inject:out");
if (jack_connect(midi_inject_client, src, target_port) != 0) {
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
midi_inject_port = NULL;
return -1;
}
midi_inject_pending = 1; /* signal before activation */
jack_set_process_callback(midi_inject_client, midi_inject_process, NULL);
if (jack_activate(midi_inject_client) != 0) {
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
midi_inject_port = NULL;
return -1;
}
/* wait for the process callback to clear the flag (event delivered) */ /* wait for the process callback to clear the flag (event delivered) */
for (int attempts = 0; attempts < 50; attempts++) { /* ~50 * 10ms = 500ms */ for (int attempts = 0; attempts < 50; attempts++) { /* ~500ms */
safe_usleep(10000); safe_usleep(10000);
if (!midi_inject_pending) break; if (!midi_inject_pending) break;
} }
jack_deactivate(midi_inject_client); return (midi_inject_pending == 0) ? 0 : -1;
jack_client_close(midi_inject_client);
midi_inject_client = NULL;
midi_inject_port = NULL;
return 0;
} }
/* /*
@@ -1346,6 +1368,7 @@ int main(void) {
failures++; failures++;
} }
cleanup_persistent_midi_client();
if (failures > 0) { if (failures > 0) {
fprintf(stderr, "%d test(s) FAILED\n", failures); fprintf(stderr, "%d test(s) FAILED\n", failures);
return 1; return 1;