feat: add external JACK test program for audio routing verification

Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-04 17:38:36 +00:00
parent ea23febdde
commit 3df9a02f6d

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <jack/jack.h>
#include <unistd.h>
#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;
}
}