1import { timeStep } from '../sim/solver';
2import type { SimOptions } from '../sim/types';
3import type { Built, InstrumentConfig, SimParams } from './instrumentPage';
5// Settings in a shared link (#/<instrument>?<param>=<value>&…&preset=<i>&gpu=<0|1>),
6// read by the instrument page and by the embedded widget.
8export interface Settings<P> {
9 params: P;
10 preset: number;
11 gpu?: boolean;
12}
14// Settings p as a query string: every parameter, the preset they started from
15// (for its description) and the WebGPU toggle (CPU and WebGPU results are cached separately).
16export function settingsQuery<P extends SimParams>(p: P, preset: number, gpu: boolean): string {
17 const qs = new URLSearchParams();
18 for (const [k, v] of Object.entries(p)) qs.set(k, String(v));
19 qs.set('preset', String(preset));
20 qs.set('gpu', gpu ? '1' : '0');
21 return qs.toString();
22}
24// Parameters missing from the query (or invalid) keep the values of its preset;
25// unknown ones are ignored, so old links still open.
26export function parseSettings<P extends SimParams>(cfg: InstrumentConfig<P>, query: string): Settings<P> {
27 const qs = new URLSearchParams(query);
28 const n = Number(qs.get('preset'));
29 const preset = Number.isInteger(n) && n >= 0 && n < cfg.presets.length ? n : 0;
30 const base = cfg.presets[preset].params;
31 const params = { ...base } as Record<string, unknown>;
32 const options = new Map<string, string[]>();
33 for (const sec of cfg.sections) for (const s of sec.params) if (s.options) options.set(s.key, s.options.map(([v]) => v));
34 for (const [k, v0] of Object.entries(base)) {
35 const v = qs.get(k);
36 if (v === null) continue;
37 if (typeof v0 === 'number') {
38 const x = Number(v);
39 if (v.trim() !== '' && Number.isFinite(x) && (!options.has(k) || options.get(k)!.includes(String(x)))) params[k] = x;
40 } else if (typeof v0 === 'string' && (!options.has(k) || options.get(k)!.includes(v))) params[k] = v;
41 }
42 const gpu = qs.get('gpu');
43 return { params: params as P, preset, gpu: gpu === null ? undefined : gpu === '1' };
44}
46// The solver options for parameters p of a built instrument.
47export function simOptions(p: SimParams, built: Built): SimOptions {
48 return { duration: p.duration, fs: p.fs, cfl: p.cfl, frameRate: p.frameRate, pickup: built.pickup, pickupDelay: built.pickupDelay };
49}
51// Size of a run: time step and element updates (a bending hinge costs about four
52// springs). A desktop CPU does roughly CPU_RATE updates per second.
53export const CPU_RATE = 2.2e7;
54export function runCost(p: SimParams, built: Built) {
55 const { omegaMax, stepsPerSample } = timeStep(built.sys, built.exc.mallet, p.fs, p.cfl);
56 const elements = built.sys.si.length + 4 * (built.sys.bending?.w0.length ?? 0);
57 return { omegaMax, stepsPerSample, work: stepsPerSample * p.fs * p.duration * elements };
58}