feat(visualizer): controls row + unified MixVisualizerControlState; 3 inert uniforms wired (P10 W2)
This commit is contained in:
@@ -44,6 +44,21 @@ export const MAX_VISIBLE_SECONDS = 30;
|
||||
/** Default opening window when a mix is first opened. Tunable. */
|
||||
export const DEFAULT_VISIBLE_SECONDS = 10;
|
||||
|
||||
// ── Wave 2 control tuning anchors. These mirror the C#-side defaults in ───────────
|
||||
// MixVisualizerControlState.cs — keep the two in sync, exactly as the
|
||||
// DEFAULT_VISIBLE_SECONDS / DefaultVisibleSeconds pair is kept in sync. All three are
|
||||
// normalized [0,1]. They are wired through to GPU uniforms now (Wave 2 plumbing) but
|
||||
// the parity shader does NOT consume them visually yet — they come alive in Wave 3.
|
||||
|
||||
/** Default bulge amount, normalized [0,1]. Mirrors C# DefaultBubblyness. */
|
||||
export const DEFAULT_BUBBLYNESS = 0.35;
|
||||
|
||||
/** Default lava-lamp detach amount, normalized [0,1]. Mirrors C# DefaultDetach. */
|
||||
export const DEFAULT_DETACH = 0;
|
||||
|
||||
/** Default gradient-morph rate, normalized [0,1]. Mirrors C# DefaultColorShiftSpeed. */
|
||||
export const DEFAULT_COLOR_SHIFT_SPEED = 0.3;
|
||||
|
||||
/**
|
||||
* Where the "now" line sits within the window, as a fraction from the top.
|
||||
* 0.5 = vertical centre (default): a short lead-in below, a short trail-out above.
|
||||
@@ -231,6 +246,12 @@ export interface MixVisualizerHandle {
|
||||
setDatum(samplesBase64: string, durationSeconds: number): void;
|
||||
setPlayback(positionSeconds: number, isPlaying: boolean): void;
|
||||
setZoom(visibleSeconds: number): void;
|
||||
/** Bulge amount [0,1]. Wave 2: sets the uniform; the parity shader does not consume it yet. */
|
||||
setBubblyness(value: number): void;
|
||||
/** Lava-lamp detach [0,1]. Wave 2: sets the uniform; the parity shader does not consume it yet. */
|
||||
setDetach(value: number): void;
|
||||
/** Gradient-morph rate [0,1]. Wave 2: sets the uniform; the parity shader does not consume it yet. */
|
||||
setColorShiftSpeed(value: number): void;
|
||||
/** Re-read the palette CSS vars off the canvas (call after a dark-mode toggle). */
|
||||
refreshTheme(): void;
|
||||
dispose(): void;
|
||||
@@ -316,6 +337,9 @@ uniform vec2 uResolution; // canvas size in device pixels
|
||||
uniform float uPlayheadSeconds; // current playback position (per-frame)
|
||||
uniform float uTimeSeconds; // monotonic clock (per-frame) — reserved for Wave 3 motion
|
||||
uniform float uVisibleSeconds; // zoom: window time-span (per change)
|
||||
uniform float uBubblyness; // bulge amount [0,1] (per change) — reserved for Wave 3, inert now
|
||||
uniform float uDetach; // lava-lamp detach [0,1] (per change) — reserved for Wave 3, inert now
|
||||
uniform float uColorShiftSpeed; // gradient-morph rate [0,1] (per change) — reserved for Wave 3, inert now
|
||||
uniform float uDurationSeconds; // mix length (per datum)
|
||||
uniform vec3 uColorAccent; // brightest stop, at the now line (per theme)
|
||||
uniform vec3 uColorEdge; // dim stop, at the window edges (per theme)
|
||||
@@ -456,6 +480,9 @@ function noopHandle(): MixVisualizerHandle {
|
||||
setDatum() {},
|
||||
setPlayback() {},
|
||||
setZoom() {},
|
||||
setBubblyness() {},
|
||||
setDetach() {},
|
||||
setColorShiftSpeed() {},
|
||||
refreshTheme() {},
|
||||
dispose() {},
|
||||
};
|
||||
@@ -504,14 +531,20 @@ export function create(canvas: HTMLCanvasElement): MixVisualizerHandle {
|
||||
// Cache uniform locations once. A null here for a uniform we actually upload
|
||||
// means either the name is misspelled or the GLSL compiler dead-stripped it
|
||||
// (it isn't reachable in the shader) — both of which silently break a uniform's
|
||||
// effect, so surface them. `uTimeSeconds` is reserved for Wave 3 and currently
|
||||
// unused by the fragment shader, so the compiler is free to strip it; we exempt
|
||||
// it from the warning to avoid a false alarm.
|
||||
// effect, so surface them. The Wave-3-reserved uniforms (`uTimeSeconds`,
|
||||
// `uBubblyness`, `uDetach`, `uColorShiftSpeed`) are declared and uploaded but not
|
||||
// yet consumed by the parity shader, so the compiler is free to dead-strip them;
|
||||
// we exempt them from the warning to avoid a false alarm. Their values still reach
|
||||
// the GPU when a location survives (verifiable in Wave 3).
|
||||
const RESERVED_UNUSED = new Set(['timeSeconds', 'bubblyness', 'detach', 'colorShiftSpeed']);
|
||||
const u = {
|
||||
resolution: gl.getUniformLocation(program, 'uResolution'),
|
||||
playheadSeconds: gl.getUniformLocation(program, 'uPlayheadSeconds'),
|
||||
timeSeconds: gl.getUniformLocation(program, 'uTimeSeconds'),
|
||||
visibleSeconds: gl.getUniformLocation(program, 'uVisibleSeconds'),
|
||||
bubblyness: gl.getUniformLocation(program, 'uBubblyness'),
|
||||
detach: gl.getUniformLocation(program, 'uDetach'),
|
||||
colorShiftSpeed: gl.getUniformLocation(program, 'uColorShiftSpeed'),
|
||||
durationSeconds: gl.getUniformLocation(program, 'uDurationSeconds'),
|
||||
colorAccent: gl.getUniformLocation(program, 'uColorAccent'),
|
||||
colorEdge: gl.getUniformLocation(program, 'uColorEdge'),
|
||||
@@ -521,7 +554,7 @@ export function create(canvas: HTMLCanvasElement): MixVisualizerHandle {
|
||||
datumSampleCount: gl.getUniformLocation(program, 'uDatumSampleCount'),
|
||||
};
|
||||
for (const [name, loc] of Object.entries(u)) {
|
||||
if (loc === null && name !== 'timeSeconds') {
|
||||
if (loc === null && !RESERVED_UNUSED.has(name)) {
|
||||
console.warn(`${TAG} uniform '${name}' resolved to null — it will have no effect (misspelled or dead-stripped from the shader).`);
|
||||
}
|
||||
}
|
||||
@@ -530,6 +563,11 @@ export function create(canvas: HTMLCanvasElement): MixVisualizerHandle {
|
||||
let datum: Datum | null = null;
|
||||
let playback: Playback = { positionSeconds: 0, isPlaying: false, pushWallClockMs: performance.now() };
|
||||
let visibleSeconds = DEFAULT_VISIBLE_SECONDS;
|
||||
// Wave 2 control values, fed through the handle. Uploaded as uniforms in draw() but inert in the
|
||||
// parity shader (Wave 3 consumes them). Seeded to the defaults that mirror MixVisualizerControlState.
|
||||
let bubblyness = DEFAULT_BUBBLYNESS;
|
||||
let detach = DEFAULT_DETACH;
|
||||
let colorShiftSpeed = DEFAULT_COLOR_SHIFT_SPEED;
|
||||
|
||||
/**
|
||||
* The *authoritative* playhead for this instant: the last pushed position advanced
|
||||
@@ -695,6 +733,12 @@ export function create(canvas: HTMLCanvasElement): MixVisualizerHandle {
|
||||
// Per-change / per-theme / per-datum uniforms (cheap to set every frame; no
|
||||
// separate dirty-tracking needed for scalars/vec3s).
|
||||
gl.uniform1f(u.visibleSeconds, visibleSeconds);
|
||||
// Wave 2 control uniforms. Uploaded every frame (cheap scalars); inert in the parity shader.
|
||||
// gl.uniform1f with a null location (dead-stripped uniform) is a documented silent no-op, so
|
||||
// these are safe to set unconditionally even before the Wave 3 shader references them.
|
||||
gl.uniform1f(u.bubblyness, bubblyness);
|
||||
gl.uniform1f(u.detach, detach);
|
||||
gl.uniform1f(u.colorShiftSpeed, colorShiftSpeed);
|
||||
gl.uniform3fv(u.colorAccent, theme.accent);
|
||||
gl.uniform3fv(u.colorEdge, theme.edge);
|
||||
|
||||
@@ -977,6 +1021,25 @@ export function create(canvas: HTMLCanvasElement): MixVisualizerHandle {
|
||||
if (idleRedraw) redrawOnce();
|
||||
},
|
||||
|
||||
// The three Wave 2 controls. Each clamps to [0,1], stores the value (uploaded as a uniform in
|
||||
// draw()), and forces one still frame while idle — mirroring setZoom — so the new value reaches
|
||||
// the GPU even when paused. INERT in Wave 2: the parity shader does not read these uniforms, so
|
||||
// a change does not visibly alter the render; the value is verifiable in Wave 3.
|
||||
setBubblyness(value: number): void {
|
||||
bubblyness = Math.min(1, Math.max(0, value));
|
||||
if (!playback.isPlaying) redrawOnce();
|
||||
},
|
||||
|
||||
setDetach(value: number): void {
|
||||
detach = Math.min(1, Math.max(0, value));
|
||||
if (!playback.isPlaying) redrawOnce();
|
||||
},
|
||||
|
||||
setColorShiftSpeed(value: number): void {
|
||||
colorShiftSpeed = Math.min(1, Math.max(0, value));
|
||||
if (!playback.isPlaying) redrawOnce();
|
||||
},
|
||||
|
||||
refreshTheme(): void {
|
||||
theme = readTheme();
|
||||
if (!playback.isPlaying) redrawOnce();
|
||||
|
||||
Reference in New Issue
Block a user