import { PARAM_GROUPS, type ParamSpec, type WoodblockParams } from '../params'; interface Props { params: WoodblockParams; onChange: (key: keyof WoodblockParams, value: number | boolean) => void; } const SLIDER_STEPS = 1000; function sliderFromValue(spec: ParamSpec, v: number): number { if (spec.log) { const t = (Math.log(v) - Math.log(spec.min)) / (Math.log(spec.max) - Math.log(spec.min)); return Math.round(t * SLIDER_STEPS); } return Math.round(((v - spec.min) / (spec.max - spec.min)) * SLIDER_STEPS); } function valueFromSlider(spec: ParamSpec, s: number): number { const t = s / SLIDER_STEPS; let v: number; if (spec.log) { v = Math.exp(Math.log(spec.min) + t * (Math.log(spec.max) - Math.log(spec.min))); // keep three significant digits so the displayed number stays readable const mag = 10 ** Math.floor(Math.log10(Math.abs(v))); v = Math.round((v / mag) * 100) / 100 * mag; } else { v = spec.min + t * (spec.max - spec.min); if (spec.step) v = Math.round(v / spec.step) * spec.step; } if (spec.integer) v = Math.round(v); return Math.min(spec.max, Math.max(spec.min, v)); } function formatValue(v: number): string { if (v === 0) return '0'; const a = Math.abs(v); if (a >= 1e5 || a < 1e-3) return v.toExponential(2); return String(Math.round(v * 1e6) / 1e6); } function ParamRow({ spec, params, onChange }: Props & { spec: ParamSpec }) { const v = params[spec.key] as number; return (
{spec.label} {spec.unit ? ({spec.unit}) : null}
onChange(spec.key, valueFromSlider(spec, Number(e.target.value)))} /> { const nv = Number(e.target.value); if (Number.isFinite(nv)) onChange(spec.key, spec.integer ? Math.round(nv) : nv); }} />
); } export default function ParamPanel({ params, onChange }: Props) { return (
{PARAM_GROUPS.map((g, gi) => (
{g.name}
{g.specs.map((spec) => ( ))} {g.name === 'Output' && ( <>
Normalize peak
onChange('normalize', e.target.checked)} /> {params.normalize ? `scaled to ${params.peak_dbfs} dBFS` : `absolute: ${params.full_scale_pa} Pa = 0 dBFS`}
WAV bit depth
)}
))}
); }