/** * The available models: their MATLAB source, and the metadata the host owns. * * A model's *algorithm* lives in its .m file. Everything around it lives here: * the parameter names the .m may take as arguments, their defaults and slider * ranges, the state fields it advances, and which of them is the pressure the * app draws. The .m declares nothing about these — it just names the * parameters it wants, and `CompiledModel` matches each against this table. */ import leapfrogSource from '../../models/leapfrog.m?raw'; import leapfrog4Source from '../../models/leapfrog4.m?raw'; import { C_AIR } from '../units.ts'; export type Params = Record; /** A tunable scalar the .m may take as an argument. */ export interface ParamSpec { key: string; label: string; value: number; min: number; max: number; step: number; /** This parameter is a random seed: its value picks a draw and means * nothing on its own, so the UI offers a button that jumps to another one * rather than a slider. */ reseed?: boolean; /** Shown as a tooltip. */ hint?: string; } export interface MModel { key: string; label: string; blurb: string; /** State field names the .m advances, in the order its functions return * them. The first is the pressure, which is what gets drawn. */ state: string[]; params: ParamSpec[]; /** Order of the spatial stencil the .m uses — what the stable timestep * follows from. */ order: 2 | 4; /** MATLAB source — the algorithm itself. */ source: string; } /** * The source parameters, shared by every model here: both differ only in * their Laplacian. */ const sourceParams: ParamSpec[] = [ { key: 'f', label: 'frequency (Hz)', value: 300, min: 50, max: 2000, step: 10, hint: `Cycles per second. At the background speed of ${C_AIR} m/s, this is ` + 'a real audible pitch, and the wavelength (speed / frequency) is what ' + 'the grid has to resolve — the app warns when it does not.', }, { key: 'tw', label: 'pulse width (s)', value: 0.01, min: 0.001, max: 0.05, step: 0.001, hint: 'Duration of the Gaussian envelope, in seconds. Wide means many cycles and a narrow band; narrow means a click.', }, { key: 'cw', label: 'continuous', value: 0, min: 0, max: 1, step: 0.05, hint: '0 is a single pulse, 1 a wave that turns on smoothly and stays on. In between is both.', }, { key: 'point', label: 'point source', value: 0, min: 0, max: 1, step: 0.05, hint: '0 is a line source spanning the grid, whose far field is a plane wave; 1 is a point at (source x, source y).', }, { key: 'x0', label: 'source x (m)', value: -3, min: -4.5, max: 4.5, step: 0.1, }, { key: 'y0', label: 'source y (m)', value: 0, min: -4.5, max: 4.5, step: 0.1, hint: 'Only meaningful for a point source; a line source spans y.', }, { key: 'w', label: 'source width (m)', value: 0.05, min: 0.01, max: 0.3, step: 0.01, hint: 'Physical size of the source. Much smaller than a wavelength and the grid cannot resolve it.', }, { key: 't0', label: 'start time (s)', value: 0.05, min: 0.005, max: 0.2, step: 0.005, hint: 'When the pulse is emitted, in seconds. Needs a few pulse widths of head room, or it starts already part-way up its envelope.', }, ]; const leapfrog: MModel = { key: 'leapfrog', label: 'Leapfrog, 5-point Laplacian', blurb: 'Second order in space and time. The plain scheme.', state: ['p', 'pm', 't'], params: sourceParams, order: 2, source: leapfrogSource, }; const leapfrog4: MModel = { key: 'leapfrog4', label: 'Leapfrog, 9-point Laplacian', blurb: 'Fourth order in space: much less grid dispersion at the same resolution.', state: ['p', 'pm', 't'], params: sourceParams, order: 4, source: leapfrog4Source, }; export const mModels: MModel[] = [leapfrog, leapfrog4]; export const mModelByKey = (key: string): MModel | undefined => mModels.find((m) => m.key === key); export const defaultParams = (m: { params: ParamSpec[] }): Params => Object.fromEntries(m.params.map((p) => [p.key, p.value]));