Files
looper/evaluation.md
Loic Coenen 7b61384154 docs: update evaluation.md with current code analysis
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-09 23:55:07 +00:00

68 lines
7.2 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Evaluation
## Summary Table
| Category | Rating | Remarks |
|--------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Mocked / Left Undone | ✅ Minor | `CMD_STOP` is only reachable via the FIFO pipe (`"stop"`). The MIDI handler never sends it. This is an unused path, not a bug. The FIFO pipe is still untested in the integration suite all tests use MIDI. No test for `"stop"` via FIFO. Everything else is implemented and matches the test suite. |
| Potential Segfaults | ✅ Good | All `jack_port_get_buffer()` calls are guarded against NULL. Array bounds respected (fixedsize loops). SPSC queues use modulo arithmetic, no overrun possible. No dynamic memory in RT path. |
| Memory Safety | ✅ OK | No leaks, no useafterfree. All buffers static. Deferred port unregistration waits for at least one RT cycle after `active=0` safe. The FIFO reader thread uses stack memory for line reading. |
| Thread Safety / Race | ✅ Good | SPSC queues have correct `acquire`/`release` ordering. Multiproducer issue on mainloop commands has been fixed by giving each producer its own queue (`cmd_queue_main_midi` for MIDI, `cmd_queue_main_fifo` for FIFO). The RT callback only writes to `cmd_queue_main_midi`; the FIFO thread only writes to `cmd_queue_main_fifo`. Both are consumed solely by the main loop, restoring SPSC safety. The deferred unregistration race is now fixed via `global_rt_cycles` counter the main loop ensures the RT thread has completed a cycle after `active=0` before calling `jack_port_unregister()`. `prev_state` is a plain `int` but accessed only from the RT callback (single thread). All other shared state (`state`, `active`, `control_key_active`, `bind_channel`) uses atomics. |
| Performance | ✅ Good | No syscalls, locks, or dynamic allocations in the RT callback. Two queue drains (one for RT commands, one for mainloop commands) add negligible overhead. O(1) queue operations. Linear audio processing. |
| Architectural Soundness | ✅ Good | Clean separation: each input source has its own SPSC queue for nonRT commands; RT callback drains its own queue; main loop drains both auxiliary queues. The command queue approach is now fully uniform (no atomic flags remaining for add/remove). The FIFO pipe works in parallel. The code is easily extensible to new input sources. |
## Detailed Remarks
### 1. Mocked / Left Undone
- `CMD_STOP` is only reachable via the FIFO pipe (`"stop"`). The MIDI handler never sends it. This is not a bug, just an unused feature path.
- The FIFO pipe is completely untested in the main integration suite. All existing tests use MIDI notes. Adding a FIFObased test would increase coverage.
- No other functionality is missing.
### 2. Potential Segfaults
- Every `jack_port_get_buffer()` call is followed by a null check (`if (!out) continue;`).
- Array accesses are bounded by `MAX_CHANNELS` and `QUEUE_CAPACITY`.
- No use of `malloc` or variablelength arrays in the realtime callback.
- The only unguarded `jack_port_get_buffer()` is in `midi_handle_events` where the caller already checks `midi_ctrl_buf` against NULL. Safe.
### 3. Memory Safety
- All `loop_buffer` arrays and command queue buffers are static globals. No heap allocation in RT context.
- Port unregistration is deferred until after the RT thread has surely passed the `active=0` check (via `global_rt_cycles`). No useafterunregister possible.
- The FIFO reader thread uses a stackallocated line buffer safe.
- No memory leaks are present.
### 4. Thread Safety / Race Conditions
- **RTsafe commands queue (`cmd_queue`)** single writer (MIDI handler, called from RT callback) and single reader (the same callback, immediately after writing). Correct.
- **Add/remove command queues** two separate SPSC queues:
- `cmd_queue_main_midi`: written only by the RT callback (via `midi_handle_events`).
- `cmd_queue_main_fifo`: written only by the FIFO reader thread (nonRT).
Both are read only by the main loop (single consumer). No concurrent writes to the same queue.
- `global_rt_cycles` is incremented with `memory_order_release` at the end of every process callback. The main loop reads it with implicit acquire. This ensures visibility of the store to `active` and prevents unregistering ports while the RT thread may still be using them.
- `channel_add()` and `channel_remove()` are called only from the main loop, never from the RT callback. The RT callback reads `active`, `state`, `audio_in`, `audio_out` (all atomic). Safe.
- `prev_state` is a plain `int` but written and read only from the RT callback no data race.
### 5. Performance
- The RT callback performs:
1. MIDI event processing (may push to `cmd_queue` and `cmd_queue_main_midi`).
2. Drain `cmd_queue` (O(1) per command, usually 02 commands).
3. Perchannel audio processing (linear buffer copy or playback).
4. MIDI clock event handling (rare).
5. Increment `global_rt_cycles` (atomic store).
- No syscalls, no locks, no `printf` in the RT path.
- The main loop runs at 50ms intervals; draining two queues is negligible.
### 6. Architectural Soundness
- The design cleanly separates RTsafe command handling (immediate in the process callback) from nonRT operations (deferred to the main loop).
- Each input source (MIDI, FIFO) has its own dedicated SPSC queue for commands that must be processed outside the RT thread. This avoids the multiproducer race that existed before.
- All commands are represented by a uniform `command_t` structure with a typed enum. No adhoc atomic flags remain.
- The FIFO pipe reader runs in a detached thread simple and nonblocking.
- The code is easy to extend: adding a new input source (e.g., network socket) would involve creating a new SPSC queue and another drain loop.
## Overall Verdict
The code is **safe, racefree, and architecturally sound**. It meets all realtime constraints and correctly implements the loopers state machine with unified command handling from MIDI and a FIFO pipe.
**Minor remaining items:**
- The FIFO pipe is untested in the integration suite.
- `CMD_STOP` is not triggered from MIDI (only from FIFO).
- The existing `evaluation.md` is outdated and should be replaced with this evaluation.