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; }