# Phase 5: Live Show Execution - Context **Gathered:** 2026-04-07 **Status:** Ready for planning ## Phase Boundary A complete show runs end-to-end: audio plays, animation blocks fire UDP commands to LED devices at correct timestamps, a live preview panel shows per-device active animations in the browser, and shows persist across sessions. Keyboard shortcuts make all primary operations mouse-free. No AI sync (Phase 6), no firmware (Phase 7). ## Implementation Decisions ### Cue Scheduler Architecture - **D-01:** Server-side scheduling — the server fires UDP commands, not the browser. On each WebSocket `tick`, the server scans the current show's cue list and dispatches `UDPSender.send()` for any block due to fire. - **D-02:** 60ms look-ahead window — if a block's `timestamp` is within 60ms ahead of the current tick position, fire it immediately. This prevents missed cues at 10Hz tick rate (max ~100ms tick gap). Blocks fire slightly early, never late. - **D-03:** Seek-safe reset — on `seek` events, the server resets the "already fired" cue pointer so no cues are double-fired or skipped after a seek. - **D-04:** The server needs the current show's cue list available per-connection. Wire this up by loading the active show from `show_store` in the WebSocket handler when a `load` message arrives (browser already sends this when audio loads). ### Live Preview Panel - **D-05:** Animation color + type per device — show the active animation type and its color as a simple strip per device. No per-LED frame simulation; just which animation is active and in what color. Turns dark when no block is active on a device. - **D-06:** Panel placement: below the timeline canvas, above the transport bar — always visible during playback. Layout: TIMELINE → LIVE PREVIEW → TRANSPORT BAR. Matches the Phase 4 inspector strip placement approach. - **D-07:** Preview updates are driven by the same WebSocket `tick` loop — when the server fires a cue, it also broadcasts a `preview_update` message so the browser canvas reflects the new state. ### Claude's Discretion - **Keyboard shortcuts (UI-03):** Standard DAW conventions: Space = play/pause, Left/Right arrow = seek ±5s, Ctrl+Z = undo, Ctrl+Shift+Z or Ctrl+Y = redo, Ctrl+S = explicit save. Planner decides exact key bindings but these are safe defaults. - **Show save/load UX:** All block mutations already auto-save via CRUD API (Phase 4). Add Ctrl+S as an explicit "save now" shortcut that calls the existing save endpoint. Show loading: use the existing show selector dropdown + a "load" button. No need to redesign. - **Preview panel height:** Compact — 40-60px total. One row per device, enough for a labeled color strip. Collapsible if many devices. ## Canonical References **Downstream agents MUST read these before planning or implementing.** ### Requirements - `.planning/REQUIREMENTS.md` — SHW-03 (live show execution), UI-03 (keyboard shortcuts), UI-04 (live preview panel) - `.planning/ROADMAP.md` §Phase 5 — Success criteria (5 items), plan breakdown (05-01 through 05-03) ### Existing WebSocket protocol - `lightsync/api/ws.py` — Tick/beat WebSocket handler; cue scheduler extends this handler. Already handles `tick`, `play`, `pause`, `seek`, `load` message types. ### UDP dispatch - `lightsync/protocol/animation_cmd.py` — `encode_animation_cmd(animation, params)` + `ANIMATION_IDS` registry - `lightsync/protocol/udp_sender.py` — `UDPSender.send(payload, ip, port)` — already wired in `main.py` ### Data models - `lightsync/models/show.py` — `ShowModel`, `TrackModel`, `CueModel` (fields: `id`, `timestamp`, `duration`, `animation`, `params`) - `lightsync/models/device.py` — `DeviceConfig` (fields: `id`, `name`, `strip_type`, `led_count`, `target_ip`, `target_port`) ### Show persistence - `lightsync/api/shows.py` — Existing save/load REST API (`GET/POST /api/shows`, `GET /api/shows/{id}`) - `lightsync/api/timeline.py` — Block CRUD endpoints (all mutations already auto-save via `show_store.save()`) ### Existing frontend - `lightsync/frontend/app.js` — WebSocket client, 10Hz tick loop, show/device state, existing event wiring - `lightsync/frontend/timeline/timeline.js` — `TimelineCanvas`, `this.tracks`, `this.showId` — preview panel reads same track data - `lightsync/frontend/style.css` — CSS variables; preview strip must match terminal aesthetic ### Prior context - `.planning/phases/01-foundation/01-CONTEXT.md` — Visual aesthetic decisions (flat, sharp, cyan/teal, no rounded corners, no shadows) - `.planning/phases/04-timeline-editor/04-CONTEXT.md` — D-01/D-03: inspector strip placement pattern; preview panel follows same layout slot logic ## Existing Code Insights ### Reusable Assets - `ws.py` `ConnectionManager.broadcast()` — already wired for multi-client broadcast; `preview_update` messages can use this - `UDPSender` (wired in `main.py`) — available as `app.state.udp_sender` or module-level ref; Phase 5 calls `send()` from the tick handler - `show_store.load(show_id)` — async, used in `shows.py` and `timeline.py`; ws.py can call this on `load` message - `lightsync/frontend/timeline/timeline.js` `this.tracks` — the live track data (including blocks) is already in the browser; preview can read `timeline.tracks` directly to know which blocks are active at `audio.currentTime` ### Established Patterns - WebSocket message protocol: `{"type": "...", ...}` — extend with `{"type": "preview_update", "device_id": "...", "animation": "chase", "color": [0, 255, 180]}` - Server-side per-connection state pattern already used in `ws.py` (`current_file`, `beats`, `last_beat_reported`) - CSS: `--accent` (cyan), `--bg` (near-black), `--border` (dimmed cyan); panel borders `1px solid var(--border)` - Panel strip pattern from Phase 4: `
` horizontal row — preview strip follows same structure ### Integration Points - `ws.py` websocket_endpoint function — add cue scheduler loop inside the `tick` branch; needs `show` loaded per-connection - `main.py` — `app.state.udp_sender` already initialized; access from ws.py via `websocket.app.state.udp_sender` - `index.html` layout — preview panel slot between `.timeline-container` and `.transport-bar` - `app.js` WebSocket `onmessage` handler — add `preview_update` case to update the preview canvas ## Specific Ideas - Terminal aesthetic carries forward: preview strips should look like terminal status bars — flat rectangles, color fill, device name label in monospace uppercase, no rounded corners. - The "lights dark" / "no active block" state should show the device label with a dim inactive indicator — consistent with the rest of the UI's use of `--text-dim`. ## Deferred Ideas - Per-LED frame simulation in the preview (see what the chase animation actually looks like moving) — v2 or Phase 6+ - Preview panel as a standalone full-screen mode — out of scope - Keyboard shortcut customization UI — out of scope for v1 --- *Phase: 05-live-show-execution* *Context gathered: 2026-04-07*