4.3 KiB
Command Architecture
Overview
The looper uses a lock‑free, single‑producer single‑consumer (SPSC) command queue to communicate between the real‑time JACK audio thread and the main (non‑RT) thread.
There are two families of queues:
-
cmd_queue(RT‑safe) – used for commands that can be handled directly inside the process callback (CMD_CYCLE,CMD_STOP,CMD_BIND_CHANNEL,CMD_UNBIND).
The producer is the MIDI handler (midi_handle_events) or the FIFO pipe reader (pipe_thread_func); the consumer isprocess_callback. -
cmd_queue_main_midi/cmd_queue_main_fifo– used for commands that require memory allocation or JACK API calls (CMD_ADD_CHANNEL,CMD_REMOVE_CHANNEL).
The producer is the MIDI handler (or FIFO reader), and the consumer islooper_process_commands, which runs in the main loop approximately every 50 ms.
Command Types
The command_t struct (defined in command.h) contains:
type– one of thecmd_type_tenumerators.channel– target channel index;-1means “current bind channel” for some commands.data– extra parameter (e.g., bind channel number forCMD_BIND_CHANNEL).
RT‑safe Commands (pushed to cmd_queue)
| Type | Effect |
|---|---|
CMD_CYCLE |
Toggle the state machine of the target channel (IDLE→RECORD→LOOPING→PAUSED→LOOPING…). |
CMD_STOP |
Force the target channel (or all channels, if channel == -1) to STATE_IDLE. |
CMD_BIND_CHANNEL |
Set the global bind_channel index to data. |
CMD_UNBIND |
Reset bind_channel to 0. |
Main‑thread Commands (pushed to cmd_queue_main_midi / cmd_queue_main_fifo)
| Type | Effect |
|---|---|
CMD_ADD_CHANNEL |
Create a new dynamic channel (port registration). |
CMD_REMOVE_CHANNEL |
Remove the highest‑numbered active dynamic channel (excluding channel 0). |
Command Flow
-
MIDI input –
midi_handle_eventsparses incoming note‑on events and decides which command to push.
RT‑safe commands are pushed tocmd_queue; add/remove commands are pushed tocmd_queue_main_midi. -
FIFO input –
pipe_thread_funcreads lines from/tmp/looper_cmdand pushes the corresponding command.
RT‑safe commands go tocmd_queue; add/remove go tocmd_queue_main_fifo. -
Process callback –
process_callbackis invoked by JACK for each audio cycle. It drainscmd_queueand applies each command viaapply_command. This function modifies the channel state and bind index atomically. -
Main loop –
looper_process_commandsis called in the main loop (≈ every 50 ms). It drainscmd_queue_main_midiandcmd_queue_main_fifo, performing the necessary port registrations/unregistrations and callingchannel_add/channel_remove.
Deferred Port Unregistration
When a dynamic channel is removed, the RT thread first sets active = 0. The main thread waits until it has seen at least one full RT cycle pass (using global_rt_cycles) before calling jack_port_unregister. This prevents a race between the RT thread still holding a reference to the port buffer and the port being unregistered.
SPSC Queue Implementation
The queue itself (defined in queue.c/queue.h) is a simple circular buffer with head and tail indices. It uses C11 atomic loads/stores with appropriate memory ordering (memory_order_acquire/memory_order_release) to guarantee visibility without locks. Capacity is fixed at QUEUE_CAPACITY (256 commands). Push/pop operations are O(1) and never block.
Thread Safety
- The JACK process callback runs in an RT thread.
- The MIDI handler runs inside the process callback (it is called from
process_callback). - The FIFO reader lives in a separate POSIX thread.
- The main thread runs the rest of the program.
The two‑queue design ensures that memory‑allocating operations never happen inside the RT thread, while RT‑pertinent commands are processed with minimal latency.