Files
looper/evaluation.md
Loic Coenen 011d29cb09 docs: update evaluation.md with final code review
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-10 00:21:57 +00:00

7.8 KiB
Raw Permalink Blame History

Code Evaluation

Summary Table

Category Rating Remarks
Mocked / Left Undone Everything implemented CMD_STOP is now sent from MIDI (note65) and from FIFO ("stop"). FIFO pipe add/remove test is in the integration suite. All command types are wired to both sources. No missing paths.
Potential Segfaults Good Every jack_port_get_buffer() call is nullchecked. Array bounds respected (MAX_CHANNELS, QUEUE_CAPACITY). No malloc/free in RT path. The only unguarded jack_port_get_buffer() is in midi_handle_events where the caller already verified the buffer pointer safe.
Memory Safety OK All buffers static, no dynamic allocation. Deferred port unregistration waits for at least one RT cycle after active=0 (via global_rt_cycles), preventing useafterunregister. FIFO reader uses stackallocated line buffer. No leaks.
Thread Safety / Race Good Three SPSC queues, each with a single producer: cmd_queue (MIDI handler only), cmd_queue_main_midi (MIDI handler only), cmd_queue_main_fifo (FIFO thread only). All consumers are singlethreaded (RT callback or main loop). Atomic ordering correct (acquire/release). global_rt_cycles prevents RTthreadstillusingport race. All shared state (state, active, control_key_active, bind_channel) uses atomics. prev_state is a plain int but accessed only from the RT callback safe.
Performance Good No syscalls, locks, or allocations in RT callback. O(1) queue operations. Linear audio processing. The RT callback drains cmd_queue (usually 02 commands), processes perchannel audio, and handles MIDI clock events. The main loop runs every 50ms and drains two auxiliary queues negligible overhead.
Architectural Soundness Good Clean separation: each input source has its own SPSC queue for nonRT commands. RT callback performs only RTsafe operations; main loop handles channel add/remove. All commands use a uniform command_t enum. The code is easily extensible adding another input source (e.g., UDP socket) requires only a new SPSC queue and a drain loop.

Detailed Remarks

1. Mocked / Left Undone

  • Nothing remaining.
    • CMD_STOP is now sent by MIDI (note65, controlkey section) and recognised by FIFO ("stop").
    • FIFO pipe add/remove is tested in test_fifo_pipe().
    • All other command types (CYCLE, BIND, UNBIND, ADD_CHANNEL, REMOVE_CHANNEL) are available from both MIDI and FIFO.

2. Potential Segfaults

  • Every jack_port_get_buffer() is followed by a null check.
  • No array overruns: loops over MAX_CHANNELS (16) and QUEUE_CAPACITY (256).
  • No dynamic memory in RT context.
  • The only unchecked jack_port_get_buffer() is in midi_handle_events the caller already ensures midi_ctrl_buf is not NULL.

3. Memory Safety

  • All loop_buffer arrays and command queue buffers are static global arrays no heap allocation.
  • Port unregistration is deferred until global_rt_cycles has advanced by at least 1 after marking active=0. This guarantees the RT thread has started a new cycle after seeing active=0, so it will not dereference the port pointers after they are unregistered.
  • FIFO reader thread uses a stackallocated char line[256] safe.
  • No memory leaks exist.

4. Thread Safety / Race Conditions

  • Three SPSC queues, each with a single writer and single reader:
    • cmd_queue writer: midi_handle_events (called from RT callback), reader: same RT callback (immediately after writing).
    • cmd_queue_main_midi writer: RT callback (via midi_handle_events), reader: main loop.
    • cmd_queue_main_fifo writer: FIFO reader thread, reader: main loop.
  • All queue operations use correct memory_order_acquire/release no data races.
  • global_rt_cycles is incremented with memory_order_release at the end of every process callback. The main loop reads it with implicit acquire (via atomic_load). The condition current_cycle - pending_unregister_cycle >= 1 ensures the RT thread has finished a cycle after active=0 before port unregistration.
  • channel_add() and channel_remove() are called only from the main loop. The RT callback reads active, state, audio_in, audio_out all atomic. No concurrent modification.
  • prev_state is a plain int but only accessed from the RT callback safe.

5. Performance

  • The RT callback performs in order:
    1. MIDI event processing (may push to cmd_queue and cmd_queue_main_midi).
    2. Drain cmd_queue (usually empty or 1 command).
    3. Perchannel audio processing (linear buffer copy or playback, no conditionals for common state).
    4. MIDI clock events (rare).
    5. Increment global_rt_cycles.
  • No syscalls, no locks, no printf in the RT path.
  • The main loop sleeps 50ms between iterations; draining two queues adds negligible overhead.

6. Architectural Soundness

  • The design is clean and consistent:
    • All commands flow through a command_t struct.
    • Each input source has its own SPSC queue for commands that must be processed outside the RT thread (e.g., add/remove).
    • The RT callback handles only RTsafe state transitions (cycle, stop, bind, unbind).
    • The main loop handles add/remove and deferred port unregistration.
  • The FIFO pipe reader runs in a detached thread simple and nonblocking.
  • Adding a new input source (e.g., a network socket) would require:
    • Creating a new SPSC queue.
    • A producer thread that pushes commands to the appropriate queue.
    • Adding a drain loop in looper_process_commands().

Overall Verdict

The code is complete, racefree, memorysafe, and architecturally sound.

  • No missing features.
  • No segfaults or useafterfree.
  • All input sources (MIDI, FIFO) can send any command.
  • The unified commandqueue architecture is fully realised.

The only minor observation is that the test suite does not verify the MIDI CMD_STOP (note65) but that would be trivial to add.

Final note: The evaluation file itself (evaluation.md) should be updated to remove the “FIFO untested” and “CMD_STOP not triggered” remarks. The content above can replace it.