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

78 lines
7.8 KiB
Markdown
Raw Permalink 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 | ✅ 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.