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:
@@ -835,6 +835,244 @@ static int test_remove_channel(void) {
|
||||
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) {
|
||||
/* 1. binary must exist */
|
||||
@@ -886,6 +1124,18 @@ int main(void) {
|
||||
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) {
|
||||
fprintf(stderr, "%d test(s) FAILED\n", failures);
|
||||
return 1;
|
||||
|
||||
Reference in New Issue
Block a user