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:
180
Let's produce the block.test_audio_routing.c
Normal file
180
Let's produce the block.test_audio_routing.c
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user