1/**
2 * The available models: their MATLAB source, and the metadata the host owns.
3 *
4 * A model's *algorithm* lives in its .m file. Everything around it lives here:
5 * the parameter names the .m may take as arguments, their defaults and slider
6 * ranges, the state fields it advances, and which of them is the pressure the
7 * app draws. The .m declares nothing about these — it just names the
8 * parameters it wants, and `CompiledModel` matches each against this table.
9 */
10import leapfrogSource from '../../models/leapfrog.m?raw';
11import leapfrog4Source from '../../models/leapfrog4.m?raw';
12import { C_AIR } from '../units.ts';
14export type Params = Record<string, number>;
16/** A tunable scalar the .m may take as an argument. */
17export interface ParamSpec {
18 key: string;
19 label: string;
20 value: number;
21 min: number;
22 max: number;
23 step: number;
24 /** This parameter is a random seed: its value picks a draw and means
25 * nothing on its own, so the UI offers a button that jumps to another one
26 * rather than a slider. */
27 reseed?: boolean;
28 /** Shown as a tooltip. */
29 hint?: string;
30}
32export interface MModel {
33 key: string;
34 label: string;
35 blurb: string;
36 /** State field names the .m advances, in the order its functions return
37 * them. The first is the pressure, which is what gets drawn. */
38 state: string[];
39 params: ParamSpec[];
40 /** Order of the spatial stencil the .m uses — what the stable timestep
41 * follows from. */
42 order: 2 | 4;
43 /** MATLAB source — the algorithm itself. */
44 source: string;
45}
47/**
48 * The source parameters, shared by every model here: both differ only in
49 * their Laplacian.
50 */
51const sourceParams: ParamSpec[] = [
52 {
53 key: 'f',
54 label: 'frequency (Hz)',
55 value: 300,
56 min: 50,
57 max: 2000,
58 step: 10,
59 hint: `Cycles per second. At the background speed of ${C_AIR} m/s, this is ` +
60 'a real audible pitch, and the wavelength (speed / frequency) is what ' +
61 'the grid has to resolve — the app warns when it does not.',
62 },
63 {
64 key: 'tw',
65 label: 'pulse width (s)',
66 value: 0.01,
67 min: 0.001,
68 max: 0.05,
69 step: 0.001,
70 hint: 'Duration of the Gaussian envelope, in seconds. Wide means many cycles and a narrow band; narrow means a click.',
71 },
72 {
73 key: 'cw',
74 label: 'continuous',
75 value: 0,
76 min: 0,
77 max: 1,
78 step: 0.05,
79 hint: '0 is a single pulse, 1 a wave that turns on smoothly and stays on. In between is both.',
80 },
81 {
82 key: 'point',
83 label: 'point source',
84 value: 0,
85 min: 0,
86 max: 1,
87 step: 0.05,
88 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).',
89 },
90 {
91 key: 'x0',
92 label: 'source x (m)',
93 value: -3,
94 min: -4.5,
95 max: 4.5,
96 step: 0.1,
97 },
98 {
99 key: 'y0',
100 label: 'source y (m)',
101 value: 0,
102 min: -4.5,
103 max: 4.5,
104 step: 0.1,
105 hint: 'Only meaningful for a point source; a line source spans y.',
106 },
107 {
108 key: 'w',
109 label: 'source width (m)',
110 value: 0.05,
111 min: 0.01,
112 max: 0.3,
113 step: 0.01,
114 hint: 'Physical size of the source. Much smaller than a wavelength and the grid cannot resolve it.',
115 },
116 {
117 key: 't0',
118 label: 'start time (s)',
119 value: 0.05,
120 min: 0.005,
121 max: 0.2,
122 step: 0.005,
123 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.',
124 },
125];
127const leapfrog: MModel = {
128 key: 'leapfrog',
129 label: 'Leapfrog, 5-point Laplacian',
130 blurb: 'Second order in space and time. The plain scheme.',
131 state: ['p', 'pm', 't'],
132 params: sourceParams,
133 order: 2,
134 source: leapfrogSource,
135};
137const leapfrog4: MModel = {
138 key: 'leapfrog4',
139 label: 'Leapfrog, 9-point Laplacian',
140 blurb: 'Fourth order in space: much less grid dispersion at the same resolution.',
141 state: ['p', 'pm', 't'],
142 params: sourceParams,
143 order: 4,
144 source: leapfrog4Source,
145};
147export const mModels: MModel[] = [leapfrog, leapfrog4];
149export const mModelByKey = (key: string): MModel | undefined =>
150 mModels.find((m) => m.key === key);
152export const defaultParams = (m: { params: ParamSpec[] }): Params =>
153 Object.fromEntries(m.params.map((p) => [p.key, p.value]));