feat: replace mutex with lock-free ring buffer for real-time audio recording

Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-04 22:53:24 +00:00
parent 256a06bc21
commit 5ed187a181
3 changed files with 41 additions and 5 deletions

View File

@@ -152,11 +152,30 @@ static AppState clip_trigger(AppState state, int clip_index) {
clip->buffer_size = 0;
clip->read_position = 0;
break;
case CLIP_RECORDING:
case CLIP_RECORDING: {
// Transition to looping: copy from ring buffer to clip buffer
clip->state = CLIP_LOOPING;
clip->buffer_size = clip->write_position;
clip->read_position = 0;
// Determine which channel this clip belongs to
int channel = clip_index % MAX_CHANNELS;
// Read from ring buffer
size_t wp = atomic_load(&state.record_write_pos[channel]);
size_t rp = atomic_load(&state.record_read_pos[channel]);
size_t available = wp - rp;
if (available > 0 && clip->buffer != NULL) {
size_t to_copy = (available < MAX_BUFFER_SIZE) ? available : MAX_BUFFER_SIZE;
for (size_t i = 0; i < to_copy; i++) {
clip->buffer[i] = state.record_buffer[channel][(rp + i) % MAX_BUFFER_SIZE];
}
clip->buffer_size = to_copy;
clip->write_position = to_copy;
atomic_store(&state.record_read_pos[channel], wp);
}
break;
}
case CLIP_LOOPING:
clip->state = CLIP_STOPPED;
clip->read_position = 0;
@@ -236,6 +255,10 @@ static AppState clip_reset(AppState state, int clip_index) {
clip->read_position = 0;
if (clip->buffer) memset(clip->buffer, 0, MAX_BUFFER_SIZE * sizeof(float));
// Also reset the ring buffer read position for this channel
int channel = clip_index % MAX_CHANNELS;
atomic_store(&state.record_read_pos[channel], atomic_load(&state.record_write_pos[channel]));
return state;
}
@@ -612,6 +635,12 @@ static void* dispatcher_thread_func(void *arg) {
DispatchFn dispatcher_init(AppState *initial_state) {
memcpy(&dispatcher.state, initial_state, sizeof(AppState));
// Initialize ring buffer positions
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
atomic_store(&dispatcher.state.record_write_pos[ch], 0);
atomic_store(&dispatcher.state.record_read_pos[ch], 0);
}
// NEW: Ensure midi clip events are allocated (in case initial_state has NULL pointers)
for (int i = 0; i < MAX_CLIPS; i++) {
if (dispatcher.state.midi_clips[i].events == NULL) {