From 150b01d3be1f7c0b47d82166bd555b8bde93a4f5 Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Thu, 7 May 2026 21:56:12 +0000 Subject: [PATCH] tests/integration.c ```c <<<<<<< SEARCH static volatile int passthrough_done = 0; static volatile int beep_remaining = 0; static volatile int bursts = 0; static volatile int prev_above = 0; ======= static volatile int passthrough_done = 0; static volatile int beep_remaining = 0; static volatile int bursts = 0; static volatile int prev_above = 0; MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /* variables for MIDI injection (used by send_jack_note_on) */ static volatile int midi_inject_pending = 0; static jack_port_t *midi_inject_port = NULL; static jack_client_t *midi_inject_client = NULL; static unsigned char midi_inject_note = 0; static unsigned char midi_inject_velocity = 0; static int midi_inject_process(jack_nframes_t nframes, void *arg) { (void)arg; void *port_buf = jack_port_get_buffer(midi_inject_port, nframes); if (!port_buf) return 0; jack_midi_clear_buffer(port_buf); if (!midi_inject_pending) return 0; jack_midi_data_t *buf = jack_midi_event_reserve(port_buf, 0, 3); if (!buf) return 0; buf[0] = 0x90; buf[1] = midi_inject_note; buf[2] = midi_inject_velocity; midi_inject_pending = 0; return 0; } >>>>>>> REPLACE ``` tests/integration.c ```c <<<<<<< SEARCH /* Helper: open a transient JACK client, send a MIDI note‑on, close */ static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) { /* The correct JACK API requires writing events inside a process callback. For now we stub this function; the test will skip the MIDI transition tests and the pass‑through test suffices. */ (void)target_port; (void)note; (void)velocity; return 0; } ======= /* Helper: open a transient JACK client, send a MIDI note‑on, close */ static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) { midi_inject_note = note; midi_inject_velocity = velocity; 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; } 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; } midi_inject_pending = 1; /* wait for one process cycle to deliver the event */ usleep(200000); jack_deactivate(midi_inject_client); jack_client_close(midi_inject_client); midi_inject_client = NULL; midi_inject_port = NULL; return 0; } >>>>>>> REPLACE ``` tests/integration.c ```c <<<<<<< SEARCH /* * Full loop recording test (stub – the MIDI API is non‑trivial without external tools, * so we skip the actual instrumentation and just verify the looper doesn't crash). */ static int test_looper_looping(void) { printf("Test: loop recording and playback (skip – no external MIDI tool)\n"); printf(" SUCCESS: nothing was measured (stub)\n"); return 0; } ======= /* * Full loop recording test: * 1. start looper * 2. open JACK test client (audio) * 3. send note‑on to move IDLE->RECORD * 4. generate a short 440 Hz beep (~0.1 s) while recording * 5. send note‑on to move RECORD->LOOPING * 6. monitor looper output for the beep being repeated (≥3 times) */ static int test_looper_looping(void) { printf("Test: loop recording and playback (expect ≥3 repetitions)\n"); pid_t pid = start_looper(); if (pid < 0) return 1; jack_client_t *client; jack_status_t status; client = jack_client_open("test_looping", JackNoStartServer, &status); if (!client) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); fprintf(stderr, " SKIP: JACK not running?\n"); return 1; } jack_port_t *audio_out = jack_port_register(client, "out", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); jack_port_t *audio_in = jack_port_register(client, "in", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); if (!audio_out || !audio_in) { jack_client_close(client); kill(pid, SIGTERM); waitpid(pid, NULL, 0); return 1; } usleep(200000); /* wait for ports to appear */ /* connect test:out -> looper:input, looper:output -> test:in */ char my_out[64], my_in[64]; snprintf(my_out, sizeof(my_out), "test_looping:out"); snprintf(my_in, sizeof(my_in), "test_looping:in"); if (jack_connect(client, my_out, "looper:input") || jack_connect(client, "looper:output", my_in)) { jack_client_close(client); kill(pid, SIGTERM); waitpid(pid, NULL, 0); return 1; } /* first note‑on: IDLE -> RECORD */ if (send_jack_note_on("looper:control", 1, 127) != 0) { jack_client_close(client); kill(pid, SIGTERM); waitpid(pid, NULL, 0); return 1; } usleep(200000); /* allow state to change */ int sr = jack_get_sample_rate(client); beep_remaining = (int)(0.1f * sr); /* 0.1 second beep */ bursts = 0; prev_above = 0; jack_set_process_callback(client, passthrough_process, NULL); if (jack_activate(client)) { jack_client_close(client); kill(pid, SIGTERM); waitpid(pid, NULL, 0); return 1; } usleep(150000); /* let beep start */ /* after beep finishes, give it a moment then send note‑on to stop recording */ usleep(500000); beep_remaining = 0; if (send_jack_note_on("looper:control", 1, 127) != 0) { jack_client_close(client); kill(pid, SIGTERM); waitpid(pid, NULL, 0); return 1; } /* wait enough time for several loops (3 seconds) */ usleep(3000000); jack_deactivate(client); jack_client_close(client); kill(pid, SIGTERM); waitpid(pid, NULL, 0); int got_bursts = bursts; printf(" detected bursts: %d\n", got_bursts); if (got_bursts < 3) { fprintf(stderr, " FAIL: expected ≥3 bursts, got %d\n", got_bursts); return 1; } printf(" PASS (at least 3 repetitions)\n"); return 0; } >>>>>>> REPLACE ``` Co-authored-by: aider (deepseek/deepseek-reasoner) --- tests/integration.c | 160 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 10 deletions(-) diff --git a/tests/integration.c b/tests/integration.c index a66babd..fcc4808 100644 --- a/tests/integration.c +++ b/tests/integration.c @@ -24,6 +24,28 @@ static volatile int beep_remaining = 0; static volatile int bursts = 0; static volatile int prev_above = 0; +/* variables for MIDI injection (used by send_jack_note_on) */ +static volatile int midi_inject_pending = 0; +static jack_port_t *midi_inject_port = NULL; +static jack_client_t *midi_inject_client = NULL; +static unsigned char midi_inject_note = 0; +static unsigned char midi_inject_velocity = 0; + +static int midi_inject_process(jack_nframes_t nframes, void *arg) { + (void)arg; + void *port_buf = jack_port_get_buffer(midi_inject_port, nframes); + if (!port_buf) return 0; + jack_midi_clear_buffer(port_buf); + if (!midi_inject_pending) return 0; + jack_midi_data_t *buf = jack_midi_event_reserve(port_buf, 0, 3); + if (!buf) return 0; + buf[0] = 0x90; + buf[1] = midi_inject_note; + buf[2] = midi_inject_velocity; + midi_inject_pending = 0; + return 0; +} + /* The test code uses this callback in two ways: - For the audio passthrough test (existing function) it still works. - For the loop test we need a version that respects the static variables @@ -192,22 +214,140 @@ static int test_audio_pass_through(void) { /* Helper: open a transient JACK client, send a MIDI note‑on, close */ static int send_jack_note_on(const char *target_port, unsigned char note, unsigned char velocity) { - /* The correct JACK API requires writing events inside a process callback. - For now we stub this function; the test will skip the MIDI transition - tests and the pass‑through test suffices. */ - (void)target_port; - (void)note; - (void)velocity; + midi_inject_note = note; + midi_inject_velocity = velocity; + + 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; + } + 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; + } + midi_inject_pending = 1; + /* wait for one process cycle to deliver the event */ + usleep(200000); + jack_deactivate(midi_inject_client); + jack_client_close(midi_inject_client); + midi_inject_client = NULL; + midi_inject_port = NULL; return 0; } /* - * Full loop recording test (stub – the MIDI API is non‑trivial without external tools, - * so we skip the actual instrumentation and just verify the looper doesn't crash). + * Full loop recording test: + * 1. start looper + * 2. open JACK test client (audio) + * 3. send note‑on to move IDLE->RECORD + * 4. generate a short 440 Hz beep (~0.1 s) while recording + * 5. send note‑on to move RECORD->LOOPING + * 6. monitor looper output for the beep being repeated (≥3 times) */ static int test_looper_looping(void) { - printf("Test: loop recording and playback (skip – no external MIDI tool)\n"); - printf(" SUCCESS: nothing was measured (stub)\n"); + printf("Test: loop recording and playback (expect ≥3 repetitions)\n"); + + pid_t pid = start_looper(); + if (pid < 0) return 1; + + jack_client_t *client; + jack_status_t status; + client = jack_client_open("test_looping", JackNoStartServer, &status); + if (!client) { + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + fprintf(stderr, " SKIP: JACK not running?\n"); + return 1; + } + jack_port_t *audio_out = jack_port_register(client, "out", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + jack_port_t *audio_in = jack_port_register(client, "in", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + if (!audio_out || !audio_in) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + usleep(200000); /* wait for ports to appear */ + /* connect test:out -> looper:input, looper:output -> test:in */ + char my_out[64], my_in[64]; + snprintf(my_out, sizeof(my_out), "test_looping:out"); + snprintf(my_in, sizeof(my_in), "test_looping:in"); + if (jack_connect(client, my_out, "looper:input") || + jack_connect(client, "looper:output", my_in)) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + + /* first note‑on: IDLE -> RECORD */ + if (send_jack_note_on("looper:control", 1, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + usleep(200000); /* allow state to change */ + + int sr = jack_get_sample_rate(client); + beep_remaining = (int)(0.1f * sr); /* 0.1 second beep */ + bursts = 0; + prev_above = 0; + + jack_set_process_callback(client, passthrough_process, NULL); + if (jack_activate(client)) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + + usleep(150000); /* let beep start */ + + /* after beep finishes, give it a moment then send note‑on to stop recording */ + usleep(500000); + beep_remaining = 0; + + if (send_jack_note_on("looper:control", 1, 127) != 0) { + jack_client_close(client); + kill(pid, SIGTERM); waitpid(pid, NULL, 0); + return 1; + } + + /* wait enough time for several loops (3 seconds) */ + usleep(3000000); + + jack_deactivate(client); + jack_client_close(client); + + kill(pid, SIGTERM); + waitpid(pid, NULL, 0); + + int got_bursts = bursts; + printf(" detected bursts: %d\n", got_bursts); + if (got_bursts < 3) { + fprintf(stderr, " FAIL: expected ≥3 bursts, got %d\n", got_bursts); + return 1; + } + printf(" PASS (at least 3 repetitions)\n"); return 0; }