feat: add address sanitizer, persistent FIFO fds, and latency test

This commit is contained in:
Loic Coenen
2026-05-24 09:22:22 +00:00
committed by Loic Coenen (aider)
parent 0537263a7a
commit dd67576c45
6 changed files with 217 additions and 69 deletions

View File

@@ -1,6 +1,6 @@
CC ?= gcc
CFLAGS ?= -Wall -Wextra -g -Isrc
LDFLAGS ?= -ljack -lm -lsndfile -lpthread
CFLAGS ?= -Wall -Wextra -g -Isrc -fsanitize=address -fno-omit-frame-pointer
LDFLAGS ?= -fsanitize=address -ljack -lm -lsndfile -lpthread
SRC = src/main.c src/looper.c src/channel.c src/midi.c src/queue.c src/pipe.c src/ringbuffer.c src/wav.c src/log.c
OBJ = $(SRC:.c=.o)

View File

@@ -1,6 +1,7 @@
// cppcheck-suppress missingIncludeSystem
#include "looper.h"
#include "channel.h"
#include "log.h"
#include "midi.h"
#include "pipe.h"
#include "queue.h"
@@ -10,12 +11,14 @@
#include <jack/midiport.h>
#include <math.h>
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
/* Global command queues (used by midi.c and pipe.c) */
spsc_queue_t cmd_queue;
@@ -27,15 +30,81 @@ spsc_queue_t cmd_queue_main_fifo;
/* writer status fd */
static int status_fd = -1;
static jack_client_t *global_client = NULL;
/* Global state (shared across files) */
struct channel_t channels[MAX_CHANNELS];
atomic_int channel_count = 0;
atomic_int channel_capacity = MAX_CHANNELS;
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;
atomic_int bind_channel = 0;
/* Track previous state to avoid writing unchanged status lines */
static atomic_int prev_state[MAX_CHANNELS][MAX_SCENES];
/* Unregister all ports and close the JACK client */
static void looper_cleanup(jack_client_t *client) {
for (int c = 0; c < MAX_CHANNELS; c++) {
if (channels[c].audio_in) {
jack_port_unregister(client, channels[c].audio_in);
channels[c].audio_in = NULL;
}
if (channels[c].audio_out) {
jack_port_unregister(client, channels[c].audio_out);
channels[c].audio_out = NULL;
}
if (channels[c].midi_in) {
jack_port_unregister(client, channels[c].midi_in);
channels[c].midi_in = NULL;
}
if (channels[c].midi_out) {
jack_port_unregister(client, channels[c].midi_out);
channels[c].midi_out = NULL;
}
}
if (midi_control_port) {
jack_port_unregister(client, midi_control_port);
midi_control_port = NULL;
}
if (midi_clock_port) {
jack_port_unregister(client, midi_clock_port);
midi_clock_port = NULL;
}
}
/* Signal handler: deactivate and cleanup before exit */
static void signal_handler(int sig) {
(void)sig;
if (global_client) {
looper_cleanup(global_client);
jack_client_close(global_client);
}
log_close();
exit(0);
}
static void looper_write_status(void) {
if (status_fd < 0)
return;
char buf[256];
char buf[4096];
int pos = 0;
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);
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) {
case STATE_IDLE:
@@ -53,29 +122,17 @@ static void looper_write_status(void) {
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;
}
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;
}
if (pos > 0) {
int ret = write(status_fd, buf, pos);
(void)ret;
}
}
/* Global state (shared across files) */
struct channel_t channels[MAX_CHANNELS];
atomic_int channel_count = 0;
atomic_int channel_capacity = MAX_CHANNELS;
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;
atomic_int bind_channel = 0;
/* Deferred removal index (1 second grace) */
static int pending_unregister_idx = -1;
@@ -465,16 +522,28 @@ int looper_init(jack_client_t *client) {
/* store sample rate for writer thread */
global_sample_rate = jack_get_sample_rate(client);
global_client = client;
/* Install signal handlers for graceful shutdown */
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGQUIT, signal_handler);
/* create status FIFO (ignore if already exists) */
mkfifo(STATUS_FIFO, 0666);
/* open the status FIFO for reading+writing so writes work even without reader
*/
status_fd = open(STATUS_FIFO, O_RDWR);
status_fd = open(STATUS_FIFO, O_RDWR | O_NONBLOCK);
if (status_fd < 0) {
perror("open status FIFO");
}
/* initialise prev_state to -1 */
for (int ch = 0; ch < MAX_CHANNELS; ch++)
for (int sc = 0; sc < MAX_SCENES; sc++)
atomic_init(&prev_state[ch][sc], -1);
queue_init(&cmd_queue);
queue_init(&cmd_queue_main_midi);
queue_init(&cmd_queue_main_fifo);