# Per‑Channel MIDI Looping ## Overview Each looper channel can be either **audio** or **MIDI**. Audio channels record and loop audio samples (existing behaviour). MIDI channels record and loop MIDI event sequences, using separate JACK MIDI input/output ports. The state machine (`IDLE → RECORD → LOOPING → PAUSED`) operates identically for both types. ## Commands | Command | Source | Action | |----------------------------|-----------------|------------------------------------------------------------| | `CMD_ADD_MIDI_CHANNEL` | MIDI note 66 | Adds a new MIDI looping channel | | `add_midi` | FIFO pipe | Same | | `CMD_REMOVE_CHANNEL` | MIDI note 61 | Removes the last‑added channel (audio or MIDI) | | `CMD_CYCLE` | any note binding| Toggles channel state (IDLE→RECORD→LOOPING→PAUSED) | ## Ports When a MIDI channel is created, two JACK MIDI ports are registered: - `looper:channel_midi_in` (input) - `looper:channel_midi_out` (output) The `` is a global counter, independent of the index inside the internal channel array. ## Recording During `STATE_RECORD`: 1. All incoming MIDI events on the `_midi_in` port are stored in the channel’s event buffer, along with their frame offset relative to the start of the recording. 2. The incoming events are also **forwarded** to the `_midi_out` port, providing a direct pass‑through during recording. **Buffer limit:** A channel can hold up to `MAX_MIDI_EVENTS` (1024) events. ## Looping During `STATE_LOOPING`: - All recorded events are output at the **start** of every cycle (frame 0). This is a simplification; no per‑event timestamp scheduling is implemented. The loop length is determined by the total number of recorded events. ## Pass‑Through During `STATE_IDLE` (and `STATE_PAUSED` for MIDI) incoming MIDI events are **copied** from `_midi_in` to `_midi_out` unchanged. ## FIFO Pipe Commands The FIFO pipe at `/tmp/looper_cmd` accepts the following new line‑based commands: | Command | Effect | |---------------|--------------------------------------------| | `add_midi` | Adds a MIDI channel | | `stop` | Resets all channels to idle | | `bind ` | Binds the next control note to channel `` | | `unbind` | Resets binding to channel 0 | ## Example Workflow 1. Start the looper. 2. Connect a MIDI keyboard to `looper:channel1_midi_in`. 3. Send MIDI note 66 on `looper:control` to create a MIDI channel. 4. Send a CYCLE command (e.g., MIDI note 62 under control key) to start recording. 5. Play notes on the keyboard – the events are captured. 6. Send CYCLE again to enter LOOPING mode – the captured sequence repeats. 7. Send CYCLE again to pause, or send STOP (note 65 under control key) to reset. ## Implementation Details - **Channel structure** (`struct channel_t` in `channel.h`): - `type` field (`CHANNEL_AUDIO` or `CHANNEL_MIDI`) - `loop` union containing `audio_buffer[MAX_BUFFER]` or `midi_events[MAX_MIDI_EVENTS]` - **MIDI event type** (`midi_event_t`): - `timestamp` (frame offset relative to loop start) - `status`, `note`, `velocity` - **Processing** (`process_callback` in `looper.c`): - The callback checks `type` before routing to the appropriate handler block. - MIDI handler reads from `midi_in` port, writes to `midi_out` port. - **Port cleanup**: On channel removal, both MIDI ports are unregistered via `jack_port_unregister()` after a one‑RT‑cycle grace period. ## Testing Integration tests in `tests/integration.c` cover: - `test_midi_channel_add` – verifies that sending `add_midi` via FIFO creates `looper:channel_midi_in` ports. - `test_fifo_stop_bind_unbind` – verifies that `stop`, `bind`, and `unbind` FIFO commands are processed correctly. - Other existing tests continue to verify audio‑only functionality. Run the test suite with: ```bash make test ```