/** * Shared driver for the voice reaction. * * Every registered instance is stepped from a SINGLE requestAnimationFrame * loop (capped at ~60 fps so 120 Hz displays do not double the paint work). * Each frame the driver reads the instance's source — an AnalyserNode on a * MediaStream, or a plain level getter — shapes the raw level (gain, noise * gate, soft saturation), follows it with an attack/release envelope, * advances the flow (the lobes sliding sideways at a speed set by the * level, so the spectrum travels while a voice is heard and rests when it * stops), carries the gathered beam across the range while processing, * folds in the idle breathing and the slow hue drift, and writes * the result as * CSS custom properties on the element. The generated stylesheet multiplies * every lobe size and position by those, so the paint work is the * browser's and this loop stays a few arithmetic ops per instance. */ export interface VoiceDriverConfig { id: string; sensitivity: number; threshold: number; attack: number; release: number; idle: number; breatheDuration: number; reach: number; spread: number; bands: boolean; /** Flow speed in px/s at full level; negative flows right-to-left. */ flow: number; /** How much each part of the glow moves on its own while a voice is heard, 0–1 (organic.ts). */ organic: number; /** Multiplies the resting distance between lobes (and so the flow ring). */ lobeSpacing: number; /** Bend: px of extra height the glow's top gains at the centre at full level. */ bend: number; /** The band: opacity, thickness, height as a fraction of the ceiling, * bell exponent, bell width, skew, chromatic split. */ bandStrength: number; bandWidth: number; bandPosition: number; bandCurve: number; bandSpread: number; bandSkew: number; /** Vertical shift of the whole band line, px (negative lowers it). */ bandOffset: number; /** Rise of the band's ends toward the corners, as a fraction of the peak. */ bandTail: number; /** Where the rise starts, as a share of the centre-to-edge distance. */ bandTailPosition: number; /** Exponent of the rise. */ bandTailCurve: number; /** Px the band runs past each side of the element. */ bandTailOverflow: number; bandAberration: number; /** The ceiling's tuned size multipliers, so the band can sit on it. */ rangeWidth: number; rangeHeight: number; theme: 'dark' | 'light'; /** Band colours as `r, g, b` triples: the core and its fringes. */ bandColors: { core: string; above: string; mid: string; below: string; }; /** Horizontal distortion of the glow under the band, 0–1. */ distortion: number; /** The epicentre wash's opacity; above 0 the band-line clips are written even without distortion. */ coreLight: number; /** The effect's overall scale, for the few px values not carried by the geometry props. */ scale: number; /** The element's corner radius, px — the travelling beam follows its arc while processing. */ radius: number; /** Processing state: the beam gathers and travels the range, ping-pong. */ processing: boolean; /** Seconds for one pass of the beam (left to right, or back). */ processingDuration: number; /** Seconds the morph between the voice glow and the travelling beam takes. */ processingEase: number; /** How far the beam travels to each side, × half the lobe ring (1 = the resting spread). */ processingTravel: number; /** How the sweep eases into each turn: 1 constant speed, 2 smooth, higher dwells at the ends. */ processingCurve: number; /** How much the coloured glow rides the corner arcs, 0–1 (the band line always does). */ cornerFollow: number; /** How lit the glow is held while processing, 0–1. */ processingLevel: number; hueRange: number; hueDuration: number; staticColors: boolean; reducedMotion: boolean; /** Hold the frame: the clock stops and the source is not read, but a changed config still repaints. */ paused: boolean; } export interface VoiceSource { /** Audio to analyse; wins over `getLevel`. */ stream?: MediaStream | null; /** Manual 0–1 level, sampled every frame. */ getLevel?: () => number; } /** * Register an element to be driven by the shared voice loop. * * @returns a cleanup function that unregisters the instance, releases its * analyser, and stops the shared loop once no instances remain. */ export declare function registerVoiceInstance(el: HTMLElement, config: VoiceDriverConfig, source: VoiceSource, onLevel?: (level: number) => void): () => void;