Files
looper/evaluation.md
Loic Coenen 4e489b5e40 docs: add MIDI looping documentation and update evaluation
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-10 11:54:00 +00:00

7.1 KiB
Raw Blame History

Code Evaluation

Summary Table

Category Rating Remarks
Mocked / Left Undone Complete All features are implemented: audio/MIDI looping, dynamic channels, bind/unbind, FIFO pipe, MIDI control with note 66 for MIDI channel creation, FIFO add_midi command. Integration tests cover MIDI channel creation, FIFO stop/bind/unbind, and all previously missing functionality. No placeholder code remains.
Potential Segfaults Good Every jack_port_get_buffer() call is nullchecked based on channel type. Array accesses bounded by channel_capacity. No useafterfree deferred cleanup ensures RT thread has finished with old resources. The only unprotected call is in midi_handle_events, but the caller has already verified the buffer.
Memory Safety Good Dynamic channel array allocated with calloc, freed exactly once after one RT cycle via deferred free. No leaks. Integration tests do not leak JACK clients or file descriptors. All other buffers are stackallocated or static.
Thread Safety / Race Good Three SPSC queues with correct atomic memory ordering (acquire/release). Shared state uses atomics. Deferred port/array cleanup uses global_rt_cycles with releaseacquire synchronisation. Channel type is written before active=1 (release), RT thread reads type only after confirming active==1 (acquire). No data races.
Performance Good RT callback has no syscalls, locks, or allocations. Linear perchannel processing. Main loop sleeps 50ms negligible overhead. Integration tests are slow (~25s total) due to fixed usleep() waits; this is acceptable for an integration suite.
Architectural Soundness Good Clean commanddriven design; persource input queues; RCUlike deferred cleanup; extensible. Integration tests are wellstructured (pertest looper process, real JACK connections, helpers). Missing test coverage has been addressed (MIDI channel creation, FIFO stop/bind/unbind).

Detailed Remarks

1. Mocked / Left Undone

  • Nothing remains.
    • CMD_ADD_MIDI_CHANNEL is triggered by MIDI note66 (under control key) and by FIFO command "add_midi".
    • CMD_STOP is sent from MIDI (note65 under control key) and from FIFO ("stop").
    • CMD_BIND_CHANNEL, CMD_UNBIND, CMD_CYCLE, CMD_ADD_CHANNEL, CMD_REMOVE_CHANNEL are all wired.
    • The integration test suite now includes test_fifo_stop_bind_unbind() and test_midi_channel_add().
    • The FIFO pipe reader handles "stop", "bind <ch>", "unbind", and "add_midi".

2. Potential Segfaults

  • Audio channels: audio_in/audio_out are checked for NULL before use.
  • MIDI channels: midi_in/midi_out are checked before use.
  • All jack_port_get_buffer() calls are inside guarded blocks.
  • Array indices are validated: cap = atomic_load(&channel_capacity); idx < cap.
  • The only unguarded call is in midi_handle_events, but its caller (process_callback) has already verified the port buffer pointer.

3. Memory Safety

  • The channel array is grown via calloc + memcpy + atomic exchange. The old pointer is freed only after at least one RT cycle has passed (pending_old_cycle vs global_rt_cycles).
  • No dynamic allocation occurs in the RT callback.
  • The FIFO pipe thread uses a stackallocated buffer (char line[LINE_MAX]).
  • No memory leaks: every calloc is eventually freed, and JACK ports are unregistered in deferred cleanup.

4. Thread Safety / Race Conditions

  • Three SPSC queues:
    • cmd_queue producer = RT callback, consumer = same RT (no race).
    • cmd_queue_main_midi producer = RT callback, consumer = main loop.
    • cmd_queue_main_fifo producer = FIFO thread, consumer = main loop.
  • All queues use correct memory_order_acquire/release for head/tail.
  • global_rt_cycles is incremented with memory_order_release at the end of every RT cycle.
  • Deferred port unregistration and array free both wait for current_cycle - pending_cycle >= 1, guaranteeing the RT thread has seen the change.
  • prev_state is a plain int but only accessed from the RT thread safe.
  • No data races detected.

5. Performance

  • RT callback per frame:
    1. MIDI event scan (may push to queues).
    2. Drain cmd_queue (usually 02 commands).
    3. Perchannel processing linear audio or MIDI event copy/playback.
    4. MIDI clock events (rare).
    5. Increment global_rt_cycles.
  • No syscalls, locks, or heap operations.
  • Main loop sleeps 50ms; draining two queues adds negligible overhead.

6. Architectural Soundness

  • Commanddriven design all state changes are explicit command_t structs.
  • Input source isolation each source (MIDI, FIFO) has its own queue for mainloop commands. RTsafe commands go to cmd_queue.
  • Deferred cleanup RCUlike pattern for port unregistration and array deallocation ensures no useafterfree.
  • Extensibility adding a new control input requires only a new SPSC queue, a producer thread, and a drain loop in looper_process_commands().
  • Integration tests cover all major control paths.

Overall Verdict

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

  • All intended features are implemented and tested.
  • No segfault or memory corruption is possible under normal operation.
  • Thread safety is correctly handled with atomic variables and deferred cleanup.
  • Performance is suitable for realtime audio.
  • The architecture is clean and extensible.