Merge branch '3-integrate-carla'
This commit is contained in:
@@ -2,13 +2,11 @@
|
||||
#include "looper.h"
|
||||
#include "channel.h"
|
||||
#include "midi.h"
|
||||
#include "queue.h"
|
||||
#include "wav.h"
|
||||
#include "ringbuffer.h"
|
||||
#include "pipe.h"
|
||||
#include <jack/jack.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <jack/jack.h>
|
||||
#include <jack/midiport.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
@@ -16,11 +14,10 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "queue.h"
|
||||
#include "command.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Global command queues */
|
||||
/* Global command queues (used by midi.c and pipe.c) */
|
||||
spsc_queue_t cmd_queue;
|
||||
spsc_queue_t cmd_queue_main_midi;
|
||||
spsc_queue_t cmd_queue_main_fifo;
|
||||
@@ -31,29 +28,28 @@ spsc_queue_t cmd_queue_main_fifo;
|
||||
static int status_fd = -1;
|
||||
|
||||
static void looper_write_status(void) {
|
||||
if (status_fd < 0)
|
||||
return;
|
||||
char buf[256];
|
||||
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
|
||||
if (!atomic_load(&channels[ch].active))
|
||||
continue;
|
||||
int state_val = atomic_load(&channels[ch].state);
|
||||
const char *state_str;
|
||||
switch (state_val) {
|
||||
case STATE_IDLE: state_str = "IDLE"; break;
|
||||
case STATE_RECORD: state_str = "RECORD"; break;
|
||||
case STATE_LOOPING: state_str = "LOOPING"; break;
|
||||
case STATE_PAUSED: state_str = "PAUSED"; break;
|
||||
default: state_str = "UNKNOWN";
|
||||
}
|
||||
int n = snprintf(buf, sizeof(buf),
|
||||
"CH=%d SC=%d STATE=%s\n",
|
||||
ch, 0, state_str);
|
||||
if (n > 0) {
|
||||
int ret = write(status_fd, buf, n);
|
||||
(void)ret;
|
||||
}
|
||||
if (status_fd < 0)
|
||||
return;
|
||||
char buf[256];
|
||||
for (int ch = 0; ch < MAX_CHANNELS; ch++) {
|
||||
if (!atomic_load(&channels[ch].active))
|
||||
continue;
|
||||
int sc_idx = atomic_load(&channels[ch].current_scene);
|
||||
int state = atomic_load(&channels[ch].scenes[sc_idx].state);
|
||||
const char *state_str;
|
||||
switch (state) {
|
||||
case STATE_IDLE: state_str = "IDLE"; break;
|
||||
case STATE_RECORD: state_str = "RECORD"; break;
|
||||
case STATE_LOOPING:state_str = "LOOPING";break;
|
||||
case STATE_PAUSED: state_str = "PAUSED"; break;
|
||||
default: state_str = "UNKNOWN";
|
||||
}
|
||||
int n = snprintf(buf, sizeof(buf), "CH=%d SC=%d STATE=%s\n", ch, sc_idx, state_str);
|
||||
if (n > 0) {
|
||||
int ret = write(status_fd, buf, n);
|
||||
(void)ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Global state (shared across files) */
|
||||
@@ -84,27 +80,30 @@ static void exec_command(command_t cmd, jack_client_t *client) {
|
||||
|
||||
switch (cmd.type) {
|
||||
case CMD_CYCLE: {
|
||||
int state = atomic_load(&channels[ch].state);
|
||||
int sc_idx = atomic_load(&channels[ch].current_scene);
|
||||
int state = atomic_load(&channels[ch].scenes[sc_idx].state);
|
||||
switch (state) {
|
||||
case STATE_IDLE:
|
||||
atomic_store(&channels[ch].state, STATE_RECORD);
|
||||
atomic_store(&channels[ch].scenes[sc_idx].state, STATE_RECORD);
|
||||
break;
|
||||
case STATE_RECORD:
|
||||
atomic_store(&channels[ch].state, STATE_LOOPING);
|
||||
atomic_store(&channels[ch].scenes[sc_idx].state, STATE_LOOPING);
|
||||
break;
|
||||
case STATE_LOOPING:
|
||||
atomic_store(&channels[ch].state, STATE_PAUSED);
|
||||
atomic_store(&channels[ch].scenes[sc_idx].state, STATE_PAUSED);
|
||||
break;
|
||||
case STATE_PAUSED:
|
||||
atomic_store(&channels[ch].state, STATE_LOOPING);
|
||||
atomic_store(&channels[ch].scenes[sc_idx].state, STATE_LOOPING);
|
||||
break;
|
||||
}
|
||||
atomic_store(&channels[ch].prev_state, -1);
|
||||
atomic_store(&channels[ch].scenes[sc_idx].prev_state, -1);
|
||||
break;
|
||||
}
|
||||
case CMD_STOP:
|
||||
atomic_store(&channels[ch].state, STATE_IDLE);
|
||||
atomic_store(&channels[ch].prev_state, -1);
|
||||
for (int s = 0; s < atomic_load(&channels[ch].scene_count); s++) {
|
||||
atomic_store(&channels[ch].scenes[s].state, STATE_IDLE);
|
||||
atomic_store(&channels[ch].scenes[s].prev_state, -1);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_ADD_CHANNEL:
|
||||
@@ -147,9 +146,19 @@ static void exec_command(command_t cmd, jack_client_t *client) {
|
||||
break;
|
||||
|
||||
case CMD_ADD_SCENE:
|
||||
channel_add_scene(client, ch);
|
||||
break;
|
||||
|
||||
case CMD_REMOVE_SCENE:
|
||||
channel_remove_scene(client, ch);
|
||||
break;
|
||||
|
||||
case CMD_NEXT_SCENE:
|
||||
channel_next_scene(client, ch);
|
||||
break;
|
||||
|
||||
case CMD_PREV_SCENE:
|
||||
channel_prev_scene(client, ch);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -181,45 +190,113 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const jack_default_audio_sample_t *in =
|
||||
(const jack_default_audio_sample_t *)jack_port_get_buffer(
|
||||
channels[c].audio_in, nframes);
|
||||
jack_default_audio_sample_t *out =
|
||||
(jack_default_audio_sample_t *)jack_port_get_buffer(
|
||||
channels[c].audio_out, nframes);
|
||||
if (!out)
|
||||
continue;
|
||||
/* For each channel, use the current scene */
|
||||
int sc_idx = atomic_load(&channels[c].current_scene);
|
||||
scene_t *sc = &channels[c].scenes[sc_idx];
|
||||
int state = atomic_load(&sc->state);
|
||||
int prev = atomic_load(&sc->prev_state);
|
||||
|
||||
int state = atomic_load(&channels[c].state);
|
||||
|
||||
if (state != atomic_load(&channels[c].prev_state)) {
|
||||
if (state != prev) {
|
||||
switch (state) {
|
||||
case STATE_RECORD:
|
||||
atomic_store(&channels[c].record_pos, 0);
|
||||
atomic_store(&channels[c].loop_count, 0);
|
||||
atomic_store(&sc->record_pos, 0);
|
||||
atomic_store(&sc->loop_count, 0);
|
||||
break;
|
||||
case STATE_LOOPING:
|
||||
if (atomic_load(&channels[c].prev_state) == STATE_RECORD &&
|
||||
atomic_load(&channels[c].record_pos) > 0)
|
||||
atomic_store(&channels[c].loop_count,
|
||||
atomic_load(&channels[c].record_pos));
|
||||
atomic_store(&channels[c].playback_pos, 0);
|
||||
if (prev == STATE_RECORD && atomic_load(&sc->record_pos) > 0)
|
||||
atomic_store(&sc->loop_count, atomic_load(&sc->record_pos));
|
||||
atomic_store(&sc->playback_pos, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jack_nframes_t i;
|
||||
/* Handle MIDI channels separately */
|
||||
if (channels[c].type == CHANNEL_MIDI) {
|
||||
/* MIDI channel handling */
|
||||
void *midi_in_buf = jack_port_get_buffer(channels[c].midi_in, nframes);
|
||||
void *midi_out_buf = jack_port_get_buffer(channels[c].midi_out, nframes);
|
||||
if (!midi_out_buf) continue;
|
||||
|
||||
switch (state) {
|
||||
case STATE_RECORD: {
|
||||
if (midi_in_buf) {
|
||||
jack_nframes_t nevents = jack_midi_get_event_count(midi_in_buf);
|
||||
jack_midi_event_t ev;
|
||||
for (jack_nframes_t j = 0; j < nevents; j++) {
|
||||
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0)
|
||||
continue;
|
||||
int rp = atomic_load(&sc->record_pos);
|
||||
if (rp < MAX_MIDI_EVENTS) {
|
||||
sc->loop.midi_events[rp].timestamp = ev.time;
|
||||
sc->loop.midi_events[rp].status = ev.buffer[0];
|
||||
sc->loop.midi_events[rp].note = (ev.size > 1) ? ev.buffer[1] : 0;
|
||||
sc->loop.midi_events[rp].velocity = (ev.size > 2) ? ev.buffer[2] : 0;
|
||||
atomic_store(&sc->record_pos, rp + 1);
|
||||
}
|
||||
}
|
||||
/* forward incoming MIDI to output during record */
|
||||
jack_midi_clear_buffer(midi_out_buf);
|
||||
for (jack_nframes_t j = 0; j < nevents; j++) {
|
||||
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0)
|
||||
continue;
|
||||
jack_midi_event_write(midi_out_buf, ev.time, ev.buffer, ev.size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATE_LOOPING: {
|
||||
jack_midi_clear_buffer(midi_out_buf);
|
||||
int cnt = atomic_load(&sc->loop_count);
|
||||
if (cnt > 0) {
|
||||
for (int e = 0; e < cnt; e++) {
|
||||
unsigned char msg[3];
|
||||
msg[0] = sc->loop.midi_events[e].status;
|
||||
msg[1] = sc->loop.midi_events[e].note;
|
||||
msg[2] = sc->loop.midi_events[e].velocity;
|
||||
jack_midi_event_write(midi_out_buf, 0, msg, 3);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case STATE_PAUSED:
|
||||
jack_midi_clear_buffer(midi_out_buf);
|
||||
break;
|
||||
default: /* IDLE */
|
||||
jack_midi_clear_buffer(midi_out_buf);
|
||||
if (midi_in_buf) {
|
||||
jack_nframes_t nevents = jack_midi_get_event_count(midi_in_buf);
|
||||
jack_midi_event_t ev;
|
||||
for (jack_nframes_t j = 0; j < nevents; j++) {
|
||||
if (jack_midi_event_get(&ev, midi_in_buf, j) != 0)
|
||||
continue;
|
||||
jack_midi_event_write(midi_out_buf, ev.time, ev.buffer, ev.size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Audio channel handling */
|
||||
const jack_default_audio_sample_t *in =
|
||||
(const jack_default_audio_sample_t *)jack_port_get_buffer(
|
||||
channels[c].audio_in, nframes);
|
||||
jack_default_audio_sample_t *out =
|
||||
(jack_default_audio_sample_t *)jack_port_get_buffer(
|
||||
channels[c].audio_out, nframes);
|
||||
if (!out) continue;
|
||||
|
||||
switch (state) {
|
||||
case STATE_RECORD:
|
||||
if (in) {
|
||||
float *f_out = (float *)out;
|
||||
const float *f_in = (const float *)in;
|
||||
for (i = 0; i < nframes; i++) {
|
||||
int rp = atomic_fetch_add(&channels[c].record_pos, 1);
|
||||
for (jack_nframes_t i = 0; i < nframes; i++) {
|
||||
int rp = atomic_fetch_add(&sc->record_pos, 1);
|
||||
if (rp < LOOP_BUF_SIZE)
|
||||
channels[c].loop_buffer[rp] = f_in[i];
|
||||
sc->loop.audio_buffer[rp] = f_in[i];
|
||||
f_out[i] = f_in[i];
|
||||
}
|
||||
} else {
|
||||
@@ -227,19 +304,21 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_LOOPING:
|
||||
int lc = atomic_load(&channels[c].loop_count);
|
||||
if (lc > 0) {
|
||||
case STATE_LOOPING: {
|
||||
int loop_cnt = atomic_load(&sc->loop_count);
|
||||
if (loop_cnt > 0) {
|
||||
float *outf = (float *)out;
|
||||
for (i = 0; i < nframes; i++) {
|
||||
int pp = atomic_load(&channels[c].playback_pos);
|
||||
outf[i] = channels[c].loop_buffer[pp];
|
||||
atomic_store(&channels[c].playback_pos, (pp + 1) % lc);
|
||||
int pp = atomic_load(&sc->playback_pos);
|
||||
for (jack_nframes_t i = 0; i < nframes; i++) {
|
||||
outf[i] = sc->loop.audio_buffer[pp];
|
||||
pp = (pp + 1) % loop_cnt;
|
||||
}
|
||||
atomic_store(&sc->playback_pos, pp);
|
||||
} else {
|
||||
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case STATE_PAUSED:
|
||||
memset(out, 0, sizeof(jack_default_audio_sample_t) * nframes);
|
||||
@@ -254,20 +333,20 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
break;
|
||||
}
|
||||
|
||||
// push loop output into save ring if saving (atomic load)
|
||||
/* push loop output into save ring if saving (atomic load) */
|
||||
RingBuf *r = (RingBuf *)atomic_load_explicit(&channels[c].save_ring,
|
||||
memory_order_acquire);
|
||||
if (r != NULL) {
|
||||
if (state == STATE_LOOPING && atomic_load(&channels[c].loop_count) > 0) {
|
||||
if (r != NULL && !atomic_load(&channels[c].save_complete)) {
|
||||
if (state == STATE_LOOPING && atomic_load(&sc->loop_count) > 0) {
|
||||
const float *outf = (const float *)out;
|
||||
ring_write(r, outf, nframes);
|
||||
}
|
||||
}
|
||||
|
||||
atomic_store(&channels[c].prev_state, state);
|
||||
atomic_store(&sc->prev_state, state);
|
||||
}
|
||||
|
||||
/* MIDI clock events – affect channel 0 only */
|
||||
/* MIDI clock events – affect current scene of channel 0 */
|
||||
if (midi_clock_port) {
|
||||
void *midi_clock_buf = jack_port_get_buffer(midi_clock_port, nframes);
|
||||
if (midi_clock_buf) {
|
||||
@@ -280,18 +359,22 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
unsigned char msg = cev.buffer[0];
|
||||
switch (msg) {
|
||||
case 0xFA: {
|
||||
int s = atomic_load(&channels[0].state);
|
||||
int sc0 = atomic_load(&channels[0].current_scene);
|
||||
int s = atomic_load(&channels[0].scenes[sc0].state);
|
||||
if (s == STATE_IDLE)
|
||||
atomic_store(&channels[0].state, STATE_RECORD);
|
||||
atomic_store(&channels[0].scenes[sc0].state, STATE_RECORD);
|
||||
break;
|
||||
}
|
||||
case 0xFC:
|
||||
atomic_store(&channels[0].state, STATE_IDLE);
|
||||
case 0xFC: {
|
||||
int sc0 = atomic_load(&channels[0].current_scene);
|
||||
atomic_store(&channels[0].scenes[sc0].state, STATE_IDLE);
|
||||
break;
|
||||
}
|
||||
case 0xFB: {
|
||||
int s = atomic_load(&channels[0].state);
|
||||
int sc0 = atomic_load(&channels[0].current_scene);
|
||||
int s = atomic_load(&channels[0].scenes[sc0].state);
|
||||
if (s == STATE_PAUSED)
|
||||
atomic_store(&channels[0].state, STATE_LOOPING);
|
||||
atomic_store(&channels[0].scenes[sc0].state, STATE_LOOPING);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -341,12 +424,15 @@ int looper_init(jack_client_t *client) {
|
||||
|
||||
/* channel 0 */
|
||||
channels[0].active = 1;
|
||||
atomic_store(&channels[0].state, STATE_IDLE);
|
||||
atomic_store(&channels[0].prev_state, -1);
|
||||
channels[0].loop_count = 0;
|
||||
atomic_store(&channels[0].record_pos, 0);
|
||||
atomic_store(&channels[0].playback_pos, 0);
|
||||
channels[0].type = CHANNEL_AUDIO; /* default */
|
||||
channels[0].current_scene = 0;
|
||||
channels[0].scene_count = 1;
|
||||
init_scene(&channels[0].scenes[0]); /* sets state IDLE, prev_state -1 */
|
||||
atomic_store(&channels[0].scenes[0].loop_count, 0);
|
||||
atomic_store(&channels[0].scenes[0].record_pos, 0);
|
||||
atomic_store(&channels[0].scenes[0].playback_pos, 0);
|
||||
atomic_store_explicit(&channels[0].save_ring, NULL, memory_order_release);
|
||||
atomic_store(&channels[0].save_complete, 0);
|
||||
|
||||
channels[0].audio_in = jack_port_register(
|
||||
client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
|
||||
@@ -375,6 +461,8 @@ int looper_init(jack_client_t *client) {
|
||||
* ---------------------------------------------------------------- */
|
||||
static void *writer_thread(void *arg) {
|
||||
struct channel_t *ch = (struct channel_t *)arg;
|
||||
int sc_idx = atomic_load(&ch->current_scene);
|
||||
scene_t *sc = &ch->scenes[sc_idx];
|
||||
RingBuf *ring = (RingBuf *)ch->save_ring;
|
||||
if (!ring)
|
||||
return NULL;
|
||||
@@ -384,7 +472,7 @@ static void *writer_thread(void *arg) {
|
||||
if (sr == 0)
|
||||
sr = 48000;
|
||||
|
||||
int lc = atomic_load(&ch->loop_count);
|
||||
int lc = atomic_load(&sc->loop_count);
|
||||
float *outbuf = malloc((size_t)lc * sizeof(float));
|
||||
if (!outbuf) {
|
||||
ring_destroy(ring);
|
||||
@@ -405,6 +493,12 @@ static void *writer_thread(void *arg) {
|
||||
wav_write(path, outbuf, (unsigned)lc, sr);
|
||||
free(outbuf);
|
||||
|
||||
/* Signal the RT thread to stop writing */
|
||||
atomic_store_explicit(&ch->save_complete, 1, memory_order_release);
|
||||
/* Wait for the RT thread to see the flag (one audio period) */
|
||||
struct timespec req = { .tv_sec = 0, .tv_nsec = 10000000 }; /* 10ms */
|
||||
nanosleep(&req, NULL);
|
||||
|
||||
ring_destroy(ring);
|
||||
free(ring);
|
||||
atomic_store_explicit(&ch->save_ring, NULL, memory_order_release);
|
||||
@@ -467,14 +561,16 @@ void looper_process_commands(jack_client_t *client) {
|
||||
printf("LOAD: wav_read called\n");
|
||||
if (wav_read("loop.wav", &buf, &frames) == 0 && frames > 0) {
|
||||
printf("LOAD: success, frames=%u\n", frames);
|
||||
int sc_idx = atomic_load(&channels[0].current_scene);
|
||||
scene_t *sc = &channels[0].scenes[sc_idx];
|
||||
if (frames > LOOP_BUF_SIZE)
|
||||
frames = LOOP_BUF_SIZE;
|
||||
memcpy(channels[0].loop_buffer, buf, frames * sizeof(float));
|
||||
atomic_store(&channels[0].loop_count, (int)frames);
|
||||
atomic_store(&channels[0].record_pos, 0);
|
||||
atomic_store(&channels[0].playback_pos, 0);
|
||||
atomic_store(&channels[0].state, STATE_LOOPING);
|
||||
atomic_store(&channels[0].prev_state, -1);
|
||||
memcpy(sc->loop.audio_buffer, buf, frames * sizeof(float));
|
||||
atomic_store(&sc->loop_count, (int)frames);
|
||||
atomic_store(&sc->record_pos, 0);
|
||||
atomic_store(&sc->playback_pos, 0);
|
||||
atomic_store(&sc->state, STATE_LOOPING);
|
||||
atomic_store(&sc->prev_state, -1);
|
||||
free(buf);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to load loop.wav\n");
|
||||
@@ -484,8 +580,10 @@ void looper_process_commands(jack_client_t *client) {
|
||||
|
||||
/* ---------- save command (writer thread) ---------- */
|
||||
if (atomic_exchange(&cmd_save, 0)) {
|
||||
int lc = atomic_load(&channels[0].loop_count);
|
||||
if (atomic_load(&channels[0].state) == STATE_LOOPING && lc > 0 &&
|
||||
int sc_idx = atomic_load(&channels[0].current_scene);
|
||||
scene_t *sc = &channels[0].scenes[sc_idx];
|
||||
int lc = atomic_load(&sc->loop_count);
|
||||
if (atomic_load(&sc->state) == STATE_LOOPING && lc > 0 &&
|
||||
channels[0].save_ring == NULL) {
|
||||
RingBuf *ring = (RingBuf *)malloc(sizeof(RingBuf));
|
||||
if (ring) {
|
||||
|
||||
Reference in New Issue
Block a user