feat: add save/load thread and WAV file I/O for clip persistence
Co-authored-by: aider (deepseek/deepseek-coder) <aider@aider.chat>
This commit is contained in:
184
engine.c
184
engine.c
@@ -1,9 +1,14 @@
|
||||
#include "engine.h"
|
||||
#include "wav_io.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdatomic.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
// Forward declarations
|
||||
static void process_queued_triggers(Engine *engine, jack_nframes_t current_frame);
|
||||
@@ -243,6 +248,158 @@ void command_queue_init(CommandQueue *q) {
|
||||
atomic_store(&q->read_index, 0);
|
||||
}
|
||||
|
||||
// Initialize save/load queue
|
||||
void save_load_queue_init(SaveLoadQueue *q) {
|
||||
atomic_store(&q->write_index, 0);
|
||||
atomic_store(&q->read_index, 0);
|
||||
}
|
||||
|
||||
// Push a save/load request (called from audio thread)
|
||||
int save_load_queue_push(SaveLoadQueue *q, SaveLoadType type, int clip_index, const char *filename) {
|
||||
if (!q || !filename) return -1;
|
||||
|
||||
unsigned int write = atomic_load(&q->write_index);
|
||||
unsigned int read = atomic_load(&q->read_index);
|
||||
|
||||
if ((write - read) >= MAX_QUEUED_COMMANDS) {
|
||||
fprintf(stderr, "Save/Load queue full, dropping request\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int slot = write % MAX_QUEUED_COMMANDS;
|
||||
q->buffer[slot].type = type;
|
||||
q->buffer[slot].clip_index = clip_index;
|
||||
strncpy(q->buffer[slot].filename, filename, sizeof(q->buffer[slot].filename) - 1);
|
||||
q->buffer[slot].filename[sizeof(q->buffer[slot].filename) - 1] = '\0';
|
||||
|
||||
atomic_store(&q->write_index, write + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Pop a save/load request (called from save/load thread)
|
||||
int save_load_queue_pop(SaveLoadQueue *q, SaveLoadRequest *req) {
|
||||
if (!q || !req) return -1;
|
||||
|
||||
unsigned int read = atomic_load(&q->read_index);
|
||||
unsigned int write = atomic_load(&q->write_index);
|
||||
|
||||
if (read >= write) return 0; // Empty
|
||||
|
||||
unsigned int slot = read % MAX_QUEUED_COMMANDS;
|
||||
*req = q->buffer[slot];
|
||||
|
||||
atomic_store(&q->read_index, read + 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Save/Load thread function
|
||||
void* save_load_thread_func(void *arg) {
|
||||
Engine *engine = (Engine *)arg;
|
||||
if (!engine) return NULL;
|
||||
|
||||
// Create samples directory if it doesn't exist
|
||||
mkdir("samples", 0755);
|
||||
|
||||
while (engine->save_load_running) {
|
||||
SaveLoadRequest req;
|
||||
int ret = save_load_queue_pop(&engine->save_load_queue, &req);
|
||||
|
||||
if (ret == 1) {
|
||||
char filepath[512];
|
||||
|
||||
switch (req.type) {
|
||||
case REQ_SAVE_CLIP: {
|
||||
if (req.clip_index < 0 || req.clip_index >= MAX_CLIPS) break;
|
||||
Clip *clip = &engine->clips[req.clip_index];
|
||||
|
||||
// Build filename: samples/clip_<index>.wav
|
||||
snprintf(filepath, sizeof(filepath), "samples/clip_%d.wav", req.clip_index);
|
||||
|
||||
if (clip->buffer && clip->buffer_size > 0) {
|
||||
int result = save_wav_float(filepath, clip->buffer, clip->buffer_size, engine->sample_rate);
|
||||
if (result == 0) {
|
||||
printf("Saved clip %d to %s (%zu samples)\n", req.clip_index, filepath, clip->buffer_size);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to save clip %d to %s\n", req.clip_index, filepath);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case REQ_LOAD_CLIP: {
|
||||
if (req.clip_index < 0 || req.clip_index >= MAX_CLIPS) break;
|
||||
Clip *clip = &engine->clips[req.clip_index];
|
||||
|
||||
float *new_buffer = NULL;
|
||||
size_t num_samples = 0;
|
||||
unsigned int file_sample_rate = 0;
|
||||
|
||||
int result = load_wav_float(req.filename, &new_buffer, &num_samples, &file_sample_rate);
|
||||
if (result == 0 && new_buffer && num_samples > 0) {
|
||||
// Allocate a new buffer for the clip
|
||||
float *clip_buffer = (float *)calloc(MAX_BUFFER_SIZE, sizeof(float));
|
||||
if (!clip_buffer) {
|
||||
free(new_buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
// Copy samples (truncate if too long)
|
||||
size_t copy_size = (num_samples < MAX_BUFFER_SIZE) ? num_samples : MAX_BUFFER_SIZE;
|
||||
memcpy(clip_buffer, new_buffer, copy_size * sizeof(float));
|
||||
|
||||
// Atomically swap the clip's buffer
|
||||
float *old_buffer = atomic_exchange(&clip->buffer, clip_buffer);
|
||||
|
||||
// Update clip state atomically
|
||||
clip->state = CLIP_LOOPING;
|
||||
clip->buffer_size = copy_size;
|
||||
clip->write_position = copy_size;
|
||||
clip->read_position = 0;
|
||||
|
||||
// Free old buffer and temporary buffer
|
||||
if (old_buffer) free(old_buffer);
|
||||
free(new_buffer);
|
||||
|
||||
printf("Loaded clip %d from %s (%zu samples, %u Hz)\n",
|
||||
req.clip_index, req.filename, num_samples, file_sample_rate);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to load %s into clip %d\n", req.filename, req.clip_index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No requests, sleep a bit to avoid busy-waiting
|
||||
usleep(1000); // 1ms
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Start the save/load thread
|
||||
int engine_start_save_load_thread(Engine *engine) {
|
||||
if (!engine) return -1;
|
||||
|
||||
engine->save_load_running = true;
|
||||
save_load_queue_init(&engine->save_load_queue);
|
||||
|
||||
if (pthread_create(&engine->save_load_thread, NULL, save_load_thread_func, engine) != 0) {
|
||||
engine->save_load_running = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Stop the save/load thread
|
||||
void engine_stop_save_load_thread(Engine *engine) {
|
||||
if (!engine) return;
|
||||
|
||||
engine->save_load_running = false;
|
||||
pthread_join(engine->save_load_thread, NULL);
|
||||
}
|
||||
|
||||
// Submit command from frontend thread (non-blocking)
|
||||
int engine_submit_command(Engine *engine, CommandType type, int index, jack_nframes_t value) {
|
||||
if (!engine) return -1;
|
||||
@@ -309,6 +466,8 @@ void engine_process_commands(Engine *engine) {
|
||||
action.previous_read_position = clip->read_position;
|
||||
engine_push_undo_action(engine, &action);
|
||||
|
||||
ClipState prev_state = clip->state;
|
||||
|
||||
switch (clip->state) {
|
||||
case CLIP_EMPTY:
|
||||
clip->state = CLIP_RECORDING;
|
||||
@@ -330,6 +489,11 @@ void engine_process_commands(Engine *engine) {
|
||||
clip->read_position = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Auto-save when recording finishes (RECORDING -> LOOPING)
|
||||
if (prev_state == CLIP_RECORDING && clip->state == CLIP_LOOPING) {
|
||||
save_load_queue_push(&engine->save_load_queue, REQ_SAVE_CLIP, cmd.index, "");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -347,6 +511,8 @@ 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;
|
||||
|
||||
switch (clip->state) {
|
||||
case CLIP_EMPTY:
|
||||
clip->state = CLIP_RECORDING;
|
||||
@@ -368,6 +534,11 @@ void engine_process_commands(Engine *engine) {
|
||||
clip->read_position = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Auto-save when recording finishes
|
||||
if (prev_state == CLIP_RECORDING && clip->state == CLIP_LOOPING) {
|
||||
save_load_queue_push(&engine->save_load_queue, REQ_SAVE_CLIP, clip_idx, "");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -788,6 +959,9 @@ int engine_init(Engine *engine, const char *client_name) {
|
||||
// Initialize command queue
|
||||
command_queue_init(&engine->command_queue);
|
||||
|
||||
// Initialize save/load queue
|
||||
save_load_queue_init(&engine->save_load_queue);
|
||||
|
||||
// Initialize atomic state mirrors
|
||||
atomic_store(&engine->quantize_mode_atomic, (int)QUANTIZE_OFF);
|
||||
atomic_store(&engine->quantize_threshold_atomic, 0);
|
||||
@@ -921,6 +1095,15 @@ int engine_start(Engine *engine) {
|
||||
}
|
||||
|
||||
engine->running = true;
|
||||
|
||||
// Start save/load thread
|
||||
if (engine_start_save_load_thread(engine) != 0) {
|
||||
fprintf(stderr, "Failed to start save/load thread\n");
|
||||
jack_deactivate(engine->client);
|
||||
engine->running = false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -928,6 +1111,7 @@ void engine_stop(Engine *engine) {
|
||||
if (!engine || !engine->client) return;
|
||||
|
||||
engine->running = false;
|
||||
engine_stop_save_load_thread(engine);
|
||||
jack_deactivate(engine->client);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user