feat: add WAV file loading, saving, and dedicated I/O threads

Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-11 20:58:00 +00:00
parent 72839a9e5f
commit 6b490ed739

View File

@@ -835,6 +835,244 @@ static int test_remove_channel(void) {
return 0; return 0;
} }
/* ------------------------------------------------------------
* Helper: generate a simple 440 Hz WAV file for load tests
* ------------------------------------------------------------ */
static int generate_test_wav(const char *path, unsigned sample_rate, unsigned duration_frames) {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return -1;
unsigned data_bytes = duration_frames * 2;
unsigned file_size = 44 + data_bytes;
unsigned char header[44];
memset(header, 0, 44);
memcpy(header, "RIFF", 4);
header[4] = file_size & 0xff; header[5] = (file_size>>8)&0xff;
header[6] = (file_size>>16)&0xff; header[7] = (file_size>>24)&0xff;
memcpy(header+8, "WAVE", 4);
memcpy(header+12, "fmt ", 4);
header[16]=16; header[17]=0; header[18]=0; header[19]=0;
header[20]=1; header[21]=0; /* PCM */
header[22]=1; header[23]=0; /* mono */
header[24]= sample_rate & 0xff; header[25]=(sample_rate>>8)&0xff;
header[26]=(sample_rate>>16)&0xff; header[27]=(sample_rate>>24)&0xff;
unsigned br = sample_rate * 2;
header[28]= br & 0xff; header[29]=(br>>8)&0xff;
header[30]=(br>>16)&0xff; header[31]=(br>>24)&0xff;
header[32]=2; header[33]=0;
header[34]=16; header[35]=0;
memcpy(header+36, "data", 4);
header[40]= data_bytes & 0xff; header[41]=(data_bytes>>8)&0xff;
header[42]=(data_bytes>>16)&0xff; header[43]=(data_bytes>>24)&0xff;
if (write(fd, header, 44) != 44) { close(fd); return -1; }
for (unsigned i = 0; i < duration_frames; i++) {
float sample = sinf(2.0f * (float)M_PI * 440.0f * i / sample_rate);
int16_t s = (int16_t)(sample * 32767);
if (write(fd, &s, 2) != 2) { close(fd); return -1; }
}
close(fd);
return 0;
}
/* ------------------------------------------------------------
* Test: load WAV file (note 70 under control key)
* ------------------------------------------------------------ */
static int test_wav_load(void) {
printf("Test: load WAV file into channel 0 and detect playback\n");
if (generate_test_wav("loop.wav", 48000, 48000) != 0) {
fprintf(stderr, " FAIL: could not create test WAV\n");
return 1;
}
pid_t pid = start_looper();
if (pid < 0) { unlink("loop.wav"); return 1; }
jack_client_t *client;
jack_status_t status;
client = jack_client_open("test_wav_load", JackNoStartServer, &status);
if (!client) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
unlink("loop.wav");
fprintf(stderr, " SKIP: no JACK\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);
unlink("loop.wav");
return 1;
}
safe_usleep(200000);
if (jack_connect(client, "test_wav_load:out", "looper:input") ||
jack_connect(client, "looper:output", "test_wav_load:in")) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
unlink("loop.wav");
return 1;
}
/* send control key + note 70 to trigger load */
if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
unlink("loop.wav"); return 1;
}
safe_usleep(200000);
if (send_jack_note_on("looper:control", 70, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
unlink("loop.wav"); return 1;
}
safe_usleep(500000); /* give time for load to complete */
/* listen for the loop */
int sr = jack_get_sample_rate(client);
continuous_sine = 0;
beep_remaining = 0;
bursts = 0;
prev_above = 0;
passthrough_output_port = audio_out;
passthrough_input_port = audio_in;
passthrough_phase = 0.0f;
passthrough_freq = 440.0f;
passthrough_sample_rate = sr;
passthrough_total_samples = 0;
passthrough_sum_sq = 0.0;
passthrough_done = 0;
jack_set_process_callback(client, passthrough_process, NULL);
if (jack_activate(client)) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
unlink("loop.wav");
return 1;
}
safe_usleep(3000000);
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
unlink("loop.wav");
int got_bursts = bursts;
printf(" detected bursts: %d\n", got_bursts);
if (got_bursts < 3) {
fprintf(stderr, " FAIL: expected ≥3 bursts from loaded loop, got %d\n", got_bursts);
return 1;
}
printf(" PASS (loaded loop plays)\n");
return 0;
}
/* ------------------------------------------------------------
* Test: save WAV file (note 71 under control key)
* ------------------------------------------------------------ */
static int test_wav_save(void) {
printf("Test: save WAV file from loop\n");
pid_t pid = start_looper();
if (pid < 0) return 1;
jack_client_t *client;
jack_status_t status;
client = jack_client_open("test_wav_save", JackNoStartServer, &status);
if (!client) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " SKIP: no JACK\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;
}
safe_usleep(200000);
if (jack_connect(client, "test_wav_save:out", "looper:input") ||
jack_connect(client, "looper:output", "test_wav_save:in")) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
/* record a beep: send note 1 (toggle channel 0) */
if (send_jack_note_on("looper:control", 1, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(200000);
/* start generating a beep */
int sr = jack_get_sample_rate(client);
continuous_sine = 0;
beep_remaining = (int)(0.5f * sr);
bursts = 0; prev_above = 0;
passthrough_output_port = audio_out;
passthrough_input_port = audio_in;
passthrough_phase = 0.0f;
passthrough_freq = 440.0f;
passthrough_sample_rate = sr;
passthrough_total_samples = 0;
passthrough_sum_sq = 0.0;
passthrough_done = 0;
jack_set_process_callback(client, passthrough_process, NULL);
if (jack_activate(client)) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(800000);
/* toggle again to stop recording and start looping */
if (send_jack_note_on("looper:control", 1, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(500000);
/* send control key + note 71 to save */
if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(200000);
if (send_jack_note_on("looper:control", 71, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(2000000);
/* check save.wav exists and has data */
int fd = open("save.wav", O_RDONLY);
if (fd < 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: save.wav not created\n");
return 1;
}
unsigned char hdr[44];
if (read(fd, hdr, 44) != 44) {
close(fd); unlink("save.wav");
jack_deactivate(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: short header\n");
return 1;
}
unsigned data_bytes = hdr[40] | (hdr[41]<<8) | (hdr[42]<<16) | (hdr[43]<<24);
close(fd);
if (data_bytes == 0) {
unlink("save.wav");
jack_deactivate(client); jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: empty save.wav\n");
return 1;
}
printf(" save.wav data size: %u bytes\n", data_bytes);
unlink("save.wav");
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
printf(" PASS (save.wav created)\n");
return 0;
}
int main(void) { int main(void) {
/* 1. binary must exist */ /* 1. binary must exist */
@@ -886,6 +1124,18 @@ int main(void) {
failures++; failures++;
} }
/* 10. Test WAV load */
if (test_wav_load() != 0) {
fprintf(stderr, " FAILED\n");
failures++;
}
/* 11. Test WAV save */
if (test_wav_save() != 0) {
fprintf(stderr, " FAILED\n");
failures++;
}
if (failures > 0) { if (failures > 0) {
fprintf(stderr, "%d test(s) FAILED\n", failures); fprintf(stderr, "%d test(s) FAILED\n", failures);
return 1; return 1;