fix: use atomic operations for thread-safe clip state access

Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-02 11:09:17 +00:00
parent 42ecd94d83
commit a8223baf43
3 changed files with 53 additions and 51 deletions

View File

@@ -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));

View File

@@ -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;

8
tui.c
View File

@@ -4,6 +4,7 @@
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <stdatomic.h>
#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);