feat: unify add/remove commands into queue and fix race on channel removal
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
@@ -4,10 +4,10 @@
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
CMD_CYCLE, // toggle record/stop for a channel
|
CMD_CYCLE, // toggle record/stop for a channel
|
||||||
CMD_STOP, // force to idle
|
CMD_STOP, // force to idle
|
||||||
// CMD_LOOP_TOGGLE not needed, CYCLE covers it
|
|
||||||
CMD_BIND_CHANNEL, // bind a channel index (data = channel)
|
CMD_BIND_CHANNEL, // bind a channel index (data = channel)
|
||||||
CMD_UNBIND, // reset bind to channel 0
|
CMD_UNBIND, // reset bind to channel 0
|
||||||
// ADD and REMOVE are still driven via atomics for now
|
CMD_ADD_CHANNEL, // add a new dynamic channel
|
||||||
|
CMD_REMOVE_CHANNEL, // remove last dynamic channel
|
||||||
} cmd_type_t;
|
} cmd_type_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
74
src/looper.c
74
src/looper.c
@@ -16,16 +16,17 @@
|
|||||||
struct channel_t channels[MAX_CHANNELS];
|
struct channel_t channels[MAX_CHANNELS];
|
||||||
atomic_int channel_count = 0;
|
atomic_int channel_count = 0;
|
||||||
int next_channel_id = 1;
|
int next_channel_id = 1;
|
||||||
atomic_int cmd_add = 0;
|
spsc_queue_t cmd_queue_main;
|
||||||
atomic_int cmd_remove = 0;
|
atomic_int global_rt_cycles = 0;
|
||||||
jack_port_t *midi_control_port = NULL;
|
jack_port_t *midi_control_port = NULL;
|
||||||
jack_port_t *midi_clock_port = NULL;
|
jack_port_t *midi_clock_port = NULL;
|
||||||
atomic_int control_key_active = 0;
|
atomic_int control_key_active = 0;
|
||||||
atomic_int bind_channel = 0;
|
atomic_int bind_channel = 0;
|
||||||
spsc_queue_t cmd_queue;
|
spsc_queue_t cmd_queue;
|
||||||
|
|
||||||
/* Deferred removal index (1 second grace) */
|
/* Deferred removal index and cycle counter */
|
||||||
static int pending_unregister_idx = -1;
|
static int pending_unregister_idx = -1;
|
||||||
|
static int pending_unregister_cycle = 0;
|
||||||
|
|
||||||
static void apply_command(command_t cmd) {
|
static void apply_command(command_t cmd) {
|
||||||
switch (cmd.type) {
|
switch (cmd.type) {
|
||||||
@@ -199,6 +200,7 @@ int process_callback(jack_nframes_t nframes, void *arg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atomic_fetch_add_explicit(&global_rt_cycles, 1, memory_order_release);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +218,7 @@ void jack_shutdown_cb(void *arg) {
|
|||||||
* ---------------------------------------------------------------- */
|
* ---------------------------------------------------------------- */
|
||||||
int looper_init(jack_client_t *client) {
|
int looper_init(jack_client_t *client) {
|
||||||
queue_init(&cmd_queue);
|
queue_init(&cmd_queue);
|
||||||
|
queue_init(&cmd_queue_main);
|
||||||
/* channel 0 */
|
/* channel 0 */
|
||||||
channels[0].active = 1;
|
channels[0].active = 1;
|
||||||
atomic_store(&channels[0].state, STATE_IDLE);
|
atomic_store(&channels[0].state, STATE_IDLE);
|
||||||
@@ -250,37 +253,46 @@ int looper_init(jack_client_t *client) {
|
|||||||
* main‑loop command processing
|
* main‑loop command processing
|
||||||
* ---------------------------------------------------------------- */
|
* ---------------------------------------------------------------- */
|
||||||
void looper_process_commands(jack_client_t *client) {
|
void looper_process_commands(jack_client_t *client) {
|
||||||
/* Unregister any ports that were marked for deferred removal.
|
/* Drain main‑loop command queue (add/remove) */
|
||||||
By now the real‑time thread has had at least one full cycle
|
command_t cmd;
|
||||||
to see the `active = 0` store. */
|
while (queue_pop(&cmd_queue_main, &cmd)) {
|
||||||
if (pending_unregister_idx != -1) {
|
switch (cmd.type) {
|
||||||
int idx = pending_unregister_idx;
|
case CMD_ADD_CHANNEL: {
|
||||||
if (channels[idx].audio_in)
|
int idx;
|
||||||
jack_port_unregister(client, channels[idx].audio_in);
|
for (idx = 0; idx < MAX_CHANNELS; idx++)
|
||||||
if (channels[idx].audio_out)
|
if (!channels[idx].active)
|
||||||
jack_port_unregister(client, channels[idx].audio_out);
|
break;
|
||||||
pending_unregister_idx = -1;
|
if (idx < MAX_CHANNELS)
|
||||||
}
|
channel_add(client, idx);
|
||||||
|
break;
|
||||||
if (atomic_exchange(&cmd_add, 0)) {
|
}
|
||||||
int idx;
|
case CMD_REMOVE_CHANNEL: {
|
||||||
for (idx = 0; idx < MAX_CHANNELS; idx++)
|
int remove_idx = -1;
|
||||||
if (!channels[idx].active)
|
for (int idx = 1; idx < MAX_CHANNELS; idx++)
|
||||||
break;
|
if (channels[idx].active)
|
||||||
if (idx < MAX_CHANNELS) {
|
remove_idx = idx;
|
||||||
channel_add(client, idx);
|
if (remove_idx != -1) {
|
||||||
|
channel_remove(client, remove_idx);
|
||||||
|
pending_unregister_idx = remove_idx;
|
||||||
|
pending_unregister_cycle = atomic_load(&global_rt_cycles);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atomic_exchange(&cmd_remove, 0)) {
|
/* Deferred port unregistration – wait until RT thread has seen active=0 */
|
||||||
int remove_idx = -1;
|
if (pending_unregister_idx != -1) {
|
||||||
for (int idx = 1; idx < MAX_CHANNELS; idx++)
|
int current_cycle = atomic_load(&global_rt_cycles);
|
||||||
if (channels[idx].active)
|
if (current_cycle - pending_unregister_cycle >= 1) {
|
||||||
remove_idx = idx;
|
int idx = pending_unregister_idx;
|
||||||
if (remove_idx != -1) {
|
if (channels[idx].audio_in)
|
||||||
/* Mark inactive now; ports will be unregistered next round */
|
jack_port_unregister(client, channels[idx].audio_in);
|
||||||
channel_remove(client, remove_idx);
|
if (channels[idx].audio_out)
|
||||||
pending_unregister_idx = remove_idx;
|
jack_port_unregister(client, channels[idx].audio_out);
|
||||||
|
pending_unregister_idx = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/midi.c
27
src/midi.c
@@ -8,10 +8,9 @@
|
|||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
|
|
||||||
extern atomic_int control_key_active;
|
extern atomic_int control_key_active;
|
||||||
extern atomic_int cmd_add;
|
|
||||||
extern atomic_int cmd_remove;
|
|
||||||
extern atomic_int bind_channel;
|
extern atomic_int bind_channel;
|
||||||
extern spsc_queue_t cmd_queue;
|
extern spsc_queue_t cmd_queue;
|
||||||
|
extern spsc_queue_t cmd_queue_main;
|
||||||
|
|
||||||
void midi_handle_events(void *port_buffer, jack_nframes_t nframes) {
|
void midi_handle_events(void *port_buffer, jack_nframes_t nframes) {
|
||||||
(void)nframes;
|
(void)nframes;
|
||||||
@@ -42,11 +41,15 @@ void midi_handle_events(void *port_buffer, jack_nframes_t nframes) {
|
|||||||
} else {
|
} else {
|
||||||
switch (note) {
|
switch (note) {
|
||||||
case 60:
|
case 60:
|
||||||
atomic_store(&cmd_add, 1);
|
{
|
||||||
break;
|
command_t cmd = { .type = CMD_ADD_CHANNEL, .channel = -1, .data = 0 };
|
||||||
|
queue_push(&cmd_queue_main, cmd);
|
||||||
|
} break;
|
||||||
case 61:
|
case 61:
|
||||||
atomic_store(&cmd_remove, 1);
|
{
|
||||||
break;
|
command_t cmd = { .type = CMD_REMOVE_CHANNEL, .channel = -1, .data = 0 };
|
||||||
|
queue_push(&cmd_queue_main, cmd);
|
||||||
|
} break;
|
||||||
case 62:
|
case 62:
|
||||||
{
|
{
|
||||||
int bch = atomic_load(&bind_channel);
|
int bch = atomic_load(&bind_channel);
|
||||||
@@ -73,11 +76,15 @@ void midi_handle_events(void *port_buffer, jack_nframes_t nframes) {
|
|||||||
queue_push(&cmd_queue, cmd);
|
queue_push(&cmd_queue, cmd);
|
||||||
} break;
|
} break;
|
||||||
case 60:
|
case 60:
|
||||||
atomic_store(&cmd_add, 1);
|
{
|
||||||
break;
|
command_t cmd = { .type = CMD_ADD_CHANNEL, .channel = -1, .data = 0 };
|
||||||
|
queue_push(&cmd_queue_main, cmd);
|
||||||
|
} break;
|
||||||
case 61:
|
case 61:
|
||||||
atomic_store(&cmd_remove, 1);
|
{
|
||||||
break;
|
command_t cmd = { .type = CMD_REMOVE_CHANNEL, .channel = -1, .data = 0 };
|
||||||
|
queue_push(&cmd_queue_main, cmd);
|
||||||
|
} break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/pipe.c
13
src/pipe.c
@@ -13,12 +13,9 @@
|
|||||||
#define FIFO_PATH "/tmp/looper_cmd"
|
#define FIFO_PATH "/tmp/looper_cmd"
|
||||||
#define LINE_MAX 256
|
#define LINE_MAX 256
|
||||||
|
|
||||||
/* forward‑declare the global queue (defined in looper.c) */
|
/* forward‑declare the global queues (defined in looper.c) */
|
||||||
extern spsc_queue_t cmd_queue;
|
extern spsc_queue_t cmd_queue;
|
||||||
|
extern spsc_queue_t cmd_queue_main;
|
||||||
/* external atomic flags for add/remove (defined in looper.c) */
|
|
||||||
extern atomic_int cmd_add;
|
|
||||||
extern atomic_int cmd_remove;
|
|
||||||
|
|
||||||
static void *pipe_thread_func(void *arg) {
|
static void *pipe_thread_func(void *arg) {
|
||||||
(void)arg;
|
(void)arg;
|
||||||
@@ -35,9 +32,11 @@ static void *pipe_thread_func(void *arg) {
|
|||||||
line[len-1] = '\0';
|
line[len-1] = '\0';
|
||||||
|
|
||||||
if (strcmp(line, "add") == 0) {
|
if (strcmp(line, "add") == 0) {
|
||||||
atomic_store(&cmd_add, 1);
|
command_t cmd = { .type = CMD_ADD_CHANNEL, .channel = -1, .data = 0 };
|
||||||
|
queue_push(&cmd_queue_main, cmd);
|
||||||
} else if (strcmp(line, "remove") == 0) {
|
} else if (strcmp(line, "remove") == 0) {
|
||||||
atomic_store(&cmd_remove, 1);
|
command_t cmd = { .type = CMD_REMOVE_CHANNEL, .channel = -1, .data = 0 };
|
||||||
|
queue_push(&cmd_queue_main, cmd);
|
||||||
} else if (strncmp(line, "record ", 7) == 0) {
|
} else if (strncmp(line, "record ", 7) == 0) {
|
||||||
int ch = atoi(line + 7);
|
int ch = atoi(line + 7);
|
||||||
command_t cmd = { .type = CMD_CYCLE, .channel = ch, .data = 0 };
|
command_t cmd = { .type = CMD_CYCLE, .channel = ch, .data = 0 };
|
||||||
|
|||||||
Reference in New Issue
Block a user