91 lines
4.1 KiB
Markdown
91 lines
4.1 KiB
Markdown
# 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<N>_midi_in` (input)
|
||
- `looper:channel<N>_midi_out` (output)
|
||
|
||
The `<N>` 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 <ch>` | Binds the next control note to channel `<ch>` |
|
||
| `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<N>_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
|
||
```
|