refactor: replace writer thread with synchronous save and fix ring buffer memory ordering

This commit is contained in:
Loic Coenen
2026-05-18 17:35:31 +00:00
committed by Loic Coenen (aider)
parent 10e47e6c0c
commit f38797fe0a
22 changed files with 291 additions and 1369 deletions

View File

@@ -11,6 +11,7 @@
#include <jack/midiport.h>
#include <math.h>
#include <time.h>
#include <sndfile.h>
/* static variables for passthrough test */
static jack_port_t *passthrough_output_port = NULL;
@@ -333,20 +334,12 @@ static int test_looper_looping(void) {
return 1;
}
/* first noteon: IDLE -> RECORD */
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(500000); /* allow state to change (500ms) */
/* connect audio and activate immediately */
int sr = jack_get_sample_rate(client);
continuous_sine = 0; /* disable continuous tone */
beep_remaining = (int)(0.1f * sr); /* 0.1 second beep */
continuous_sine = 0;
beep_remaining = (int)(3.0f * sr); /* 3 sec beep covers entire recording */
bursts = 0;
prev_above = 0;
passthrough_output_port = audio_out;
passthrough_input_port = audio_in;
passthrough_phase = 0.0f;
@@ -355,7 +348,6 @@ static int test_looper_looping(void) {
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_client_close(client);
@@ -363,19 +355,23 @@ static int test_looper_looping(void) {
return 1;
}
safe_usleep(150000); /* let beep start */
/* ensure beep is fully captured */
safe_usleep(800000); /* 0.8s after start of beep */
/* first noteon: IDLE -> RECORD */
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(3000000); /* 3s to capture beep */
/* second noteon: RECORD -> LOOPING */
if (send_jack_note_on("looper:control", 1, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
/* wait enough time for several loops (4 seconds to be safe) */
safe_usleep(4000000);
/* wait for several loop repetitions */
safe_usleep(8000000); /* 8 seconds listen */
jack_deactivate(client);
jack_client_close(client);
@@ -504,7 +500,7 @@ static int test_control_key_modifier(void) {
/* Wait for looper to enter RECORD and detect audio */
int sr = jack_get_sample_rate(client);
continuous_sine = 0;
beep_remaining = (int)(0.1f * sr); /* 0.1 second beep */
beep_remaining = (int)(0.5f * sr); /* 0.5 sec beep (was 0.1) */
bursts = 0;
prev_above = 0;
passthrough_output_port = audio_out;
@@ -517,26 +513,30 @@ static int test_control_key_modifier(void) {
passthrough_done = 0;
jack_set_process_callback(client, passthrough_process, NULL);
if (jack_activate(client)) {
fprintf(stderr, " FAIL: cannot activate test client\n");
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(200000); /* allow beep */
/* send note 62 again under control key to move RECORD->LOOPING */
safe_usleep(500000); /* allow beep to start */
/* second note 64 + note 62 to move RECORD → LOOPING */
if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: control key resend\n");
return 1;
}
safe_usleep(200000);
safe_usleep(500000);
if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: send note 62 for loop\n");
return 1;
}
safe_usleep(2000000);
safe_usleep(5000000); /* listen for 5 seconds */
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM);
@@ -619,7 +619,7 @@ static int test_bind_channel(void) {
/* Wait and detect bursts as before */
int sr = jack_get_sample_rate(client);
continuous_sine = 0;
beep_remaining = (int)(0.1f * sr);
beep_remaining = (int)(0.5f * sr); /* 0.5 sec beep (was 0.1) */
bursts = 0;
prev_above = 0;
passthrough_output_port = audio_out;
@@ -632,26 +632,30 @@ static int test_bind_channel(void) {
passthrough_done = 0;
jack_set_process_callback(client, passthrough_process, NULL);
if (jack_activate(client)) {
fprintf(stderr, " FAIL: cannot activate test client\n");
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(200000); /* allow beep */
/* send control+note62 again to move RECORD->LOOPING */
safe_usleep(500000); /* allow beep to start */
/* second note 64 + note 62 to move RECORD → LOOPING */
if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: control key for loop\n");
return 1;
}
safe_usleep(200000);
safe_usleep(500000);
if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: toggle for loop\n");
return 1;
}
safe_usleep(2000000);
safe_usleep(5000000); /* listen for 5 seconds */
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM);
@@ -749,7 +753,7 @@ static int test_bind_unbind(void) {
/* Wait for beep and loop */
int sr = jack_get_sample_rate(client);
continuous_sine = 0;
beep_remaining = (int)(0.1f * sr);
beep_remaining = (int)(0.5f * sr); /* 0.5 sec beep (was 0.1) */
bursts = 0;
prev_above = 0;
passthrough_output_port = audio_out;
@@ -766,7 +770,7 @@ static int test_bind_unbind(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(200000); /* allow beep */
safe_usleep(1000000); /* allow recording to start before we set beep */
/* second control+62 -> loop */
if (send_jack_note_on("looper:control", 64, 127) != 0) {
jack_client_close(client);
@@ -774,7 +778,7 @@ static int test_bind_unbind(void) {
fprintf(stderr, " FAIL: control key for loop\n");
return 1;
}
safe_usleep(200000);
safe_usleep(2000000); /* give time to record beep */
if (send_jack_note_on("looper:control", 62, 127) != 0) {
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
@@ -882,38 +886,17 @@ static int test_remove_channel(void) {
* 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);
unsigned chunk_size = file_size - 8;
header[4] = chunk_size & 0xff; header[5] = (chunk_size>>8)&0xff;
header[6] = (chunk_size>>16)&0xff; header[7] = (chunk_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; }
SF_INFO info;
info.samplerate = sample_rate;
info.channels = 1;
info.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
SNDFILE *sf = sf_open(path, SFM_WRITE, &info);
if (!sf) 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; }
sf_writef_float(sf, &sample, 1);
}
close(fd);
sf_close(sf);
return 0;
}
@@ -1062,15 +1045,17 @@ static int test_wav_save(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(800000);
/* stop recording (cycle again) */
safe_usleep(3000000); /* record for 3s (ensure enough beep) */
/* Send second record command to transition RECORD → LOOPING */
if (send_fifo_command("record 0") != 0) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(500000);
safe_usleep(1000000); /* give time for state change and loop_count to be set */
/* save */
if (send_fifo_command("save") != 0) {
jack_deactivate(client);
@@ -1078,16 +1063,28 @@ static int test_wav_save(void) {
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
return 1;
}
safe_usleep(2000000);
/* check save.wav */
int fd = open("save.wav", O_RDONLY);
if (fd < 0) {
safe_usleep(8000000); /* wait for synchronous save to complete (8s) */
/* check save.wav with retries */
int saved = 0;
for (int retry = 0; retry < 5; retry++) {
int fd = open("save.wav", O_RDONLY);
if (fd >= 0) {
saved = 1;
close(fd);
break;
}
safe_usleep(1000000);
}
if (!saved) {
jack_deactivate(client);
jack_client_close(client);
kill(pid, SIGTERM); waitpid(pid, NULL, 0);
fprintf(stderr, " FAIL: save.wav not created\n");
return 1;
}
/* verify header */
int fd = open("save.wav", O_RDONLY);
unsigned char hdr[44];
if (read(fd, hdr, 44) != 44) {
close(fd); unlink("save.wav");