feat: add WAV load/save and ring buffer implementation

Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
Loic Coenen
2026-05-11 21:15:12 +00:00
parent 6b490ed739
commit 5a2414b4c3
9 changed files with 319 additions and 3 deletions

View File

@@ -2,6 +2,7 @@
#include "looper.h"
#include "channel.h"
#include "midi.h"
#include "wav.h"
#include <jack/jack.h>
#include <jack/midiport.h>
#include <math.h>
@@ -9,6 +10,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
/* Global state (shared across files) */
struct channel_t channels[MAX_CHANNELS];
@@ -16,6 +19,8 @@ atomic_int channel_count = 0;
int next_channel_id = 1;
atomic_int cmd_add = 0;
atomic_int cmd_remove = 0;
atomic_int cmd_load = 0;
atomic_int cmd_save = 0;
jack_port_t *midi_control_port = NULL;
jack_port_t *midi_clock_port = NULL;
atomic_int control_key_active = 0;
@@ -24,6 +29,10 @@ atomic_int bind_channel = 0;
/* Deferred removal index (1 second grace) */
static int pending_unregister_idx = -1;
/* writer thread function and sample rate holder */
static void *writer_thread(void *arg);
static int global_sample_rate = 0;
/* ----------------------------------------------------------------
* process callback
* ---------------------------------------------------------------- */
@@ -172,6 +181,9 @@ void jack_shutdown_cb(void *arg) {
* looper initialisation
* ---------------------------------------------------------------- */
int looper_init(jack_client_t *client) {
/* store sample rate for writer thread */
global_sample_rate = jack_get_sample_rate(client);
/* channel 0 */
channels[0].active = 1;
atomic_store(&channels[0].state, STATE_IDLE);
@@ -179,6 +191,7 @@ int looper_init(jack_client_t *client) {
channels[0].loop_count = 0;
channels[0].record_pos = 0;
channels[0].playback_pos = 0;
channels[0].save_ring = NULL;
channels[0].audio_in = jack_port_register(
client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
@@ -202,6 +215,44 @@ int looper_init(jack_client_t *client) {
return 0;
}
/* ----------------------------------------------------------------
* writer thread consumes the save ring and writes WAV file
* ---------------------------------------------------------------- */
static void *writer_thread(void *arg) {
struct channel_t *ch = (struct channel_t *)arg;
RingBuf *ring = ch->save_ring;
if (!ring) return NULL;
static const char *path = "save.wav";
unsigned sr = (unsigned)global_sample_rate;
if (sr == 0) sr = 48000;
float *outbuf = malloc((size_t)ch->loop_count * sizeof(float));
if (!outbuf) {
ring_destroy(ring);
free(ring);
ch->save_ring = NULL;
return NULL;
}
size_t collected = 0;
size_t want = (size_t)ch->loop_count;
while (collected < want) {
size_t got = ring_read(ring, outbuf + collected, want - collected);
collected += got;
if (got == 0) {
struct timespec req = { .tv_sec = 0, .tv_nsec = 10000000 };
nanosleep(&req, NULL);
}
}
wav_write(path, outbuf, ch->loop_count, sr);
free(outbuf);
ring_destroy(ring);
free(ring);
ch->save_ring = NULL;
return NULL;
}
/* ----------------------------------------------------------------
* mainloop command processing
* ---------------------------------------------------------------- */
@@ -239,4 +290,42 @@ void looper_process_commands(jack_client_t *client) {
pending_unregister_idx = remove_idx;
}
}
/* ---------- load command ---------- */
if (atomic_exchange(&cmd_load, 0)) {
float *buf = NULL;
unsigned frames = 0;
if (wav_read("loop.wav", &buf, &frames) == 0 && frames > 0) {
if (frames > LOOP_BUF_SIZE) frames = LOOP_BUF_SIZE;
memcpy(channels[0].loop_buffer, buf, frames * sizeof(float));
channels[0].loop_count = (int)frames;
channels[0].record_pos = 0;
channels[0].playback_pos = 0;
atomic_store(&channels[0].state, STATE_LOOPING);
channels[0].prev_state = -1;
free(buf);
} else {
fprintf(stderr, "Failed to load loop.wav\n");
}
}
/* ---------- save command (writer thread) ---------- */
if (atomic_exchange(&cmd_save, 0)) {
if (atomic_load(&channels[0].state) == STATE_LOOPING &&
channels[0].loop_count > 0 &&
channels[0].save_ring == NULL) {
RingBuf *ring = (RingBuf*)malloc(sizeof(RingBuf));
if (ring) {
size_t sz = (size_t)channels[0].loop_count * 2;
if (ring_init(ring, sz) == 0) {
channels[0].save_ring = ring;
pthread_t th;
pthread_create(&th, NULL, writer_thread, &channels[0]);
pthread_detach(th);
} else {
free(ring);
}
}
}
}
}