Files
looper/docs/4-implement-scene-switching-engine.md
Loic Coenen d4a811e552 docs: add scene switching engine documentation and update evaluation
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-10 19:42:34 +00:00

4.5 KiB
Raw Permalink Blame History

Scene Switching Engine

Overview

The scene switching engine allows a channel to have multiple independent recording/playback states (scenes).
Only one scene per channel is active at a time. The active scene's state (IDLE / RECORD / LOOPING / PAUSED) is controlled independently of other scenes.

Data Model

Each channel_t holds an array of up to MAX_SCENES (16) scene_t structures. Two atomic integers keep track of the number of scenes and which scene is currently active:

atomic_int    scene_count;      // number of scenes for this channel
atomic_int    current_scene;    // index of the active scene (0 ≤ current_scene < scene_count)

Each scene_t contains the loop buffer (audio or MIDI events) and the perscene atomic state:

union {
    float         audio_buffer[LOOP_BUF_SIZE];
    midi_event_t  midi_events[MAX_MIDI_EVENTS];
} loop;

atomic_int    loop_count;
atomic_int    record_pos;
atomic_int    playback_pos;
atomic_int    state;         // STATE_IDLE / STATE_RECORD / STATE_LOOPING / STATE_PAUSED
atomic_int    prev_state;    // previous state (used by RT callback to detect transitions)

Commands

Command Trigger (MIDI) Trigger (FIFO) Effect
CMD_NEXT_SCENE note 67 (control key) scene_next\n Increments current_scene (wraps around).
CMD_PREV_SCENE note 68 (control key) scene_prev\n Decrements current_scene (wraps around).
CMD_ADD_SCENE note 69 (control key) scene_add\n Appends a new empty scene, increments scene_count.
CMD_REMOVE_SCENE note 70 (control key) scene_remove\n Removes the current scene (shifts remaining scenes).

All scene commands are processed on the main loop (not in the RT callback). They are pushed to cmd_queue_main_midi (for MIDI) or cmd_queue_main_fifo (for FIFO) and applied by looper_process_commands().

Thread Safety

  • scene_count and current_scene are atomic_int; all reads/writes use atomic_load/atomic_store.
  • The perscene fields (loop_count, record_pos, playback_pos, state, prev_state) are also atomic_int, so the RT callback and the main loop can safely read and write them concurrently.
  • The audio loop buffer itself (a plain float array) is not atomic. During scene removal the buffer is copied via memcpy. If a scene is actively looping, this copy may produce a temporarily inconsistent buffer. Known limitation: scene removal should only be performed when the channel is idle (all scenes in STATE_IDLE). The integration test test_scene_add_remove does exactly this.

Implementation Details

  1. channel_add_scene

    • Called from main loop.
    • Checks scene_count < MAX_SCENES (atomically).
    • Calls init_scene() to zero the new scene and set its state to STATE_IDLE.
    • Atomically increments scene_count.
  2. channel_remove_scene

    • Called from main loop.
    • Refuses if scene_count <= 1 (at least one scene must always exist).
    • Shifts all scenes after the current one down one position each scene field is copied with atomic_store/atomic_load.
    • The audio buffer is copied with memcpy (see limitation above).
    • Decrements scene_count and adjusts current_scene if it would become out of bounds.
  3. channel_next_scene / channel_prev_scene

    • Called from main loop.
    • If scene_count > 1, atomically increments/decrements current_scene (wrapping using modulo).
  4. RT callback (process_callback)

    • At the start of each frame it reads current_scene atomically to obtain the scene index for that channel.
    • All perscene reads (state, loop_count, record_pos, playback_pos) use atomic_load.
    • When the state changes, the callback atomically resets record_pos, loop_count, playback_pos as appropriate.

Tests

  • test_scene_add_remove (FIFO) adds a scene, cycles next, removes the scene, exits.
  • test_scene_next_prev_midi sends control key + notes 67/68 to switch scenes.
  • test_scene_cycle_per_scene records a loop on scene 0, switches to scene 1, verifies scene 1 is idle.
  • test_scene_add_remove_midi sends control key + notes 69/70 to add/remove scenes.

All scene tests pass as part of make test.