feat: add direct JACK port connection and VU meter support
This commit is contained in:
committed by
Loic Coenen (aider)
parent
1e62ec9310
commit
316320c294
@@ -58,6 +58,7 @@ struct channel_t {
|
||||
|
||||
_Atomic RingBuf *save_ring;
|
||||
atomic_int save_complete; /* 1 when writer is done; RT thread must stop writing */
|
||||
_Atomic float rms_level; /* RMS output level (computed in RT thread) */
|
||||
};
|
||||
|
||||
/* Globals declared in looper.c */
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "queue.h"
|
||||
#include "wav.h"
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
#include <jack/jack.h>
|
||||
#include <jack/midiport.h>
|
||||
#include <math.h>
|
||||
@@ -30,7 +31,7 @@ spsc_queue_t cmd_queue_main_fifo;
|
||||
/* writer status fd */
|
||||
static int status_fd = -1;
|
||||
|
||||
static jack_client_t *global_client = NULL;
|
||||
jack_client_t *global_client = NULL;
|
||||
|
||||
/* Global state (shared across files) */
|
||||
struct channel_t channels[MAX_CHANNELS];
|
||||
@@ -105,9 +106,6 @@ static void looper_write_status(void) {
|
||||
int sc_idx = atomic_load(&channels[ch].current_scene);
|
||||
int state = atomic_load(&channels[ch].scenes[sc_idx].state);
|
||||
int prev = atomic_load(&prev_state[ch][sc_idx]);
|
||||
if (state == prev)
|
||||
continue; /* unchanged, skip */
|
||||
atomic_store(&prev_state[ch][sc_idx], state);
|
||||
|
||||
const char *state_str;
|
||||
switch (state) {
|
||||
@@ -126,10 +124,22 @@ static void looper_write_status(void) {
|
||||
default:
|
||||
state_str = "UNKNOWN";
|
||||
}
|
||||
/* Always write state line to guarantee level line is sent even if state unchanged */
|
||||
int n = snprintf(buf + pos, sizeof(buf) - pos,
|
||||
"CH=%d SC=%d STATE=%s\n", ch, sc_idx, state_str);
|
||||
if (n > 0) pos += n;
|
||||
if (pos >= (int)sizeof(buf) - 128) break;
|
||||
|
||||
/* Write RMS level line every time (no change detection) */
|
||||
{
|
||||
float level = atomic_load(&channels[ch].rms_level);
|
||||
int n2 = snprintf(buf + pos, sizeof(buf) - pos,
|
||||
"CH=%d LEVEL=%f\n", ch, level);
|
||||
if (n2 > 0) pos += n2;
|
||||
if (pos >= (int)sizeof(buf) - 128) break;
|
||||
}
|
||||
|
||||
atomic_store(&prev_state[ch][sc_idx], state);
|
||||
}
|
||||
if (pos > 0) {
|
||||
int ret = write(status_fd, buf, pos);
|
||||
@@ -455,6 +465,21 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Compute RMS level for this channel */
|
||||
{
|
||||
float sum_sq = 0.0f;
|
||||
const float *f_out = (const float *)out;
|
||||
for (jack_nframes_t i = 0; i < nframes; i++)
|
||||
sum_sq += f_out[i] * f_out[i];
|
||||
float rms = sqrtf(sum_sq / nframes);
|
||||
atomic_store(&channels[c].rms_level, rms);
|
||||
static float last_rms[MAX_CHANNELS] = {0};
|
||||
if (rms > 0.001f && fabsf(rms - last_rms[c]) > 0.005f) {
|
||||
fprintf(stderr, "RMS ch%d = %f\n", c, rms);
|
||||
last_rms[c] = rms;
|
||||
}
|
||||
}
|
||||
|
||||
/* push loop output into save ring if saving (atomic load) */
|
||||
RingBuf *r = (RingBuf *)atomic_load_explicit(&channels[c].save_ring,
|
||||
memory_order_acquire);
|
||||
@@ -552,9 +577,6 @@ int looper_init(jack_client_t *client) {
|
||||
queue_init(&cmd_queue_main_midi);
|
||||
queue_init(&cmd_queue_main_fifo);
|
||||
|
||||
/* start the FIFO reader thread */
|
||||
pipe_start_reader();
|
||||
|
||||
/* channel 0 */
|
||||
channels[0].active = 1;
|
||||
channels[0].type = CHANNEL_AUDIO; /* default */
|
||||
@@ -592,6 +614,9 @@ int looper_init(jack_client_t *client) {
|
||||
nanosleep(&req, NULL);
|
||||
}
|
||||
|
||||
/* start the FIFO reader thread (after ports are registered) */
|
||||
pipe_start_reader();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -648,8 +673,8 @@ void looper_process_commands(jack_client_t *client) {
|
||||
if (atomic_exchange(&cmd_load, 0)) {
|
||||
float *buf = NULL;
|
||||
unsigned frames = 0;
|
||||
fprintf(stderr, "LOAD: wav_read called\n");
|
||||
if (wav_read("loop.wav", &buf, &frames) == 0 && frames > 0) {
|
||||
fprintf(stderr, "LOAD: wav_read called for %s\n", load_filename);
|
||||
if (wav_read(load_filename, &buf, &frames) == 0 && frames > 0) {
|
||||
fprintf(stderr, "LOAD: success, frames=%u\n", frames);
|
||||
int sc_idx = atomic_load(&channels[0].current_scene);
|
||||
scene_t *sc = &channels[0].scenes[sc_idx];
|
||||
@@ -663,7 +688,7 @@ void looper_process_commands(jack_client_t *client) {
|
||||
atomic_store(&sc->prev_state, -1);
|
||||
free(buf);
|
||||
} else {
|
||||
fprintf(stderr, "Failed to load loop.wav\n");
|
||||
fprintf(stderr, "Failed to load %s\n", load_filename);
|
||||
fprintf(stderr, "LOAD: FAILED\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// cppcheck-suppress missingIncludeSystem
|
||||
#include <jack/jack.h>
|
||||
|
||||
extern jack_client_t *global_client;
|
||||
|
||||
/* Initialisation – must be called after setting process callback */
|
||||
int looper_init(jack_client_t *client);
|
||||
|
||||
|
||||
@@ -10,10 +10,20 @@
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <jack/jack.h>
|
||||
|
||||
#define FIFO_PATH "/tmp/looper_cmd"
|
||||
#define LINE_MAX 256
|
||||
|
||||
/* Global JACK client (from looper.c) */
|
||||
extern jack_client_t *global_client;
|
||||
|
||||
/* Stored ports for from/to */
|
||||
static char fifo_from[256] = "";
|
||||
static char fifo_to[256] = "";
|
||||
/* Filename for the next load command (default "loop.wav") */
|
||||
char load_filename[256] = "loop.wav";
|
||||
|
||||
/* forward‑declare the global queues (defined in looper.c) */
|
||||
extern spsc_queue_t cmd_queue;
|
||||
extern spsc_queue_t cmd_queue_main_fifo;
|
||||
@@ -78,13 +88,92 @@ static void *pipe_thread_func(void *arg) {
|
||||
command_t cmd = {.type = CMD_SET_SCENE, .channel = ch, .data = sc};
|
||||
queue_push(&cmd_queue_main_fifo, cmd);
|
||||
}
|
||||
} else if (strcmp(line, "load") == 0) {
|
||||
} else if (strncmp(line, "load", 4) == 0) {
|
||||
/* Parse optional filename after "load " */
|
||||
const char *fn = line + 4;
|
||||
while (*fn == ' ') fn++;
|
||||
if (*fn == '\0') {
|
||||
strncpy(load_filename, "loop.wav", sizeof(load_filename) - 1);
|
||||
} else {
|
||||
strncpy(load_filename, fn, sizeof(load_filename) - 1);
|
||||
}
|
||||
load_filename[sizeof(load_filename) - 1] = '\0';
|
||||
fprintf(stderr, "FIFO RECEIVED load: %s\n", load_filename);
|
||||
command_t cmd = {.type = CMD_LOAD, .channel = -1, .data = 0};
|
||||
queue_push(&cmd_queue_main_fifo, cmd);
|
||||
} else if (strcmp(line, "save") == 0) {
|
||||
command_t cmd = {.type = CMD_SAVE, .channel = -1, .data = 0};
|
||||
queue_push(&cmd_queue_main_fifo, cmd);
|
||||
}
|
||||
// --- from <port> ---
|
||||
else if (strncmp(line, "from ", 5) == 0) {
|
||||
fprintf(stderr, "FIFO RECEIVED from: %s\n", line + 5);
|
||||
strncpy(fifo_from, line + 5, sizeof(fifo_from)-1);
|
||||
fifo_from[sizeof(fifo_from)-1] = '\0';
|
||||
// Immediately connect source to looper:input (independently of :to)
|
||||
if (global_client) {
|
||||
const char *target = "looper:input";
|
||||
int ret = jack_connect(global_client, fifo_from, target);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Failed to connect %s -> %s (ret=%d), retrying...\n", fifo_from, target, ret);
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 500000000};
|
||||
nanosleep(&ts, NULL);
|
||||
ret = jack_connect(global_client, fifo_from, target);
|
||||
if (ret != 0)
|
||||
fprintf(stderr, "Retry also failed %s -> %s (ret=%d)\n", fifo_from, target, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- to <port> ---
|
||||
else if (strncmp(line, "to ", 3) == 0) {
|
||||
fprintf(stderr, "FIFO RECEIVED to: %s\n", line + 3);
|
||||
strncpy(fifo_to, line + 3, sizeof(fifo_to)-1);
|
||||
fifo_to[sizeof(fifo_to)-1] = '\0';
|
||||
// Immediately connect looper:output to target (independently of :from)
|
||||
if (global_client) {
|
||||
const char *source = "looper:output";
|
||||
int ret = jack_connect(global_client, source, fifo_to);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Failed to connect %s -> %s (ret=%d), retrying...\n", source, fifo_to, ret);
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 500000000};
|
||||
nanosleep(&ts, NULL);
|
||||
ret = jack_connect(global_client, source, fifo_to);
|
||||
if (ret != 0)
|
||||
fprintf(stderr, "Retry also failed %s -> %s (ret=%d)\n", source, fifo_to, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- connect [from] [to] ---
|
||||
else if (strncmp(line, "connect", 7) == 0) {
|
||||
char from[256] = "";
|
||||
char to[256] = "";
|
||||
// parse optional arguments: "connect from to"
|
||||
char *p = line + 7;
|
||||
while (*p == ' ') p++;
|
||||
if (*p) {
|
||||
char *space = strchr(p, ' ');
|
||||
if (space) {
|
||||
strncpy(from, p, space - p); from[space-p] = '\0';
|
||||
strncpy(to, space+1, sizeof(to)-1);
|
||||
} else {
|
||||
strncpy(from, p, sizeof(from)-1);
|
||||
}
|
||||
}
|
||||
// fallback to stored ports
|
||||
if (!from[0]) strncpy(from, fifo_from, sizeof(from)-1);
|
||||
if (!to[0]) strncpy(to, fifo_to, sizeof(to)-1);
|
||||
if (from[0] && to[0] && global_client) {
|
||||
int ret = jack_connect(global_client, from, to);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Failed to connect %s -> %s (ret=%d), retrying...\n", from, to, ret);
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 500000000};
|
||||
nanosleep(&ts, NULL);
|
||||
ret = jack_connect(global_client, from, to);
|
||||
if (ret != 0)
|
||||
fprintf(stderr, "Retry also failed %s -> %s (ret=%d)\n", from, to, ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* ignore unknown lines */
|
||||
}
|
||||
/* EOF – all writers closed, reopen for next connection */
|
||||
|
||||
@@ -6,4 +6,7 @@
|
||||
* Returns 0 on success, -1 on failure. */
|
||||
int pipe_start_reader(void);
|
||||
|
||||
/** Filename for the next load command (default "loop.wav") */
|
||||
extern char load_filename[256];
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user