/** * Helpers shared across mode TSX files. Pure functions; no Solid state. */ import type { Param } from './generated/types'; import type { SliderConfig } from '../primitives/SliderBank'; import type { CurveName } from '../output/curves'; /** * Convert a schema param list into SliderConfig entries that the SliderBank * primitive understands. Sliders are grouped by the schema's `group` field * so the bank renders collapsible sections. */ export function paramsToSliderConfig(params: ReadonlyArray): SliderConfig[] { let lastGroup: string | null = null; return params.map((p) => { const isNewGroup = p.group !== lastGroup; lastGroup = p.group; const cfg: SliderConfig = { id: p.name, label: p.label, min: p.min, max: p.max, curve: p.curve as CurveName, }; if (isNewGroup) cfg.section = formatGroupName(p.group); return cfg; }); } /** * Map a Float32Array (length = N) of normalized values [0,1] to a flat * array sized to match the slider config (already in min/max range). * * If `overrides` is supplied, per-param min/max overrides are honoured * instead of the schema defaults — this is what the mode UI sliders * display when the user has tightened the active range. */ export function outputsToSliderValues( outputs: Float32Array, params: ReadonlyArray, overrides?: Record, ): number[] { const out: number[] = []; for (let i = 0; i < params.length; ++i) { const v = outputs[i] ?? 0; const p = params[i]!; const ov = overrides?.[p.name]; if (ov && ov.muted && ov.fixedValue !== undefined) { out.push(ov.fixedValue); continue; } const min = ov?.min ?? p.min; const max = ov?.max ?? p.max; out.push(min + v * (max - min)); } return out; } function formatGroupName(group: string): string { if (!group) return ''; return group .split(/[_\s]+/) .map((p) => p.charAt(0).toUpperCase() + p.slice(1)) .join(' '); }