diff --git a/docs/1-multichannel.md b/docs/1-multichannel.md index e69de29..a3d5503 100644 --- a/docs/1-multichannel.md +++ b/docs/1-multichannel.md @@ -0,0 +1,71 @@ +# Multi‑Channel & Bind Feature + +The looper supports up to 16 independent channels (numbered 0–15). +Channel 0 is always present and connected to the `looper:input` / `looper:output` audio ports. +Additional channels can be created and removed dynamically using MIDI commands. + +## MIDI Ports + +- **`looper:control`** – receives MIDI note‑on events for channel management and state toggling. +- **`looper:clock`** – receives MIDI clock messages (0xFA, 0xFC, 0xFB) that affect channel 0 only. + +## Control‑Key Modifier + +Hold the **control key** (MIDI note 64) pressed *before* sending another note to put the looper in “command mode”. +While control‑key is active, the next note‑on (with velocity > 0) performs a special action instead of its direct mapping. +The control key is released either by sending note‑off (note 64 or any note) or by sending a note‑on while control‑key is already active (the action is performed and control‑key is cleared). + +## Available Commands (under control key) + +| Note | Action | +|------|----------------------------------------------------------------------------------------------| +| 0–15 | **Bind** the next `control+62` toggle to the channel with that index. | +| 60 | **Add** a new dynamic channel (creates `channelX_input` / `channelX_output` ports). | +| 61 | **Remove** the highest‑numbered active channel (excluding channel 0). | +| 62 | **Toggle** the current bound channel through its state machine: | +| | IDLE → RECORD → LOOPING → PAUSED → LOOPING → … (each press advances one step). | + +> **Notes:** +> - The default bound channel is **0**. If you never send a bind command, `control+62` controls channel 0. +> - To bind a different channel, send `control + note <16>` (e.g., control + note 5 binds channel 5). +> - Bind is sticky – it stays until overwritten by another bind command. +> - There is **no unbind** command; you can rebind to channel 0 if needed. + +## Direct Mapping (without control key) + +For backward compatibility, the following notes work **without** the control‑key modifier: + +| Note | Action | +|------|----------------------------------------------------------------------------------------------| +| 1 | Toggle channel 0 state (IDLE→RECORD→LOOPING→PAUSED→LOOPING…). | +| 60 | Add a dynamic channel (same as `control+60`). | +| 61 | Remove the highest‑numbered active channel (same as `control+61`). | + +## Example Usage + +1. **Record a loop on channel 0 (using direct note 1)** + - Send note‑on, note 1, velocity 127 → channel 0 enters RECORD. + - Play some audio into `looper:input`. + - Send note‑on, note 1, velocity 127 again → channel 0 enters LOOPING. + - The recorded audio repeats indefinitely. + +2. **Use the control‑key to toggle channel 0** + - Send `note‑on, note 64` (control key). + - Then send `note‑on, note 62` → toggles channel 0 (IDLE→RECORD). + - Send `note‑on, note 64` again, then `note‑on, note 62` again → RECORD→LOOPING. + +3. **Add a new channel and bind it** + - Send `note‑on, note 64` + `note‑on, note 60` → creates channel 1. + - Send `note‑on, note 64` + `note‑on, note 1` → binds channel 1. + - Now `control+62` toggles channel 1 instead of channel 0. + - Record audio on channel 1 by sending `control+62` twice. + +4. **Remove a dynamic channel** + - Send `note‑on, note 64` + `note‑on, note 61` → removes the highest‑numbered active channel (e.g., channel 1). + +## Notes + +- The looper must be connected to a running JACK server. +- Channel buffers hold up to 5 seconds of audio at 48 kHz. +- After removal, the channel’s audio ports are unregistered on the next main‑loop cycle (deferred to avoid race conditions). +- The bind index is stored as an integer (0–15); values outside 0–15 are ignored (the note is processed as a command rather than a bind).