# flow-state Ambient/EDM music video generator. Drop in a track, get a full-length, non-story, music-reactive video. No sourced footage — every frame is generated, and the whole look is derived from the audio. ```bash npm install npm run dev # http://localhost:5180 ``` Drop an audio file onto the page (mp3, flac, wav, ogg). Analysis takes a second or two, then the video is ready to preview and export. ## How it works The track is decoded and analysed **before the first frame renders**, into a table with one row per video frame: band energies, onset flux, spectral centroid and flatness, a phase-locked beat grid, section boundaries, and lookahead fields. Nothing reads a live `AnalyserNode`. Realtime preview maps `audio.currentTime` to a frame index; export counts frames. Both read the same rows, so **what you preview is what you export** — the exporter has no render path of its own. Analysing the whole track up front also buys the thing a causal analyser cannot do: a build can *anticipate* its drop and arrive at the transition already at full tension, instead of reacting once the drop has landed. ## Working with it | | | |---|---| | `space` | play / pause | | `←` `→` | previous / next section boundary | | `L` | loop the current section | | `D` | debug HUD | | `,` `.` | step one frame | **test render** exports 20 seconds around the playhead at full export quality. Use it before committing to a full render. **reroll** re-seeds the whole track; **reroll section** changes only the section under the playhead; **lock** protects a section from further rerolls. Every parameter the generator chose is exposed under the *scene* tab and can be edited live. The **click track** button (look tab) mixes an audible click onto the detected beat grid. If the clicks don't sit on the beat, tempo detection is wrong and everything downstream inherits it — check this first when a track looks off. ## Checks ```bash npm test # audio pipeline against synthetic ground truth npm run lint:scenes # determinism grep + scene schema/shader agreement ``` `http://localhost:5180/checks.html` runs the GPU gates for every phase. Add `?slow=1` for the full suite, `?phase=5` for one phase. ## Adding a scene Quick walkthrough: `HOWTO-visualizers.md`. A scene is a shader plus a params block. Everything else — uniform binding, UI controls, seeded per-track sampling, arc automation — is derived from the schema. ```js export const myScene = { name: 'My Scene', family: 'organic', // flow organic minimal structural geometric glitch kind: 'fragment', params: { density: { type: 'float', range: [0, 1], default: 0.5, uniform: 'u_density', bias: 'density' }, speed: { type: 'float', range: [0.1, 2], default: 0.5, uniform: 'u_speed', rate: true }, palette: { type: 'palette', count: 4 }, }, reactive: { density: { feature: 'bandLow', amount: 0.3 }, }, shader: ` vec4 scene(vec2 uv, vec2 p) { float t = u_time * u_speed + u_seed; return vec4(palRamp(fbm(p * 4.0 + t, 4)), 1.0); } `, }; ``` Register it in `src/scenes/registry.js`, then run `npm run lint:scenes` and the Phase 7 checks. Three rules the linter enforces, each of which has already caused a real bug here: - **Anything multiplying `u_time` must be `rate: true`.** Phase is `elapsed × rate`, so modulating a rate jumps the phase by `elapsed × delta` — a minute in, a small wobble throws the image several whole units between frames. It measured as strobing at twice the accessibility limit. - **Don't reuse a contract uniform name** (`u_width`, `u_time`, `u_seed`, …). It's a GLSL redefinition error, and the only symptom is a black frame. - **Use `pal()` / `palRamp()`**, not hardcoded colours, or the look generator can't recolour the scene. Scenes that composite over a background rather than being one declare `role: 'accent'`. ## Layout ``` src/ audio/ decode, STFT analysis, tempo, segmentation, FeatureTrack, click track engine/ Timeline, Renderer, Layer, Compositor, passes, seeded rng, flash safety look/ palette (OKLCH), LookGenerator, ArcDriver params/ declarative schema, validation, serialisation scenes/ the library — shader/ and layers3d/ export/ WebCodecs exporter ui/ preview surface checks/ phase gates, run from checks.html ``` `PLAN.md` has the full design and the reasoning behind each gate. Forked from `party-stage` by copying what was useful, then fully detached — there are no imports across the directory boundary in either direction.