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:
33
dispatcher.c
33
dispatcher.c
@@ -152,11 +152,30 @@ static AppState clip_trigger(AppState state, int clip_index) {
|
|||||||
clip->buffer_size = 0;
|
clip->buffer_size = 0;
|
||||||
clip->read_position = 0;
|
clip->read_position = 0;
|
||||||
break;
|
break;
|
||||||
case CLIP_RECORDING:
|
case CLIP_RECORDING: {
|
||||||
|
// Transition to looping: copy from ring buffer to clip buffer
|
||||||
clip->state = CLIP_LOOPING;
|
clip->state = CLIP_LOOPING;
|
||||||
clip->buffer_size = clip->write_position;
|
|
||||||
clip->read_position = 0;
|
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;
|
break;
|
||||||
|
}
|
||||||
case CLIP_LOOPING:
|
case CLIP_LOOPING:
|
||||||
clip->state = CLIP_STOPPED;
|
clip->state = CLIP_STOPPED;
|
||||||
clip->read_position = 0;
|
clip->read_position = 0;
|
||||||
@@ -236,6 +255,10 @@ static AppState clip_reset(AppState state, int clip_index) {
|
|||||||
clip->read_position = 0;
|
clip->read_position = 0;
|
||||||
if (clip->buffer) memset(clip->buffer, 0, MAX_BUFFER_SIZE * sizeof(float));
|
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;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,6 +635,12 @@ static void* dispatcher_thread_func(void *arg) {
|
|||||||
DispatchFn dispatcher_init(AppState *initial_state) {
|
DispatchFn dispatcher_init(AppState *initial_state) {
|
||||||
memcpy(&dispatcher.state, initial_state, sizeof(AppState));
|
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)
|
// NEW: Ensure midi clip events are allocated (in case initial_state has NULL pointers)
|
||||||
for (int i = 0; i < MAX_CLIPS; i++) {
|
for (int i = 0; i < MAX_CLIPS; i++) {
|
||||||
if (dispatcher.state.midi_clips[i].events == NULL) {
|
if (dispatcher.state.midi_clips[i].events == NULL) {
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ typedef struct {
|
|||||||
int current_batch_size; // NEW: current batch being recorded
|
int current_batch_size; // NEW: current batch being recorded
|
||||||
} undo;
|
} undo;
|
||||||
|
|
||||||
|
// Ring buffers for real-time recording (written by JACK callback, read by dispatcher)
|
||||||
|
// Lock-free: single producer (JACK callback), single consumer (dispatcher thread)
|
||||||
|
float record_buffer[MAX_CHANNELS][MAX_BUFFER_SIZE];
|
||||||
|
atomic_size_t record_write_pos[MAX_CHANNELS]; // Only written by JACK callback
|
||||||
|
atomic_size_t record_read_pos[MAX_CHANNELS]; // Only written by dispatcher thread
|
||||||
|
|
||||||
// Carla host
|
// Carla host
|
||||||
CarlaHost carla_host;
|
CarlaHost carla_host;
|
||||||
|
|
||||||
|
|||||||
7
engine.c
7
engine.c
@@ -144,9 +144,10 @@ static int process_callback(jack_nframes_t nframes, void *arg) {
|
|||||||
Clip *clip = &state->clips[clip_idx];
|
Clip *clip = &state->clips[clip_idx];
|
||||||
|
|
||||||
if (clip->state == CLIP_RECORDING) {
|
if (clip->state == CLIP_RECORDING) {
|
||||||
if (clip->write_position < MAX_BUFFER_SIZE && clip->buffer != NULL) {
|
// Write to lock-free ring buffer instead of clip buffer directly
|
||||||
clip->buffer[clip->write_position++] = audio_in[ch][i];
|
size_t wp = atomic_load(&state->record_write_pos[ch]);
|
||||||
}
|
state->record_buffer[ch][wp % MAX_BUFFER_SIZE] = audio_in[ch][i];
|
||||||
|
atomic_store(&state->record_write_pos[ch], wp + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clip->state == CLIP_LOOPING && clip->buffer_size > 0 && clip->buffer != NULL) {
|
if (clip->state == CLIP_LOOPING && clip->buffer_size > 0 && clip->buffer != NULL) {
|
||||||
|
|||||||
Reference in New Issue
Block a user