/** The 5 parameters `?debugDynamicUI=1` can adjust live, session-only (plain React * state in App.tsx, no persistence). Every other constant this refactor introduces * lives in `lib/ambience.ts` / `components/Ambience.tsx` as a plain named constant - * that pair of files plus this one's `DEFAULT_TUNABLES` is the complete answer to * "what do I edit to make a debug-slider tweak permanent." */ export interface AmbienceTunables { /** Multiplies energy's swing on chroma/lightness. 1.0 = default. */ energyColorGain: number; /** Multiplies how much a beat crossing boosts current magnitude, on top of the * always-present tempo x drive-derived base. 0 = no effect. */ beatCurrentFactor: number; /** Multiplies the sampled `drive` curve before it modulates current magnitude (see * `currentMagnitudeAt`). 1.0 = default; 0 = current ignores drive entirely and sits * at `DRIVE_MAGNITUDE_FLOOR` x base magnitude regardless of the music. */ driveCurrentGain: number; /** How much bigger a bubble gets by the time it's risen a full screen height, e.g. * 0.4 = 40% larger at the top. */ bubbleGrowthRate: number; /** Seconds: exponential smoothing time constant for how quickly the curve-sampled * energy/valence/drive feeding colour and current track the underlying (already * linearly-interpolated) curve - a frontend preview knob, distinct from the * backend's fixed analysis-time sampling rate (`_ANALYSIS_HOP_SECONDS` in * `librosa_analyzer.py`, which needs a re-analysis to change). 0 = no extra * smoothing. */ curveSmoothingTau: number; } export const DEFAULT_TUNABLES: AmbienceTunables = { energyColorGain: 1.0, beatCurrentFactor: 0.6, driveCurrentGain: 1.0, bubbleGrowthRate: 0.4, curveSmoothingTau: 0.4, }; /** Drives the whole visual pipeline from these values instead of real playback data - * a "playground mode" for developing a feel for the effect without needing music * playing. Only takes effect while nothing is actually playing (`!state.playing`) - * real playback always wins the instant it starts, so there's never a fight between * the two. Session-only, same as `AmbienceTunables`; distinct from it conceptually: * tunables are *parameters* (how strongly the system reacts), this is *input* * (pretending to be the music). */ export interface ManualControl { enabled: boolean; energy: number; // 0..1 valence: number; // 0..1 tempo: number; // bpm - also stands in for the current's base magnitude and rise /** 0..1 - stands in for the sampled `drive` curve, since there's no real track to * derive rhythmic intensity from. 1.0 default = full current magnitude, matching * the flat-curve fallback's own default (see `effectiveCurve`). */ drive: number; /** Strength of each synthetic beat pulse, auto-generated at the `tempo` above while * manual mode is active - a continuous fake metronome rather than a one-shot * trigger button, so the beat-driven effects (spawn burst + current kick) can be * watched continuously rather than poked one click at a time. */ beatStrength: number; // 0..1 } export const DEFAULT_MANUAL_CONTROL: ManualControl = { enabled: false, energy: 0.5, valence: 0.5, tempo: 120, drive: 1.0, beatStrength: 0.8, };