1/**
2 * The scene: its MATLAB source, and the parameters the host offers it.
3 *
4 * Same split as the model (src/mgpu/registry.ts): what the body *is* lives in
5 * the .m, and the sliders around it live here. A scene also gets `x`, `y`,
6 * `z` (metres), `Lx`, `Ly`, `Lz`, `h` (metres), `c0` (m/s), `Ls` (the
7 * string's length, metres) and the grid's counts for free — see
8 * src/scene/scene.ts.
9 */
10import type { ParamSpec, Params } from '../mgpu/registry.ts';
11import boxSource from '../../scenes/box.m?raw';
13export interface MScene {
14 key: string;
15 label: string;
16 blurb: string;
17 params: ParamSpec[];
18 /** The string's height above the top plate, from this scene's parameters —
19 * the host needs it to draw the string and place defaults, and the scene
20 * needs it to build the line profile, so the scene's `gap` parameter is
21 * the one shared source. */
22 stringZ: (params: Params) => number;
23 source: string;
24}
26export const boxScene: MScene = {
27 key: 'box',
28 label: 'Box',
29 blurb: 'A rigid box with a round sound hole in its top plate, under the string.',
30 params: [
31 {
32 key: 'body',
33 label: 'body (0 = removed)',
34 value: 1,
35 min: 0,
36 max: 1,
37 step: 1,
38 hint: 'Set to 0 to take the box away entirely and hear the bare string in open air. The bridge patch stays, so the bridge-drive route still sounds.',
39 },
40 {
41 key: 'boxl',
42 label: 'body length (m)',
43 value: 0.66,
44 min: 0.3,
45 max: 0.9,
46 step: 0.01,
47 hint: 'Along the string. The default just outreaches the string, so the bridge end sits over the body.',
48 },
49 { key: 'boxw', label: 'body width (m)', value: 0.16, min: 0.08, max: 0.3, step: 0.005 },
50 {
51 key: 'boxd',
52 label: 'body depth (m)',
53 value: 0.08,
54 min: 0.03,
55 max: 0.2,
56 step: 0.005,
57 hint: 'How far the box hangs below its top plate. Deeper means a bigger cavity and a lower air resonance.',
58 },
59 {
60 key: 'thick',
61 label: 'wall thickness (m)',
62 value: 0.016,
63 min: 0.008,
64 max: 0.04,
65 step: 0.002,
66 hint: 'Below about two cells the plates start to leak — the grid cannot hold a thinner wall closed.',
67 },
68 {
69 key: 'holer',
70 label: 'sound hole radius (m)',
71 value: 0.028,
72 min: 0,
73 max: 0.08,
74 step: 0.002,
75 hint: 'The opening in the top plate, toward the string. At 0 the box is sealed; the hole’s size against the cavity’s volume is what sets the air resonance (the Helmholtz pitch).',
76 },
77 {
78 key: 'holex',
79 label: 'sound hole x (m)',
80 value: 0,
81 min: -0.3,
82 max: 0.3,
83 step: 0.01,
84 },
85 {
86 key: 'gap',
87 label: 'string height (m)',
88 value: 0.02,
89 min: 0.01,
90 max: 0.06,
91 step: 0.002,
92 hint: 'How far above the top plate the string runs.',
93 },
94 {
95 key: 'patchr',
96 label: 'bridge patch radius (m)',
97 value: 0.05,
98 min: 0.02,
99 max: 0.12,
100 step: 0.005,
101 hint: 'Size of the top-plate region the bridge force drives (the stand-in for the moving plate).',
102 },
103 {
104 key: 'absorb',
105 label: 'wall absorption (1/s)',
106 value: 800,
107 min: 0,
108 max: 2500,
109 step: 25,
110 hint: 'A thin lossy skin on the shell’s surfaces. At 0 the rigid cavity rings almost forever; turn it up until the box stops sounding metallic.',
111 },
112 ],
113 stringZ: (params) => params.gap ?? 0.02,
114 source: boxSource,
115};
117export const mScenes: MScene[] = [boxScene];
119export const defaultSceneParams = (s: MScene): Params =>
120 Object.fromEntries(s.params.map((p) => [p.key, p.value]));