Stream 9 (part 1/2) — shared infrastructure for the per-mode TSX layer.
- ModeShell.tsx: common chrome (header, voice-space PillToggle, audio
start/stop, control axes, training controls, drawer, status line).
Modes only have to author primary input + output JSX.
- mode-runtime.ts: useModeRuntime(schema) hook. Owns input pipeline,
WASM ML calls, output pipeline, and a 50ms-throttled engine-host
bridge. Exposes setInput/processedOutputs/training/audio surfaces
so mode TSX stays declarative.
- mode-helpers.ts: schema → SliderBank config + Float32Array →
slider-range value mapping. Pure functions.
- ModeSwitcher.tsx: top-of-page <select> wired to modeStore.
ModeShell and the runtime read controlStore (Boldness/Memory/Precision)
to derive learning rate + RL noise cap so axes affect training without
needing per-mode plumbing.
54 lines
1.5 KiB
TypeScript
54 lines
1.5 KiB
TypeScript
/**
|
|
* 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<Param>): 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).
|
|
*/
|
|
export function outputsToSliderValues(
|
|
outputs: Float32Array,
|
|
params: ReadonlyArray<Param>,
|
|
): number[] {
|
|
const out: number[] = [];
|
|
for (let i = 0; i < params.length; ++i) {
|
|
const v = outputs[i] ?? 0;
|
|
const p = params[i]!;
|
|
out.push(p.min + v * (p.max - p.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(' ');
|
|
}
|