From 3df9a02f6d2b8223b925c29430ab09be7efc536e Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Mon, 4 May 2026 17:38:36 +0000 Subject: [PATCH] feat: add external JACK test program for audio routing verification Co-authored-by: aider (deepseek/deepseek-coder) --- Let's produce the block.test_audio_routing.c | 180 +++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 Let's produce the block.test_audio_routing.c diff --git a/Let's produce the block.test_audio_routing.c b/Let's produce the block.test_audio_routing.c new file mode 100644 index 0000000..9e6c315 --- /dev/null +++ b/Let's produce the block.test_audio_routing.c @@ -0,0 +1,180 @@ +/* + * test_audio_routing.c + * + * External test program for the looper application. + * Connects to JACK, sends a test tone to the application's audio_in ports, + * captures the output from audio_out ports, and verifies that audio is flowing. + * + * Compile with: + * gcc -o test_audio_routing test_audio_routing.c -ljack -lm + * + * Run after the looper application is running and JACK is active. + */ + +#include +#include +#include +#include +#include +#include + +#define TEST_DURATION_SECONDS 2 +#define SAMPLE_RATE 48000 +#define NUM_CHANNELS 2 // test first two channels + +static jack_client_t *client = NULL; +static jack_port_t *test_out_ports[NUM_CHANNELS]; +static jack_port_t *test_in_ports[NUM_CHANNELS]; +static float *captured_output[NUM_CHANNELS]; +static jack_nframes_t captured_frames = 0; +static volatile int running = 1; + +/* Generate a sine wave sample */ +static float sine_sample(jack_nframes_t frame, float freq, float sample_rate) { + return sinf(2.0f * M_PI * freq * frame / sample_rate); +} + +/* Process callback: generate test tone on output ports, capture input ports */ +static int process(jack_nframes_t nframes, void *arg) { + (void)arg; + + /* Generate test tone on each output port */ + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *)jack_port_get_buffer(test_out_ports[ch], nframes); + for (jack_nframes_t i = 0; i < nframes; i++) { + /* 440 Hz sine wave, amplitude 0.5 */ + out[i] = 0.5f * sine_sample(captured_frames + i, 440.0f, SAMPLE_RATE); + } + } + + /* Capture input ports */ + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + jack_default_audio_sample_t *in = + (jack_default_audio_sample_t *)jack_port_get_buffer(test_in_ports[ch], nframes); + /* Append to captured buffer */ + if (captured_output[ch]) { + memcpy(captured_output[ch] + captured_frames, in, nframes * sizeof(float)); + } + } + captured_frames += nframes; + + return 0; +} + +/* Shutdown callback */ +static void shutdown(void *arg) { + (void)arg; + running = 0; +} + +int main(void) { + jack_status_t status; + const char *client_name = "test_audio_routing"; + + client = jack_client_open(client_name, JackNullOption, &status, NULL); + if (!client) { + fprintf(stderr, "Failed to open JACK client, status = 0x%2.0x\n", status); + return 1; + } + + /* Register ports */ + char port_name[64]; + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + snprintf(port_name, sizeof(port_name), "test_out_%d", ch); + test_out_ports[ch] = jack_port_register(client, port_name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (!test_out_ports[ch]) { + fprintf(stderr, "Failed to register output port %d\n", ch); + jack_client_close(client); + return 1; + } + + snprintf(port_name, sizeof(port_name), "test_in_%d", ch); + test_in_ports[ch] = jack_port_register(client, port_name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + if (!test_in_ports[ch]) { + fprintf(stderr, "Failed to register input port %d\n", ch); + jack_client_close(client); + return 1; + } + } + + /* Set process callback */ + jack_set_process_callback(client, process, NULL); + jack_on_shutdown(client, shutdown, NULL); + + /* Allocate capture buffers */ + jack_nframes_t sample_rate = jack_get_sample_rate(client); + jack_nframes_t total_frames = sample_rate * TEST_DURATION_SECONDS; + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + captured_output[ch] = (float *)calloc(total_frames, sizeof(float)); + if (!captured_output[ch]) { + fprintf(stderr, "Memory allocation failed\n"); + jack_client_close(client); + return 1; + } + } + + /* Activate client */ + if (jack_activate(client) != 0) { + fprintf(stderr, "Failed to activate JACK client\n"); + jack_client_close(client); + return 1; + } + + /* Connect to the looper application's ports */ + const char *app_client_name = "looper"; /* adjust if your app uses a different name */ + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + char src_port[128], dst_port[128]; + snprintf(src_port, sizeof(src_port), "%s:test_out_%d", client_name, ch); + snprintf(dst_port, sizeof(dst_port), "%s:audio_in_%d", app_client_name, ch); + if (jack_connect(client, src_port, dst_port) != 0) { + fprintf(stderr, "Warning: could not connect %s -> %s\n", src_port, dst_port); + } + + snprintf(src_port, sizeof(src_port), "%s:audio_out_%d", app_client_name, ch); + snprintf(dst_port, sizeof(dst_port), "%s:test_in_%d", client_name, ch); + if (jack_connect(client, src_port, dst_port) != 0) { + fprintf(stderr, "Warning: could not connect %s -> %s\n", src_port, dst_port); + } + } + + /* Run for the test duration */ + printf("Running test for %d seconds...\n", TEST_DURATION_SECONDS); + sleep(TEST_DURATION_SECONDS); + + /* Deactivate and close */ + jack_deactivate(client); + jack_client_close(client); + + /* Analyze captured output */ + int pass = 1; + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + float max_val = 0.0f; + for (jack_nframes_t i = 0; i < total_frames; i++) { + float abs_val = fabsf(captured_output[ch][i]); + if (abs_val > max_val) max_val = abs_val; + } + printf("Channel %d: max output amplitude = %f\n", ch, max_val); + if (max_val < 0.001f) { + printf("FAIL: Channel %d output is near zero (no audio flowing)\n", ch); + pass = 0; + } + } + + /* Cleanup */ + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + free(captured_output[ch]); + } + + if (pass) { + printf("PASS: Audio routing is working correctly.\n"); + return 0; + } else { + printf("FAIL: Audio routing test failed.\n"); + return 1; + } +}