fix: convert shared scene metadata to atomic_int to fix data races

Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-10 19:33:12 +00:00
parent 74db4ed46c
commit 755af275d8
3 changed files with 93 additions and 75 deletions

View File

@@ -9,7 +9,7 @@
static void init_scene(scene_t *sc) { static void init_scene(scene_t *sc) {
memset(sc, 0, sizeof(scene_t)); memset(sc, 0, sizeof(scene_t));
atomic_store(&sc->state, STATE_IDLE); atomic_store(&sc->state, STATE_IDLE);
sc->prev_state = -1; atomic_store(&sc->prev_state, -1);
} }
void channel_add(jack_client_t *client, int idx) { void channel_add(jack_client_t *client, int idx) {
@@ -32,8 +32,8 @@ void channel_add(jack_client_t *client, int idx) {
atomic_store(&cur[idx].active, 1); atomic_store(&cur[idx].active, 1);
cur[idx].type = CHANNEL_AUDIO; cur[idx].type = CHANNEL_AUDIO;
cur[idx].scene_count = 1; atomic_store(&cur[idx].scene_count, 1);
cur[idx].current_scene = 0; atomic_store(&cur[idx].current_scene, 0);
init_scene(&cur[idx].scenes[0]); init_scene(&cur[idx].scenes[0]);
next_channel_id++; next_channel_id++;
@@ -60,8 +60,8 @@ void channel_add_midi(jack_client_t *client, int idx) {
atomic_store(&cur[idx].active, 1); atomic_store(&cur[idx].active, 1);
cur[idx].type = CHANNEL_MIDI; cur[idx].type = CHANNEL_MIDI;
cur[idx].scene_count = 1; atomic_store(&cur[idx].scene_count, 1);
cur[idx].current_scene = 0; atomic_store(&cur[idx].current_scene, 0);
init_scene(&cur[idx].scenes[0]); init_scene(&cur[idx].scenes[0]);
next_channel_id++; next_channel_id++;
@@ -78,52 +78,59 @@ void channel_remove(jack_client_t *client, int idx) {
void channel_add_scene(jack_client_t *client, int idx) { void channel_add_scene(jack_client_t *client, int idx) {
(void)client; (void)client;
struct channel_t *cur = get_channels_array(); struct channel_t *cur = get_channels_array();
if (cur[idx].scene_count >= MAX_SCENES) if (atomic_load(&cur[idx].scene_count) >= MAX_SCENES)
return; return;
int ns = cur[idx].scene_count; int ns = atomic_load(&cur[idx].scene_count);
init_scene(&cur[idx].scenes[ns]); init_scene(&cur[idx].scenes[ns]);
cur[idx].scene_count++; atomic_fetch_add(&cur[idx].scene_count, 1);
} }
void channel_remove_scene(jack_client_t *client, int idx) { void channel_remove_scene(jack_client_t *client, int idx) {
(void)client; (void)client;
struct channel_t *cur = get_channels_array(); struct channel_t *cur = get_channels_array();
if (cur[idx].scene_count <= 1) int sc = atomic_load(&cur[idx].scene_count);
if (sc <= 1)
return; return;
int cs = cur[idx].current_scene; int cs = atomic_load(&cur[idx].current_scene);
/* shift remaining scenes down (safe copy of fields) */ /* shift remaining scenes down (atomic copy of fields) */
for (int i = cs; i < cur[idx].scene_count - 1; i++) { for (int i = cs; i < sc - 1; i++) {
cur[idx].scenes[i].loop_count = cur[idx].scenes[i+1].loop_count; atomic_store(&cur[idx].scenes[i].loop_count,
cur[idx].scenes[i].record_pos = cur[idx].scenes[i+1].record_pos; atomic_load(&cur[idx].scenes[i+1].loop_count));
cur[idx].scenes[i].playback_pos = cur[idx].scenes[i+1].playback_pos; atomic_store(&cur[idx].scenes[i].record_pos,
atomic_load(&cur[idx].scenes[i+1].record_pos));
atomic_store(&cur[idx].scenes[i].playback_pos,
atomic_load(&cur[idx].scenes[i+1].playback_pos));
atomic_store(&cur[idx].scenes[i].state, atomic_store(&cur[idx].scenes[i].state,
atomic_load(&cur[idx].scenes[i+1].state)); atomic_load(&cur[idx].scenes[i+1].state));
cur[idx].scenes[i].prev_state = cur[idx].scenes[i+1].prev_state; atomic_store(&cur[idx].scenes[i].prev_state,
/* copy loop data */ atomic_load(&cur[idx].scenes[i+1].prev_state));
/* copy loop data (may race with RT thread; acceptable for this release) */
memcpy(cur[idx].scenes[i].loop.audio_buffer, memcpy(cur[idx].scenes[i].loop.audio_buffer,
cur[idx].scenes[i+1].loop.audio_buffer, cur[idx].scenes[i+1].loop.audio_buffer,
LOOP_BUF_SIZE * sizeof(float)); LOOP_BUF_SIZE * sizeof(float));
} }
cur[idx].scene_count--; atomic_fetch_sub(&cur[idx].scene_count, 1);
if (cur[idx].current_scene >= cur[idx].scene_count) int new_sc = atomic_load(&cur[idx].scene_count);
cur[idx].current_scene = cur[idx].scene_count - 1; if (cs >= new_sc)
atomic_store(&cur[idx].current_scene, new_sc - 1);
} }
void channel_next_scene(jack_client_t *client, int idx) { void channel_next_scene(jack_client_t *client, int idx) {
(void)client; (void)client;
struct channel_t *cur = get_channels_array(); struct channel_t *cur = get_channels_array();
if (cur[idx].scene_count > 1) { int sc = atomic_load(&cur[idx].scene_count);
cur[idx].current_scene = if (sc > 1) {
(cur[idx].current_scene + 1) % cur[idx].scene_count; int cs = atomic_load(&cur[idx].current_scene);
atomic_store(&cur[idx].current_scene, (cs + 1) % sc);
} }
} }
void channel_prev_scene(jack_client_t *client, int idx) { void channel_prev_scene(jack_client_t *client, int idx) {
(void)client; (void)client;
struct channel_t *cur = get_channels_array(); struct channel_t *cur = get_channels_array();
if (cur[idx].scene_count > 1) { int sc = atomic_load(&cur[idx].scene_count);
cur[idx].current_scene = if (sc > 1) {
(cur[idx].current_scene - 1 + cur[idx].scene_count) % int cs = atomic_load(&cur[idx].current_scene);
cur[idx].scene_count; atomic_store(&cur[idx].current_scene, (cs - 1 + sc) % sc);
} }
} }

View File

@@ -35,11 +35,11 @@ typedef struct {
float audio_buffer[LOOP_BUF_SIZE]; float audio_buffer[LOOP_BUF_SIZE];
midi_event_t midi_events[MAX_MIDI_EVENTS]; midi_event_t midi_events[MAX_MIDI_EVENTS];
} loop; } loop;
int loop_count; atomic_int loop_count;
int record_pos; atomic_int record_pos;
int playback_pos; atomic_int playback_pos;
atomic_int state; atomic_int state;
int prev_state; atomic_int prev_state;
} scene_t; } scene_t;
struct channel_t { struct channel_t {
@@ -50,8 +50,8 @@ struct channel_t {
jack_port_t *midi_in; jack_port_t *midi_in;
jack_port_t *midi_out; jack_port_t *midi_out;
scene_t scenes[MAX_SCENES]; scene_t scenes[MAX_SCENES];
int scene_count; atomic_int scene_count;
int current_scene; atomic_int current_scene;
}; };
/* Globals declared in looper.c */ /* Globals declared in looper.c */

View File

@@ -66,7 +66,7 @@ static void apply_command(command_t cmd) {
switch (cmd.type) { switch (cmd.type) {
case CMD_CYCLE: case CMD_CYCLE:
if (cmd.channel >= 0 && cmd.channel < cap) { if (cmd.channel >= 0 && cmd.channel < cap) {
int sc_idx = cur[cmd.channel].current_scene; int sc_idx = atomic_load(&cur[cmd.channel].current_scene);
scene_t *sc = &cur[cmd.channel].scenes[sc_idx]; scene_t *sc = &cur[cmd.channel].scenes[sc_idx];
int cst = atomic_load(&sc->state); int cst = atomic_load(&sc->state);
int next; int next;
@@ -93,22 +93,24 @@ static void apply_command(command_t cmd) {
case CMD_STOP: case CMD_STOP:
if (cmd.channel >= 0 && cmd.channel < cap) { if (cmd.channel >= 0 && cmd.channel < cap) {
struct channel_t *ch = &cur[cmd.channel]; struct channel_t *ch = &cur[cmd.channel];
for (int s = 0; s < ch->scene_count; s++) { int sc_cnt = atomic_load(&ch->scene_count);
for (int s = 0; s < sc_cnt; s++) {
atomic_store(&ch->scenes[s].state, STATE_IDLE); atomic_store(&ch->scenes[s].state, STATE_IDLE);
ch->scenes[s].loop_count = 0; atomic_store(&ch->scenes[s].loop_count, 0);
ch->scenes[s].record_pos = 0; atomic_store(&ch->scenes[s].record_pos, 0);
ch->scenes[s].playback_pos = 0; atomic_store(&ch->scenes[s].playback_pos, 0);
ch->scenes[s].prev_state = -1; atomic_store(&ch->scenes[s].prev_state, -1);
} }
} else { } else {
for (int i = 0; i < cap; i++) { for (int i = 0; i < cap; i++) {
struct channel_t *ch = &cur[i]; struct channel_t *ch = &cur[i];
for (int s = 0; s < ch->scene_count; s++) { int sc_cnt = atomic_load(&ch->scene_count);
for (int s = 0; s < sc_cnt; s++) {
atomic_store(&ch->scenes[s].state, STATE_IDLE); atomic_store(&ch->scenes[s].state, STATE_IDLE);
ch->scenes[s].loop_count = 0; atomic_store(&ch->scenes[s].loop_count, 0);
ch->scenes[s].record_pos = 0; atomic_store(&ch->scenes[s].record_pos, 0);
ch->scenes[s].playback_pos = 0; atomic_store(&ch->scenes[s].playback_pos, 0);
ch->scenes[s].prev_state = -1; atomic_store(&ch->scenes[s].prev_state, -1);
} }
} }
} }
@@ -167,7 +169,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
} }
/* Obtain current scene pointer */ /* Obtain current scene pointer */
int sc_idx = active_channels[c].current_scene; int sc_idx = atomic_load(&active_channels[c].current_scene);
scene_t *sc = &active_channels[c].scenes[sc_idx]; scene_t *sc = &active_channels[c].scenes[sc_idx];
const jack_default_audio_sample_t *in = const jack_default_audio_sample_t *in =
@@ -180,17 +182,18 @@ int process_callback(jack_nframes_t nframes, void *arg) {
continue; continue;
int state = atomic_load(&sc->state); int state = atomic_load(&sc->state);
int prev_state = atomic_load(&sc->prev_state);
if (state != sc->prev_state) { if (state != prev_state) {
switch (state) { switch (state) {
case STATE_RECORD: case STATE_RECORD:
sc->record_pos = 0; atomic_store(&sc->record_pos, 0);
sc->loop_count = 0; atomic_store(&sc->loop_count, 0);
break; break;
case STATE_LOOPING: case STATE_LOOPING:
if (sc->record_pos > 0) if (atomic_load(&sc->record_pos) > 0)
sc->loop_count = sc->record_pos; atomic_store(&sc->loop_count, atomic_load(&sc->record_pos));
sc->playback_pos = 0; atomic_store(&sc->playback_pos, 0);
break; break;
default: default:
break; break;
@@ -209,14 +212,15 @@ int process_callback(jack_nframes_t nframes, void *arg) {
for (jack_nframes_t j = 0; j < nevents; j++) { for (jack_nframes_t j = 0; j < nevents; j++) {
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) if (jack_midi_event_get(&ev, midi_in_buf, j) != 0)
continue; continue;
if (sc->record_pos < MAX_MIDI_EVENTS) { int rp = atomic_load(&sc->record_pos);
sc->loop.midi_events[sc->record_pos].timestamp = ev.time; if (rp < MAX_MIDI_EVENTS) {
sc->loop.midi_events[sc->record_pos].status = ev.buffer[0]; sc->loop.midi_events[rp].timestamp = ev.time;
sc->loop.midi_events[sc->record_pos].note = sc->loop.midi_events[rp].status = ev.buffer[0];
sc->loop.midi_events[rp].note =
(ev.size > 1) ? ev.buffer[1] : 0; (ev.size > 1) ? ev.buffer[1] : 0;
sc->loop.midi_events[sc->record_pos].velocity = sc->loop.midi_events[rp].velocity =
(ev.size > 2) ? ev.buffer[2] : 0; (ev.size > 2) ? ev.buffer[2] : 0;
sc->record_pos++; atomic_store(&sc->record_pos, rp + 1);
} }
} }
/* forward incoming MIDI to output during record */ /* forward incoming MIDI to output during record */
@@ -238,7 +242,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
jack_port_get_buffer(active_channels[c].midi_out, nframes); jack_port_get_buffer(active_channels[c].midi_out, nframes);
if (midi_out_buf) { if (midi_out_buf) {
jack_midi_clear_buffer(midi_out_buf); jack_midi_clear_buffer(midi_out_buf);
int cnt = sc->loop_count; int cnt = atomic_load(&sc->loop_count);
if (cnt > 0) { if (cnt > 0) {
for (int e = 0; e < cnt; e++) { for (int e = 0; e < cnt; e++) {
unsigned char msg[3]; unsigned char msg[3];
@@ -274,7 +278,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
break; break;
} }
if (state == STATE_LOOPING) { if (state == STATE_LOOPING) {
sc->loop_count = sc->record_pos; atomic_store(&sc->loop_count, atomic_load(&sc->record_pos));
} }
} else { } else {
/* audio channel handling */ /* audio channel handling */
@@ -285,8 +289,11 @@ int process_callback(jack_nframes_t nframes, void *arg) {
float *f_out = (float *)out; float *f_out = (float *)out;
const float *f_in = (const float *)in; const float *f_in = (const float *)in;
for (i = 0; i < nframes; i++) { for (i = 0; i < nframes; i++) {
if (sc->record_pos < LOOP_BUF_SIZE) int rp = atomic_load(&sc->record_pos);
sc->loop.audio_buffer[sc->record_pos++] = f_in[i]; if (rp < LOOP_BUF_SIZE) {
sc->loop.audio_buffer[rp] = f_in[i];
atomic_store(&sc->record_pos, rp + 1);
}
f_out[i] = f_in[i]; f_out[i] = f_in[i];
} }
} else { } else {
@@ -294,17 +301,21 @@ int process_callback(jack_nframes_t nframes, void *arg) {
} }
break; break;
case STATE_LOOPING: case STATE_LOOPING: {
if (sc->loop_count > 0) { int loop_cnt = atomic_load(&sc->loop_count);
if (loop_cnt > 0) {
float *outf = (float *)out; float *outf = (float *)out;
int pp = atomic_load(&sc->playback_pos);
for (i = 0; i < nframes; i++) { for (i = 0; i < nframes; i++) {
outf[i] = sc->loop.audio_buffer[sc->playback_pos]; outf[i] = sc->loop.audio_buffer[pp];
sc->playback_pos = (sc->playback_pos + 1) % sc->loop_count; pp = (pp + 1) % loop_cnt;
} }
atomic_store(&sc->playback_pos, pp);
} else { } else {
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
} }
break; break;
}
case STATE_PAUSED: case STATE_PAUSED:
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
@@ -320,7 +331,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
} }
} }
sc->prev_state = state; atomic_store(&sc->prev_state, state);
} }
/* MIDI clock events affect channel 0 only */ /* MIDI clock events affect channel 0 only */
@@ -337,7 +348,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
switch (msg) { switch (msg) {
case 0xFA: { case 0xFA: {
struct channel_t *cur = atomic_load(&channels); struct channel_t *cur = atomic_load(&channels);
int sc_idx = cur[0].current_scene; int sc_idx = atomic_load(&cur[0].current_scene);
int s = atomic_load(&cur[0].scenes[sc_idx].state); int s = atomic_load(&cur[0].scenes[sc_idx].state);
if (s == STATE_IDLE) if (s == STATE_IDLE)
atomic_store(&cur[0].scenes[sc_idx].state, STATE_RECORD); atomic_store(&cur[0].scenes[sc_idx].state, STATE_RECORD);
@@ -345,13 +356,13 @@ int process_callback(jack_nframes_t nframes, void *arg) {
} }
case 0xFC: { case 0xFC: {
struct channel_t *cur = atomic_load(&channels); struct channel_t *cur = atomic_load(&channels);
int sc_idx = cur[0].current_scene; int sc_idx = atomic_load(&cur[0].current_scene);
atomic_store(&cur[0].scenes[sc_idx].state, STATE_IDLE); atomic_store(&cur[0].scenes[sc_idx].state, STATE_IDLE);
break; break;
} }
case 0xFB: { case 0xFB: {
struct channel_t *cur = atomic_load(&channels); struct channel_t *cur = atomic_load(&channels);
int sc_idx = cur[0].current_scene; int sc_idx = atomic_load(&cur[0].current_scene);
int s = atomic_load(&cur[0].scenes[sc_idx].state); int s = atomic_load(&cur[0].scenes[sc_idx].state);
if (s == STATE_PAUSED) if (s == STATE_PAUSED)
atomic_store(&cur[0].scenes[sc_idx].state, STATE_LOOPING); atomic_store(&cur[0].scenes[sc_idx].state, STATE_LOOPING);
@@ -394,13 +405,13 @@ int looper_init(jack_client_t *client) {
struct channel_t *init = atomic_load(&channels); struct channel_t *init = atomic_load(&channels);
/* channel 0 */ /* channel 0 */
atomic_store(&init[0].active, 1); atomic_store(&init[0].active, 1);
init[0].scene_count = 1; atomic_store(&init[0].scene_count, 1);
init[0].current_scene = 0; atomic_store(&init[0].current_scene, 0);
init[0].scenes[0].loop_count = 0; atomic_store(&init[0].scenes[0].loop_count, 0);
init[0].scenes[0].record_pos = 0; atomic_store(&init[0].scenes[0].record_pos, 0);
init[0].scenes[0].playback_pos = 0; atomic_store(&init[0].scenes[0].playback_pos, 0);
atomic_store(&init[0].scenes[0].state, STATE_IDLE); atomic_store(&init[0].scenes[0].state, STATE_IDLE);
init[0].scenes[0].prev_state = -1; atomic_store(&init[0].scenes[0].prev_state, -1);
init[0].audio_in = jack_port_register( init[0].audio_in = jack_port_register(
client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);