4.5 KiB
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 per‑scene 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_countandcurrent_sceneareatomic_int; all reads/writes useatomic_load/atomic_store.- The per‑scene fields (
loop_count,record_pos,playback_pos,state,prev_state) are alsoatomic_int, so the RT callback and the main loop can safely read and write them concurrently. - The audio loop buffer itself (a plain
floatarray) is not atomic. During scene removal the buffer is copied viamemcpy. 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 inSTATE_IDLE). The integration testtest_scene_add_removedoes exactly this.
Implementation Details
-
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 toSTATE_IDLE. - Atomically increments
scene_count.
-
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_countand adjustscurrent_sceneif it would become out of bounds.
-
channel_next_scene/channel_prev_scene- Called from main loop.
- If
scene_count > 1, atomically increments/decrementscurrent_scene(wrapping using modulo).
-
RT callback (
process_callback)- At the start of each frame it reads
current_sceneatomically to obtain the scene index for that channel. - All per‑scene reads (state, loop_count, record_pos, playback_pos) use
atomic_load. - When the state changes, the callback atomically resets
record_pos,loop_count,playback_posas appropriate.
- At the start of each frame it reads
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.