# 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: ```c 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: ```c 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 per‑scene 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 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_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`.