From a8223baf43edea407c89dd9382e8509c5bc87f3a Mon Sep 17 00:00:00 2001 From: Loic Coenen Date: Sat, 2 May 2026 11:09:17 +0000 Subject: [PATCH] fix: use atomic operations for thread-safe clip state access Co-authored-by: aider (deepseek/deepseek-coder) --- engine.c | 92 ++++++++++++++++++++++++++++---------------------------- engine.h | 4 +-- tui.c | 8 +++-- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/engine.c b/engine.c index 4fbf9ed..16c6d1d 100644 --- a/engine.c +++ b/engine.c @@ -134,19 +134,19 @@ static int process_callback(jack_nframes_t nframes, void *arg) { int clip_idx = CLIP_INDEX(s, ch); Clip *clip = &engine->clips[clip_idx]; - if (clip->state == CLIP_RECORDING) { + if ((ClipState)atomic_load(&clip->state) == CLIP_RECORDING) { if (clip->write_position < MAX_BUFFER_SIZE) { clip->buffer[clip->write_position++] = audio_in[ch][i]; } else { // Buffer full, stop recording - clip->state = CLIP_LOOPING; - clip->buffer_size = clip->write_position; + atomic_store(&clip->state, CLIP_LOOPING); + atomic_store(&clip->buffer_size, clip->write_position); clip->read_position = 0; } } // Play looping clips to this channel's output - if (clip->state == CLIP_LOOPING && clip->buffer_size > 0) { + if ((ClipState)atomic_load(&clip->state) == CLIP_LOOPING && atomic_load(&clip->buffer_size) > 0) { audio_out[ch][i] += clip->buffer[clip->read_position]; clip->read_position = (clip->read_position + 1) % clip->buffer_size; } @@ -469,32 +469,32 @@ void engine_process_commands(Engine *engine) { action.previous_read_position = clip->read_position; engine_push_undo_action(engine, &action); - ClipState prev_state = clip->state; + ClipState prev_state = (ClipState)atomic_load(&clip->state); - switch (clip->state) { + switch (prev_state) { case CLIP_EMPTY: - clip->state = CLIP_RECORDING; + atomic_store(&clip->state, CLIP_RECORDING); clip->write_position = 0; - clip->buffer_size = 0; + atomic_store(&clip->buffer_size, 0); clip->read_position = 0; break; case CLIP_RECORDING: - clip->state = CLIP_LOOPING; - clip->buffer_size = clip->write_position; + atomic_store(&clip->state, CLIP_LOOPING); + atomic_store(&clip->buffer_size, clip->write_position); clip->read_position = 0; break; case CLIP_LOOPING: - clip->state = CLIP_STOPPED; + atomic_store(&clip->state, CLIP_STOPPED); clip->read_position = 0; break; case CLIP_STOPPED: - clip->state = CLIP_LOOPING; + atomic_store(&clip->state, CLIP_LOOPING); clip->read_position = 0; break; } // Auto-save when recording finishes (RECORDING -> LOOPING) - if (prev_state == CLIP_RECORDING && clip->state == CLIP_LOOPING) { + if (prev_state == CLIP_RECORDING && (ClipState)atomic_load(&clip->state) == CLIP_LOOPING) { save_load_queue_push(&engine->save_load_queue, REQ_SAVE_CLIP, cmd.index, ""); } break; @@ -514,32 +514,32 @@ void engine_process_commands(Engine *engine) { int clip_idx = CLIP_INDEX(cmd.index, ch); Clip *clip = &engine->clips[clip_idx]; - ClipState prev_state = clip->state; + ClipState prev_state = (ClipState)atomic_load(&clip->state); - switch (clip->state) { + switch (prev_state) { case CLIP_EMPTY: - clip->state = CLIP_RECORDING; + atomic_store(&clip->state, CLIP_RECORDING); clip->write_position = 0; - clip->buffer_size = 0; + atomic_store(&clip->buffer_size, 0); clip->read_position = 0; break; case CLIP_RECORDING: - clip->state = CLIP_LOOPING; - clip->buffer_size = clip->write_position; + atomic_store(&clip->state, CLIP_LOOPING); + atomic_store(&clip->buffer_size, clip->write_position); clip->read_position = 0; break; case CLIP_LOOPING: - clip->state = CLIP_STOPPED; + atomic_store(&clip->state, CLIP_STOPPED); clip->read_position = 0; break; case CLIP_STOPPED: - clip->state = CLIP_LOOPING; + atomic_store(&clip->state, CLIP_LOOPING); clip->read_position = 0; break; } // Auto-save when recording finishes - if (prev_state == CLIP_RECORDING && clip->state == CLIP_LOOPING) { + if (prev_state == CLIP_RECORDING && (ClipState)atomic_load(&clip->state) == CLIP_LOOPING) { save_load_queue_push(&engine->save_load_queue, REQ_SAVE_CLIP, clip_idx, ""); } } @@ -562,8 +562,8 @@ void engine_process_commands(Engine *engine) { action.previous_read_position = clip->read_position; engine_push_undo_action(engine, &action); - clip->state = CLIP_EMPTY; - clip->buffer_size = 0; + atomic_store(&clip->state, CLIP_EMPTY); + atomic_store(&clip->buffer_size, 0); clip->write_position = 0; clip->read_position = 0; memset(clip->buffer, 0, MAX_BUFFER_SIZE * sizeof(float)); @@ -732,8 +732,8 @@ void engine_undo(Engine *engine) { if (clip_idx < 0 || clip_idx >= MAX_CLIPS) break; Clip *clip = &engine->clips[clip_idx]; if (!clip->buffer) break; // ADD THIS - clip->state = action->previous_state; - clip->buffer_size = action->previous_buffer_size; + atomic_store(&clip->state, action->previous_state); + atomic_store(&clip->buffer_size, action->previous_buffer_size); clip->write_position = action->previous_write_position; clip->read_position = action->previous_read_position; break; @@ -747,8 +747,8 @@ void engine_undo(Engine *engine) { if (clip_idx < 0 || clip_idx >= MAX_CLIPS) continue; Clip *clip = &engine->clips[clip_idx]; if (!clip->buffer) continue; - clip->state = CLIP_EMPTY; - clip->buffer_size = 0; + atomic_store(&clip->state, CLIP_EMPTY); + atomic_store(&clip->buffer_size, 0); clip->write_position = 0; clip->read_position = 0; } @@ -760,8 +760,8 @@ void engine_undo(Engine *engine) { if (clip_idx < 0 || clip_idx >= MAX_CLIPS) break; Clip *clip = &engine->clips[clip_idx]; if (!clip->buffer) break; - clip->state = action->previous_state; - clip->buffer_size = action->previous_buffer_size; + atomic_store(&clip->state, action->previous_state); + atomic_store(&clip->buffer_size, action->previous_buffer_size); clip->write_position = action->previous_write_position; clip->read_position = action->previous_read_position; break; @@ -829,24 +829,24 @@ void engine_redo(Engine *engine) { Clip *clip = &engine->clips[clip_idx]; if (!clip->buffer) break; // ADD THIS // Re-apply the trigger by directly manipulating state - switch (clip->state) { + switch ((ClipState)atomic_load(&clip->state)) { case CLIP_EMPTY: - clip->state = CLIP_RECORDING; + atomic_store(&clip->state, CLIP_RECORDING); clip->write_position = 0; - clip->buffer_size = 0; + atomic_store(&clip->buffer_size, 0); clip->read_position = 0; break; case CLIP_RECORDING: - clip->state = CLIP_LOOPING; - clip->buffer_size = clip->write_position; + atomic_store(&clip->state, CLIP_LOOPING); + atomic_store(&clip->buffer_size, clip->write_position); clip->read_position = 0; break; case CLIP_LOOPING: - clip->state = CLIP_STOPPED; + atomic_store(&clip->state, CLIP_STOPPED); clip->read_position = 0; break; case CLIP_STOPPED: - clip->state = CLIP_LOOPING; + atomic_store(&clip->state, CLIP_LOOPING); clip->read_position = 0; break; } @@ -861,24 +861,24 @@ void engine_redo(Engine *engine) { if (clip_idx < 0 || clip_idx >= MAX_CLIPS) continue; Clip *clip = &engine->clips[clip_idx]; if (!clip->buffer) continue; - switch (clip->state) { + switch ((ClipState)atomic_load(&clip->state)) { case CLIP_EMPTY: - clip->state = CLIP_RECORDING; + atomic_store(&clip->state, CLIP_RECORDING); clip->write_position = 0; - clip->buffer_size = 0; + atomic_store(&clip->buffer_size, 0); clip->read_position = 0; break; case CLIP_RECORDING: - clip->state = CLIP_LOOPING; - clip->buffer_size = clip->write_position; + atomic_store(&clip->state, CLIP_LOOPING); + atomic_store(&clip->buffer_size, clip->write_position); clip->read_position = 0; break; case CLIP_LOOPING: - clip->state = CLIP_STOPPED; + atomic_store(&clip->state, CLIP_STOPPED); clip->read_position = 0; break; case CLIP_STOPPED: - clip->state = CLIP_LOOPING; + atomic_store(&clip->state, CLIP_LOOPING); clip->read_position = 0; break; } @@ -891,8 +891,8 @@ void engine_redo(Engine *engine) { if (clip_idx < 0 || clip_idx >= MAX_CLIPS) break; Clip *clip = &engine->clips[clip_idx]; if (!clip->buffer) break; - clip->state = CLIP_EMPTY; - clip->buffer_size = 0; + atomic_store(&clip->state, CLIP_EMPTY); + atomic_store(&clip->buffer_size, 0); clip->write_position = 0; clip->read_position = 0; memset(clip->buffer, 0, MAX_BUFFER_SIZE * sizeof(float)); diff --git a/engine.h b/engine.h index a9d71a0..c6a2886 100644 --- a/engine.h +++ b/engine.h @@ -32,9 +32,9 @@ typedef enum { typedef struct { - ClipState state; + atomic_int state; // ClipState (atomic for thread-safe access) float *buffer; - size_t buffer_size; + atomic_size_t buffer_size; // size_t (atomic for thread-safe access) size_t write_position; size_t read_position; bool is_playing; diff --git a/tui.c b/tui.c index 51c45c7..279edf8 100644 --- a/tui.c +++ b/tui.c @@ -4,6 +4,7 @@ #include #include #include +#include #define GRID_ROWS 8 #define GRID_COLS 8 @@ -215,7 +216,7 @@ static void draw_cell(int row, int col, bool selected) { int y = row * CELL_HEIGHT + 1; int x = col * CELL_WIDTH + 1; - int color = state_to_color(clip->state); + int color = state_to_color((ClipState)atomic_load(&clip->state)); if (selected) { color = COLOR_SELECTED; } else if (current_mode == MODE_VISUAL && is_in_visual_selection(row, col)) { @@ -237,7 +238,7 @@ static void draw_cell(int row, int col, bool selected) { // Draw state indicator char state_char; - switch (clip->state) { + switch ((ClipState)atomic_load(&clip->state)) { case CLIP_EMPTY: state_char = ' '; break; case CLIP_RECORDING: state_char = 'R'; break; case CLIP_LOOPING: state_char = 'L'; break; @@ -272,7 +273,8 @@ static void draw_grid(void) { mvprintw(GRID_ROWS * CELL_HEIGHT + 1, 0, "Selected: Clip %d | State: %s | Buffer: %zu samples", - clip_idx, clip_state_to_string(clip->state), clip->buffer_size); + clip_idx, clip_state_to_string((ClipState)atomic_load(&clip->state)), + (size_t)atomic_load(&clip->buffer_size)); TransportState transport_state = (TransportState)atomic_load(&g_engine->transport->state_atomic); ClockSource clock_source = (ClockSource)atomic_load(&g_engine->transport->clock_source_atomic);