From ce2dd7be763e8b1d9b7a896dac00fdf05eb0ac9c Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Tue, 12 May 2026 19:32:10 +0000 Subject: [PATCH] fix: make channel state variables atomic to eliminate data races Co-authored-by: aider (deepseek/deepseek-reasoner) --- src/channel.h | 6 +++--- src/looper.c | 36 ++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/channel.h b/src/channel.h index 89580bc..d3cd051 100644 --- a/src/channel.h +++ b/src/channel.h @@ -19,11 +19,11 @@ typedef enum { struct channel_t { atomic_int state; - int prev_state; + atomic_int prev_state; float loop_buffer[LOOP_BUF_SIZE]; atomic_int loop_count; - int record_pos; - int playback_pos; + atomic_int record_pos; + atomic_int playback_pos; atomic_int active; jack_port_t *audio_in; jack_port_t *audio_out; diff --git a/src/looper.c b/src/looper.c index cbffec6..c80ca27 100644 --- a/src/looper.c +++ b/src/looper.c @@ -68,16 +68,16 @@ int process_callback(jack_nframes_t nframes, void *arg) { int state = atomic_load(&channels[c].state); - if (state != channels[c].prev_state) { + if (state != atomic_load(&channels[c].prev_state)) { switch (state) { case STATE_RECORD: - channels[c].record_pos = 0; + atomic_store(&channels[c].record_pos, 0); atomic_store(&channels[c].loop_count, 0); break; case STATE_LOOPING: - if (channels[c].prev_state == STATE_RECORD && channels[c].record_pos > 0) - atomic_store(&channels[c].loop_count, channels[c].record_pos); - channels[c].playback_pos = 0; + if (atomic_load(&channels[c].prev_state) == STATE_RECORD && atomic_load(&channels[c].record_pos) > 0) + atomic_store(&channels[c].loop_count, atomic_load(&channels[c].record_pos)); + atomic_store(&channels[c].playback_pos, 0); break; default: break; @@ -91,9 +91,9 @@ int process_callback(jack_nframes_t nframes, void *arg) { float *f_out = (float *)out; const float *f_in = (const float *)in; for (i = 0; i < nframes; i++) { - if (channels[c].record_pos < LOOP_BUF_SIZE) - channels[c].loop_buffer[channels[c].record_pos++] = - f_in[i]; + int rp = atomic_fetch_add(&channels[c].record_pos, 1); + if (rp < LOOP_BUF_SIZE) + channels[c].loop_buffer[rp] = f_in[i]; f_out[i] = f_in[i]; } } else { @@ -106,9 +106,9 @@ int process_callback(jack_nframes_t nframes, void *arg) { if (lc > 0) { float *outf = (float *)out; for (i = 0; i < nframes; i++) { - outf[i] = channels[c].loop_buffer[channels[c].playback_pos]; - channels[c].playback_pos = - (channels[c].playback_pos + 1) % lc; + int pp = atomic_load(&channels[c].playback_pos); + outf[i] = channels[c].loop_buffer[pp]; + atomic_store(&channels[c].playback_pos, (pp + 1) % lc); } } else { memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); @@ -137,7 +137,7 @@ int process_callback(jack_nframes_t nframes, void *arg) { } } - channels[c].prev_state = state; + atomic_store(&channels[c].prev_state, state); } /* MIDI clock events – affect channel 0 only */ @@ -197,10 +197,10 @@ int looper_init(jack_client_t *client) { /* channel 0 */ channels[0].active = 1; atomic_store(&channels[0].state, STATE_IDLE); - channels[0].prev_state = -1; + atomic_store(&channels[0].prev_state, -1); channels[0].loop_count = 0; - channels[0].record_pos = 0; - channels[0].playback_pos = 0; + atomic_store(&channels[0].record_pos, 0); + atomic_store(&channels[0].playback_pos, 0); atomic_store_explicit(&channels[0].save_ring, NULL, memory_order_release); channels[0].audio_in = jack_port_register( @@ -312,10 +312,10 @@ void looper_process_commands(jack_client_t *client) { if (frames > LOOP_BUF_SIZE) frames = LOOP_BUF_SIZE; memcpy(channels[0].loop_buffer, buf, frames * sizeof(float)); atomic_store(&channels[0].loop_count, (int)frames); - channels[0].record_pos = 0; - channels[0].playback_pos = 0; + atomic_store(&channels[0].record_pos, 0); + atomic_store(&channels[0].playback_pos, 0); atomic_store(&channels[0].state, STATE_LOOPING); - channels[0].prev_state = -1; + atomic_store(&channels[0].prev_state, -1); free(buf); } else { fprintf(stderr, "Failed to load loop.wav\n");