38 lines
1.4 KiB
TypeScript
38 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;
|
||
|
|
}
|