move engine to engine/
This commit is contained in:
90
engine/docs/2-midi-looping.md
Normal file
90
engine/docs/2-midi-looping.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user