docs: add arbitrary number of channels documentation and update evaluation
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Arbitrary Number of Channels
|
||||
|
||||
## Overview
|
||||
|
||||
Originally the looper had a fixed maximum of 16 channels (`MAX_CHANNELS = 16`).
|
||||
The limitation has been removed; channels are now stored in a **dynamically allocated array** that grows on demand.
|
||||
|
||||
## Implementation
|
||||
|
||||
- The global `channels` is a pointer (`struct channel_t *_Atomic channels`) instead of a fixed‑size array.
|
||||
- An atomic variable `channel_capacity` tracks the allocated size.
|
||||
- Initial allocation is for 8 channels; when a channel index >= current capacity is needed, the array is doubled.
|
||||
- The old array is **not freed immediately** – it is kept alive for at least one real‑time audio cycle (using the same deferred mechanism as port unregistration) to guarantee that the RT callback never accesses freed memory.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Role |
|
||||
|--------------------|-----------------------------------------------------------|
|
||||
| `src/channel.h` | Removes `MAX_CHANNELS`, adds `channels` pointer declaration and `get_channels_array()` inline accessor. |
|
||||
| `src/looper.c` | Contains `ensure_capacity()`, deferred free, and replaces all fixed‑size loop bounds with `channel_capacity`. |
|
||||
| `src/channel.c` | Adapted to use the current array pointer atomically. |
|
||||
| `src/midi.c` | Uses `atomic_load(&channel_capacity)` for bounds checks. |
|
||||
|
||||
## Thread Safety During Resize
|
||||
|
||||
1. A new, larger array is allocated (`calloc`).
|
||||
2. Existing channels are copied via `memcpy`.
|
||||
3. The global `channels` pointer is swapped with `atomic_exchange`.
|
||||
4. `channel_capacity` is updated.
|
||||
5. The old pointer is stored in `pending_old` along with the current cycle count (`pending_old_cycle`).
|
||||
6. In the main loop, `pending_old` is freed only after `global_rt_cycles` has advanced by at least 1, ensuring any RT callback that loaded the old pointer has finished.
|
||||
|
||||
This is a lightweight RCU‑like pattern that avoids locks and keeps the RT path deterministic.
|
||||
|
||||
## Compatibility
|
||||
|
||||
All existing MIDI commands and FIFO pipe commands work unchanged with the dynamic array.
|
||||
The maximum practical number of channels is limited only by available memory and JACK port limits (typically 1024 per client on modern systems).
|
||||
|
||||
Reference in New Issue
Block a user