Files
looper/engine/docs/2-midi-looping.md
2026-05-13 17:57:41 +00:00

4.1 KiB
Raw Permalink Blame History

PerChannel 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 note66 Adds a new MIDI looping channel
add_midi FIFO pipe Same
CMD_REMOVE_CHANNEL MIDI note61 Removes the lastadded 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 channels 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 passthrough 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 (frame0). This is a simplification; no perevent timestamp scheduling is implemented. The loop length is determined by the total number of recorded events.

PassThrough

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 linebased 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 note66 on looper:control to create a MIDI channel.
  4. Send a CYCLE command (e.g., MIDI note62 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 (note65 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 oneRTcycle 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 audioonly functionality.

Run the test suite with:

make test