diff --git a/src/command.h b/src/command.h index 717e13b..d14dd9c 100644 --- a/src/command.h +++ b/src/command.h @@ -4,10 +4,10 @@ typedef enum { CMD_CYCLE, // toggle record/stop for a channel CMD_STOP, // force to idle - // CMD_LOOP_TOGGLE not needed, CYCLE covers it CMD_BIND_CHANNEL, // bind a channel index (data = channel) 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; typedef struct { diff --git a/src/looper.c b/src/looper.c index cbc9dd2..d4c5254 100644 --- a/src/looper.c +++ b/src/looper.c @@ -16,16 +16,17 @@ struct channel_t channels[MAX_CHANNELS]; atomic_int channel_count = 0; int next_channel_id = 1; -atomic_int cmd_add = 0; -atomic_int cmd_remove = 0; +spsc_queue_t cmd_queue_main; +atomic_int global_rt_cycles = 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; 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_cycle = 0; static void apply_command(command_t cmd) { 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; } @@ -216,6 +218,7 @@ void jack_shutdown_cb(void *arg) { * ---------------------------------------------------------------- */ int looper_init(jack_client_t *client) { queue_init(&cmd_queue); + queue_init(&cmd_queue_main); /* channel 0 */ channels[0].active = 1; atomic_store(&channels[0].state, STATE_IDLE); @@ -250,37 +253,46 @@ int looper_init(jack_client_t *client) { * main‑loop command processing * ---------------------------------------------------------------- */ void looper_process_commands(jack_client_t *client) { - /* Unregister any ports that were marked for deferred removal. - By now the real‑time thread has had at least one full cycle - to see the `active = 0` store. */ - if (pending_unregister_idx != -1) { - int idx = pending_unregister_idx; - if (channels[idx].audio_in) - jack_port_unregister(client, channels[idx].audio_in); - if (channels[idx].audio_out) - jack_port_unregister(client, channels[idx].audio_out); - pending_unregister_idx = -1; - } - - if (atomic_exchange(&cmd_add, 0)) { - int idx; - for (idx = 0; idx < MAX_CHANNELS; idx++) - if (!channels[idx].active) - break; - if (idx < MAX_CHANNELS) { - channel_add(client, idx); + /* Drain main‑loop command queue (add/remove) */ + command_t cmd; + while (queue_pop(&cmd_queue_main, &cmd)) { + switch (cmd.type) { + case CMD_ADD_CHANNEL: { + int idx; + for (idx = 0; idx < MAX_CHANNELS; idx++) + if (!channels[idx].active) + break; + if (idx < MAX_CHANNELS) + channel_add(client, idx); + break; + } + case CMD_REMOVE_CHANNEL: { + int remove_idx = -1; + for (int idx = 1; idx < MAX_CHANNELS; idx++) + if (channels[idx].active) + remove_idx = 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)) { - int remove_idx = -1; - for (int idx = 1; idx < MAX_CHANNELS; idx++) - if (channels[idx].active) - remove_idx = idx; - if (remove_idx != -1) { - /* Mark inactive now; ports will be unregistered next round */ - channel_remove(client, remove_idx); - pending_unregister_idx = remove_idx; + /* Deferred port unregistration – wait until RT thread has seen active=0 */ + if (pending_unregister_idx != -1) { + int current_cycle = atomic_load(&global_rt_cycles); + if (current_cycle - pending_unregister_cycle >= 1) { + int idx = pending_unregister_idx; + if (channels[idx].audio_in) + jack_port_unregister(client, channels[idx].audio_in); + if (channels[idx].audio_out) + jack_port_unregister(client, channels[idx].audio_out); + pending_unregister_idx = -1; } } } diff --git a/src/midi.c b/src/midi.c index eee1bb3..f1f3957 100644 --- a/src/midi.c +++ b/src/midi.c @@ -8,10 +8,9 @@ #include extern atomic_int control_key_active; -extern atomic_int cmd_add; -extern atomic_int cmd_remove; extern atomic_int bind_channel; 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)nframes; @@ -42,11 +41,15 @@ void midi_handle_events(void *port_buffer, jack_nframes_t nframes) { } else { switch (note) { 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: - 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: { 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); } break; 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: - 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: break; } diff --git a/src/pipe.c b/src/pipe.c index 8062659..b1b49e6 100644 --- a/src/pipe.c +++ b/src/pipe.c @@ -13,12 +13,9 @@ #define FIFO_PATH "/tmp/looper_cmd" #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; - -/* external atomic flags for add/remove (defined in looper.c) */ -extern atomic_int cmd_add; -extern atomic_int cmd_remove; +extern spsc_queue_t cmd_queue_main; static void *pipe_thread_func(void *arg) { (void)arg; @@ -35,9 +32,11 @@ static void *pipe_thread_func(void *arg) { line[len-1] = '\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) { - 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) { int ch = atoi(line + 7); command_t cmd = { .type = CMD_CYCLE, .channel = ch, .data = 0 };