diff --git a/src/channel.c b/src/channel.c index 60fcaa4..cbf99a4 100644 --- a/src/channel.c +++ b/src/channel.c @@ -9,7 +9,7 @@ static void init_scene(scene_t *sc) { memset(sc, 0, sizeof(scene_t)); 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) { @@ -32,8 +32,8 @@ void channel_add(jack_client_t *client, int idx) { atomic_store(&cur[idx].active, 1); cur[idx].type = CHANNEL_AUDIO; - cur[idx].scene_count = 1; - cur[idx].current_scene = 0; + atomic_store(&cur[idx].scene_count, 1); + atomic_store(&cur[idx].current_scene, 0); init_scene(&cur[idx].scenes[0]); next_channel_id++; @@ -60,8 +60,8 @@ void channel_add_midi(jack_client_t *client, int idx) { atomic_store(&cur[idx].active, 1); cur[idx].type = CHANNEL_MIDI; - cur[idx].scene_count = 1; - cur[idx].current_scene = 0; + atomic_store(&cur[idx].scene_count, 1); + atomic_store(&cur[idx].current_scene, 0); init_scene(&cur[idx].scenes[0]); 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)client; struct channel_t *cur = get_channels_array(); - if (cur[idx].scene_count >= MAX_SCENES) + if (atomic_load(&cur[idx].scene_count) >= MAX_SCENES) return; - int ns = cur[idx].scene_count; + int ns = atomic_load(&cur[idx].scene_count); 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)client; 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; - int cs = cur[idx].current_scene; - /* shift remaining scenes down (safe copy of fields) */ - for (int i = cs; i < cur[idx].scene_count - 1; i++) { - cur[idx].scenes[i].loop_count = cur[idx].scenes[i+1].loop_count; - cur[idx].scenes[i].record_pos = cur[idx].scenes[i+1].record_pos; - cur[idx].scenes[i].playback_pos = cur[idx].scenes[i+1].playback_pos; + int cs = atomic_load(&cur[idx].current_scene); + /* shift remaining scenes down (atomic copy of fields) */ + for (int i = cs; i < sc - 1; i++) { + atomic_store(&cur[idx].scenes[i].loop_count, + atomic_load(&cur[idx].scenes[i+1].loop_count)); + 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_load(&cur[idx].scenes[i+1].state)); - cur[idx].scenes[i].prev_state = cur[idx].scenes[i+1].prev_state; - /* copy loop data */ + atomic_store(&cur[idx].scenes[i].prev_state, + 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, cur[idx].scenes[i+1].loop.audio_buffer, LOOP_BUF_SIZE * sizeof(float)); } - cur[idx].scene_count--; - if (cur[idx].current_scene >= cur[idx].scene_count) - cur[idx].current_scene = cur[idx].scene_count - 1; + atomic_fetch_sub(&cur[idx].scene_count, 1); + int new_sc = atomic_load(&cur[idx].scene_count); + if (cs >= new_sc) + atomic_store(&cur[idx].current_scene, new_sc - 1); } void channel_next_scene(jack_client_t *client, int idx) { (void)client; struct channel_t *cur = get_channels_array(); - if (cur[idx].scene_count > 1) { - cur[idx].current_scene = - (cur[idx].current_scene + 1) % cur[idx].scene_count; + int sc = atomic_load(&cur[idx].scene_count); + if (sc > 1) { + 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)client; struct channel_t *cur = get_channels_array(); - if (cur[idx].scene_count > 1) { - cur[idx].current_scene = - (cur[idx].current_scene - 1 + cur[idx].scene_count) % - cur[idx].scene_count; + int sc = atomic_load(&cur[idx].scene_count); + if (sc > 1) { + int cs = atomic_load(&cur[idx].current_scene); + atomic_store(&cur[idx].current_scene, (cs - 1 + sc) % sc); } } diff --git a/src/channel.h b/src/channel.h index 0345482..b8f2d32 100644 --- a/src/channel.h +++ b/src/channel.h @@ -35,11 +35,11 @@ typedef struct { float audio_buffer[LOOP_BUF_SIZE]; midi_event_t midi_events[MAX_MIDI_EVENTS]; } loop; - int loop_count; - int record_pos; - int playback_pos; + atomic_int loop_count; + atomic_int record_pos; + atomic_int playback_pos; atomic_int state; - int prev_state; + atomic_int prev_state; } scene_t; struct channel_t { @@ -50,8 +50,8 @@ struct channel_t { jack_port_t *midi_in; jack_port_t *midi_out; scene_t scenes[MAX_SCENES]; - int scene_count; - int current_scene; + atomic_int scene_count; + atomic_int current_scene; }; /* Globals declared in looper.c */ diff --git a/src/looper.c b/src/looper.c index 8969a01..0ce3d66 100644 --- a/src/looper.c +++ b/src/looper.c @@ -66,7 +66,7 @@ static void apply_command(command_t cmd) { switch (cmd.type) { case CMD_CYCLE: 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]; int cst = atomic_load(&sc->state); int next; @@ -93,22 +93,24 @@ static void apply_command(command_t cmd) { case CMD_STOP: if (cmd.channel >= 0 && cmd.channel < cap) { 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); - ch->scenes[s].loop_count = 0; - ch->scenes[s].record_pos = 0; - ch->scenes[s].playback_pos = 0; - ch->scenes[s].prev_state = -1; + atomic_store(&ch->scenes[s].loop_count, 0); + atomic_store(&ch->scenes[s].record_pos, 0); + atomic_store(&ch->scenes[s].playback_pos, 0); + atomic_store(&ch->scenes[s].prev_state, -1); } } else { for (int i = 0; i < cap; 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); - ch->scenes[s].loop_count = 0; - ch->scenes[s].record_pos = 0; - ch->scenes[s].playback_pos = 0; - ch->scenes[s].prev_state = -1; + atomic_store(&ch->scenes[s].loop_count, 0); + atomic_store(&ch->scenes[s].record_pos, 0); + atomic_store(&ch->scenes[s].playback_pos, 0); + 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 */ - 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]; const jack_default_audio_sample_t *in = @@ -180,17 +182,18 @@ int process_callback(jack_nframes_t nframes, void *arg) { continue; 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) { case STATE_RECORD: - sc->record_pos = 0; - sc->loop_count = 0; + atomic_store(&sc->record_pos, 0); + atomic_store(&sc->loop_count, 0); break; case STATE_LOOPING: - if (sc->record_pos > 0) - sc->loop_count = sc->record_pos; - sc->playback_pos = 0; + if (atomic_load(&sc->record_pos) > 0) + atomic_store(&sc->loop_count, atomic_load(&sc->record_pos)); + atomic_store(&sc->playback_pos, 0); break; default: break; @@ -209,14 +212,15 @@ int process_callback(jack_nframes_t nframes, void *arg) { for (jack_nframes_t j = 0; j < nevents; j++) { if (jack_midi_event_get(&ev, midi_in_buf, j) != 0) continue; - if (sc->record_pos < MAX_MIDI_EVENTS) { - sc->loop.midi_events[sc->record_pos].timestamp = ev.time; - sc->loop.midi_events[sc->record_pos].status = ev.buffer[0]; - sc->loop.midi_events[sc->record_pos].note = + int rp = atomic_load(&sc->record_pos); + if (rp < MAX_MIDI_EVENTS) { + sc->loop.midi_events[rp].timestamp = ev.time; + sc->loop.midi_events[rp].status = ev.buffer[0]; + sc->loop.midi_events[rp].note = (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; - sc->record_pos++; + atomic_store(&sc->record_pos, rp + 1); } } /* 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); if (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) { for (int e = 0; e < cnt; e++) { unsigned char msg[3]; @@ -274,7 +278,7 @@ int process_callback(jack_nframes_t nframes, void *arg) { break; } if (state == STATE_LOOPING) { - sc->loop_count = sc->record_pos; + atomic_store(&sc->loop_count, atomic_load(&sc->record_pos)); } } else { /* audio channel handling */ @@ -285,8 +289,11 @@ 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 (sc->record_pos < LOOP_BUF_SIZE) - sc->loop.audio_buffer[sc->record_pos++] = f_in[i]; + int rp = atomic_load(&sc->record_pos); + 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]; } } else { @@ -294,17 +301,21 @@ int process_callback(jack_nframes_t nframes, void *arg) { } break; - case STATE_LOOPING: - if (sc->loop_count > 0) { + case STATE_LOOPING: { + int loop_cnt = atomic_load(&sc->loop_count); + if (loop_cnt > 0) { float *outf = (float *)out; + int pp = atomic_load(&sc->playback_pos); for (i = 0; i < nframes; i++) { - outf[i] = sc->loop.audio_buffer[sc->playback_pos]; - sc->playback_pos = (sc->playback_pos + 1) % sc->loop_count; + outf[i] = sc->loop.audio_buffer[pp]; + pp = (pp + 1) % loop_cnt; } + atomic_store(&sc->playback_pos, pp); } else { memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes); } break; + } case STATE_PAUSED: 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 */ @@ -337,7 +348,7 @@ int process_callback(jack_nframes_t nframes, void *arg) { switch (msg) { case 0xFA: { 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); if (s == STATE_IDLE) 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: { 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); break; } case 0xFB: { 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); if (s == STATE_PAUSED) 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); /* channel 0 */ atomic_store(&init[0].active, 1); - init[0].scene_count = 1; - init[0].current_scene = 0; - init[0].scenes[0].loop_count = 0; - init[0].scenes[0].record_pos = 0; - init[0].scenes[0].playback_pos = 0; + atomic_store(&init[0].scene_count, 1); + atomic_store(&init[0].current_scene, 0); + atomic_store(&init[0].scenes[0].loop_count, 0); + atomic_store(&init[0].scenes[0].record_pos, 0); + atomic_store(&init[0].scenes[0].playback_pos, 0); 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( client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);