import { timeStep } from '../sim/solver'; import { oversampling } from '../sim/antialias'; import type { SimOptions } from '../sim/types'; import type { Built, InstrumentConfig, SimParams } from './instrumentPage'; // Settings in a shared link (#/?=&…&preset=&gpu=<0|1>), // read by the instrument page and by the embedded widget. export interface Settings

{ params: P; preset: number; gpu?: boolean; } // Settings p as a query string: every parameter, the preset they started from // (for its description) and the WebGPU toggle (CPU and WebGPU results are cached separately). export function settingsQuery

(p: P, preset: number, gpu: boolean): string { const qs = new URLSearchParams(); for (const [k, v] of Object.entries(p)) qs.set(k, String(v)); qs.set('preset', String(preset)); qs.set('gpu', gpu ? '1' : '0'); return qs.toString(); } // Parameters missing from the query (or invalid) keep the values of its preset; // unknown ones are ignored, so old links still open. export function parseSettings

(cfg: InstrumentConfig

, query: string): Settings

{ const qs = new URLSearchParams(query); const n = Number(qs.get('preset')); const preset = Number.isInteger(n) && n >= 0 && n < cfg.presets.length ? n : 0; const base = cfg.presets[preset].params; const params = { ...base } as Record; const options = new Map(); for (const sec of cfg.sections) for (const s of sec.params) if (s.options) options.set(s.key, s.options.map(([v]) => v)); for (const [k, v0] of Object.entries(base)) { const v = qs.get(k); if (v === null) continue; if (typeof v0 === 'number') { const x = Number(v); if (v.trim() !== '' && Number.isFinite(x) && (!options.has(k) || options.get(k)!.includes(String(x)))) params[k] = x; } else if (typeof v0 === 'string' && (!options.has(k) || options.get(k)!.includes(v))) params[k] = v; } const gpu = qs.get('gpu'); return { params: params as P, preset, gpu: gpu === null ? undefined : gpu === '1' }; } // The solver options for parameters p of a built instrument. export function simOptions(p: SimParams, built: Built): SimOptions { return { duration: p.duration, fs: p.fs, cfl: p.cfl, frameRate: p.frameRate, pickup: built.pickup, pickupDelay: built.pickupDelay }; } // Size of a run: time step (the solver runs at R times fs, see antialias.ts), // steps per output sample and element updates (a bending hinge costs about four // springs). A desktop CPU does roughly CPU_RATE updates per second. export const CPU_RATE = 2.2e7; export function runCost(p: SimParams, built: Built) { const opt = simOptions(p, built); const R = oversampling(built.sys, built.exc, opt); const { omegaMax, stepsPerSample: inner } = timeStep(built.sys, built.exc.mallet, p.fs * R, p.cfl); const stepsPerSample = inner * R; const elements = built.sys.si.length + 4 * (built.sys.bending?.w0.length ?? 0); return { omegaMax, stepsPerSample, oversampling: R, work: stepsPerSample * p.fs * p.duration * elements }; }