xtc/tests/integration/test_jack.c

119 lines
5.7 KiB
C
Raw Normal View History

2026-10-26 23:32:04 +01:00
#define _POSIX_C_SOURCE 200809L
#include "xtc/xtc_backend.h"
#include <jack/jack.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
/* Optional live test. The only route is xtc's output -> this test's input.
* No ports are connected to hardware. Never changes server configuration. */
typedef struct {
jack_port_t *input;
unsigned int blocks, bad, low, high, lost;
} probe_state;
static int probe_process(jack_nframes_t frames, void *context)
{
probe_state *probe = context;
const float *input = jack_port_get_buffer(probe->input, frames);
jack_nframes_t frame;
float peak = 0.0f;
if (input == NULL) { __atomic_store_n(&probe->bad, 1U, __ATOMIC_RELAXED); return 0; }
for (frame = 0U; frame < frames; ++frame) {
float sample = input[frame];
if (!isfinite(sample) || fabsf(sample) > 0.201f)
__atomic_store_n(&probe->bad, 1U, __ATOMIC_RELAXED);
if (fabsf(sample) > peak) peak = fabsf(sample);
}
if (peak > 0.09f && peak < 0.101f) __atomic_store_n(&probe->low, 1U, __ATOMIC_RELAXED);
if (peak > 0.19f) __atomic_store_n(&probe->high, 1U, __ATOMIC_RELAXED);
(void)__atomic_fetch_add(&probe->blocks, 1U, __ATOMIC_RELAXED);
return 0;
}
static void probe_shutdown(void *context)
{ probe_state *probe = context; __atomic_store_n(&probe->lost, 1U, __ATOMIC_RELEASE); }
static xtc_result tone(float gain, xtc_graph **out_graph)
{
xtc_result result;
size_t osc, level, output;
result = xtc_graph_create(3U, out_graph);
if (result != XTC_OK) return result;
result = xtc_graph_add_oscillator(*out_graph, 440.0f, &osc);
if (result == XTC_OK) result = xtc_graph_add_gain(*out_graph, gain, &level);
if (result == XTC_OK) result = xtc_graph_add_output(*out_graph, &output);
if (result == XTC_OK) result = xtc_graph_connect(*out_graph, osc, level);
if (result == XTC_OK) result = xtc_graph_connect(*out_graph, level, output);
return result;
}
static int count_port(const char *name, void *context)
{ (void)name; ++*(size_t *)context; return 0; }
#define REQUIRE(c) do { if (!(c)) { fprintf(stderr,"FAIL %s:%d: %s\n",__FILE__,__LINE__,#c); goto cleanup; } } while (0)
int main(int argc, char **argv)
{
long seconds = 5;
char *end;
char name[64];
xtc_backend_config config = {XTC_BACKEND_JACK, name, 4096U, 1U};
xtc_backend *backend = NULL;
xtc_backend_status status;
xtc_graph *low = NULL, *high = NULL;
jack_client_t *probe_client = NULL;
jack_status_t jack_status = 0;
probe_state probe = {0};
struct timespec pause = {0, 50000000L};
size_t port_count = 0U;
long tick;
int result = EXIT_FAILURE;
if (argc > 2) { fprintf(stderr,"Usage: %s [seconds: 1..300]\n",argv[0]); return EXIT_FAILURE; }
if (argc == 2) {
seconds = strtol(argv[1], &end, 10);
if (*end != '\0' || seconds < 1 || seconds > 300) return EXIT_FAILURE;
}
(void)snprintf(name, sizeof(name), "xtc-test-%ld", (long)getpid());
REQUIRE(xtc_backend_open(&config, &backend) == XTC_OK);
REQUIRE(xtc_backend_get_status(backend, &status) == XTC_OK);
printf("JACK: %u Hz, %u frames, mono, max 4096 frames\n", status.sample_rate, status.buffer_frames);
REQUIRE(xtc_backend_visit_ports(backend, XTC_PORT_INPUT, count_port, &port_count) == XTC_OK);
REQUIRE(port_count > 0U);
REQUIRE(tone(0.1f, &low) == XTC_OK && tone(0.2f, &high) == XTC_OK);
probe_client = jack_client_open("xtc-test-probe", JackNoStartServer, &jack_status);
REQUIRE(probe_client != NULL);
probe.input = jack_port_register(probe_client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0U);
REQUIRE(probe.input != NULL);
REQUIRE(jack_set_process_callback(probe_client, probe_process, &probe) == 0);
jack_on_shutdown(probe_client, probe_shutdown, &probe);
REQUIRE(jack_activate(probe_client) == 0);
REQUIRE(xtc_backend_submit(backend, low) == XTC_OK);
REQUIRE(xtc_backend_start(backend) == XTC_OK);
REQUIRE(xtc_backend_connect(backend, XTC_PORT_OUTPUT, jack_port_name(probe.input)) == XTC_OK);
for (tick = 0; tick < seconds * 20; ++tick) {
(void)nanosleep(&pause, NULL);
REQUIRE(!__atomic_load_n(&probe.lost, __ATOMIC_ACQUIRE));
REQUIRE(xtc_backend_get_status(backend, &status) == XTC_OK);
REQUIRE(status.running && !status.sample_rate_changed && status.process_result == XTC_OK);
xtc_backend_collect(backend);
if (tick % 8 == 7) {
xtc_result submitted = xtc_backend_submit(backend, tick % 16 == 7 ? high : low);
REQUIRE(submitted == XTC_OK || submitted == XTC_ERR_CAPACITY);
}
}
REQUIRE(__atomic_load_n(&probe.bad, __ATOMIC_RELAXED) == 0U);
REQUIRE(__atomic_load_n(&probe.low, __ATOMIC_RELAXED) && __atomic_load_n(&probe.high, __ATOMIC_RELAXED));
REQUIRE(status.processed_blocks > 0U);
REQUIRE(xtc_backend_disconnect(backend, XTC_PORT_OUTPUT, jack_port_name(probe.input)) == XTC_OK);
for (tick = 0; tick < 10; ++tick) {
REQUIRE(xtc_backend_stop(backend) == XTC_OK);
REQUIRE(xtc_backend_start(backend) == XTC_OK);
(void)nanosleep(&pause, NULL);
}
REQUIRE(xtc_backend_stop(backend) == XTC_OK);
printf("PASS: %ld seconds, %u engine blocks, %u captured blocks, both gains observed, %u xruns; 10 restart cycles\n",
seconds, status.processed_blocks, __atomic_load_n(&probe.blocks, __ATOMIC_RELAXED), status.xruns);
result = EXIT_SUCCESS;
cleanup:
if (xtc_backend_close(backend) != XTC_OK) { fprintf(stderr,"JACK client close failed\n"); result = EXIT_FAILURE; }
if (probe_client != NULL && jack_client_close(probe_client) != 0) result = EXIT_FAILURE;
xtc_graph_destroy(low); xtc_graph_destroy(high);
return result;
}