memlnaut-nisps/manifold/src/backends/mapping.ts
monkey-w1n5t0n 19b7f7eee8 feat(manifold): add convertible React front-end + simplify console chrome
Adds the Manifold app (manifold/) — the convertible-mode React front-end on
the real NISPS ML+audio engine, served live at meml.lnfinitemonkeys.org/next/.

Console chrome trimmed per UI pass:
- drop mode label + subtitle from the top-left overlay (keep MEMLNaut wordmark)
- remove the composite split preset/ratio readout (top-centre)
- remove the OUTPUT corner tag above the bars
- remove the A/B compare toggle from the verdict cluster
- remove the follow button + input/noise readout (bottom-left)
- remove AltitudeNav focus switcher (bottom-right)
2026-06-28 03:28:45 +02:00

37 lines
1.4 KiB
TypeScript

/**
* Universal per-output baseline mapping (backends-spec §3). Every backend shares
* ONE mapping from a normalised model output v ∈ [0,1] to a sink value, so
* behaviour is identical across sinks and the override UI is backend-agnostic.
*
* `applyCurve` clamps the input to [0,1] BEFORE the pow (matching the deployed
* `param-map.js:287` and the verification correction in backends-spec §"minor").
*/
import type { OutputMapping } from './backend';
export function clamp01(v: number): number {
return v < 0 ? 0 : v > 1 ? 1 : v;
}
/** 0.5 = linear; <0.5 ease-in, >0.5 ease-out. Input clamped to [0,1] first. */
export function applyCurve(v: number, c: number): number {
const x = clamp01(v);
if (c === 0.5) return x;
return Math.pow(x, Math.pow(2, 4 * (c - 0.5)));
}
/**
* Per-output baseline: curve, then scale into [min,max]. Honours freeze (fixed)
* but NOT mute (mute is a sink-level skip, handled per backend so the value is
* still computed/visible). Returns a value in [min,max].
*/
export function mapOutput(v: number, p: OutputMapping): number {
if (p.state === 'fixed') {
return p.min + clamp01(p.fixedValue) * (p.max - p.min);
}
return p.min + applyCurve(v, p.curve) * (p.max - p.min);
}
/** True when this output must NOT be emitted to the sink (off or muted). */
export function isSilent(p: OutputMapping): boolean {
return p.state === 'off' || p.muted;
}