/ proof-of-concept / instrawave
Sign in
proof-of-concept / instrawave
instrawave / instruments / drum.instrument.json
742 lines · 29.4 KBCodeBlameHistory
2 "instrawave": 1,
3 "name": "drum",
4 "description": "A struck membrane over a cylindrical shell. The head’s inharmonic modes plus the cavity under it are what make it a drum.",
5 "params": [
6 {
7 "key": "nx",
8 "label": "air cells along x",
9 "unit": "1",
10 "value": 96,
11 "min": 32,
12 "max": 256,
13 "class": "accuracy",
14 "hint": "The air grid; ny = nz = nx/2. The simulation is honest to about c/(8h). Must be even.",
15 "step": 2
16 },
17 {
18 "key": "Lx",
19 "label": "domain length",
20 "unit": "m",
21 "value": 1,
22 "min": 0.6,
23 "max": 2,
24 "class": "geometry",
25 "hint": "The air box around the instrument, twice as long in x as in y and z."
26 },
27 {
28 "key": "ir_seconds",
29 "label": "room response length",
30 "unit": "s",
31 "value": 0.5,
32 "min": 0.1,
33 "max": 2,
34 "class": "accuracy",
35 "hint": "How long the recorded room responses run; a room still ringing past this is truncated."
36 },
37 {
38 "key": "mode_margin",
39 "label": "mode set margin",
40 "unit": "1",
41 "value": 2,
42 "min": 1,
43 "max": 4,
44 "class": "accuracy",
45 "hint": "Head modes are recorded up to this multiple of what the air grid spatially resolves."
46 },
47 {
48 "key": "L1",
49 "label": "head width",
50 "unit": "m",
51 "value": 0.32,
52 "min": 0.15,
53 "max": 0.5,
54 "class": "geometry",
55 "hint": "Extent along x."
56 },
57 {
58 "key": "L2",
59 "label": "head depth",
60 "unit": "m",
61 "value": 0.32,
62 "min": 0.15,
63 "max": 0.5,
64 "class": "geometry",
65 "hint": "Extent along y."
66 },
67 {
68 "key": "oz",
69 "label": "head height",
70 "unit": "m",
71 "value": 0.02,
72 "min": 0,
73 "max": 0.15,
74 "class": "geometry",
75 "hint": "Height of the head plane above z = 0."
76 },
77 {
78 "key": "shellr",
79 "label": "shell radius",
80 "unit": "m",
81 "value": 0.17,
82 "min": 0.08,
83 "max": 0.3,
84 "class": "geometry",
85 "hint": "A hair larger than the head over it."
86 },
87 {
88 "key": "shelld",
89 "label": "shell depth",
90 "unit": "m",
91 "value": 0.3,
92 "min": 0.05,
93 "max": 0.4,
94 "class": "geometry",
95 "hint": "The cavity under the head; deeper is a lower air resonance."
96 },
97 {
98 "key": "thick",
99 "label": "wall thickness",
100 "unit": "m",
101 "value": 0.016,
102 "min": 0.008,
103 "max": 0.05,
104 "class": "geometry"
105 },
106 {
107 "key": "closed",
108 "label": "closed bottom",
109 "unit": "1",
110 "value": 1,
111 "min": 0,
112 "max": 1,
113 "class": "geometry",
114 "hint": "1 puts a plate across the bottom; 0 leaves it open."
115 },
116 {
117 "key": "absorb",
118 "label": "surface absorption",
119 "unit": "1/s",
120 "value": 800,
121 "min": 0,
122 "max": 4000,
123 "class": "geometry",
124 "hint": "A lossy skin over the shell surfaces; 0 lets the cavity ring hard."
125 },
126 {
127 "key": "mic_x",
128 "label": "microphone x",
129 "unit": "m",
130 "value": 0.15,
131 "min": -0.45,
132 "max": 0.45,
133 "class": "geometry"
134 },
135 {
136 "key": "mic_y",
137 "label": "microphone y",
138 "unit": "m",
139 "value": 0.1,
140 "min": -0.2,
141 "max": 0.2,
142 "class": "geometry"
143 },
144 {
145 "key": "mic_z",
146 "label": "microphone z",
147 "unit": "m",
148 "value": 0.12,
149 "min": -0.2,
150 "max": 0.2,
151 "class": "geometry"
152 },
153 {
154 "key": "f0",
155 "label": "pitch",
156 "unit": "Hz",
157 "value": 110,
158 "min": 60,
159 "max": 300,
160 "class": "tuning",
161 "hint": "The (1,1) fundamental; the tension follows from it and the size."
162 },
163 {
164 "key": "t60",
165 "label": "decay t60",
166 "unit": "s",
167 "value": 1.5,
168 "min": 0.2,
169 "max": 6,
170 "class": "tuning",
171 "hint": "Seconds to decay 60 dB."
172 },
173 {
174 "key": "sig1",
175 "label": "brightness decay",
176 "unit": "m^2/s",
177 "value": 0.0005,
178 "min": 0,
179 "max": 0.01,
180 "class": "tuning",
181 "hint": "Frequency-dependent damping; the slap of the attack fades first."
182 },
183 {
184 "key": "strikex",
185 "label": "strike x",
186 "unit": "1",
187 "value": 0.35,
188 "min": 0.1,
189 "max": 0.9,
190 "class": "tuning",
191 "hint": "Where the stick lands along x, as a fraction."
192 },
193 {
194 "key": "strikey",
195 "label": "strike y",
196 "unit": "1",
197 "value": 0.5,
198 "min": 0.1,
199 "max": 0.9,
200 "class": "tuning"
201 },
202 {
203 "key": "striker",
204 "label": "stick width",
205 "unit": "m",
206 "value": 0.04,
207 "min": 0.01,
208 "max": 0.1,
209 "class": "tuning",
210 "hint": "Radius of the struck patch."
211 },
212 {
213 "key": "vel",
214 "label": "strike speed",
215 "unit": "m/s",
216 "value": 1.5,
217 "min": 0.1,
218 "max": 5,
219 "class": "tuning",
220 "hint": "Linear equations: sets scale only."
221 },
222 {
223 "key": "gair",
224 "label": "radiation gain",
225 "unit": "1",
226 "value": 1,
227 "min": 0,
228 "max": 2,
229 "class": "tuning"
230 }
231 ],
232 "derived": {
233 "c0": "343",
234 "cfl": "0.5",
235 "ny": "nx / 2",
236 "nz": "nx / 2",
237 "npts": "nx * ny * nz",
238 "h": "Lx / nx",
239 "Ly": "ny * h",
240 "Lz": "nz * h",
241 "dt": "cfl * h / (sqrt(3) * c0)",
242 "ir_steps": "max(1, round(ir_seconds / dt))",
243 "mic_index": "clamp(round((mic_x + Lx/2) / h - 0.5), 0, nx - 1) + nx * (clamp(round((mic_y + Ly/2) / h - 0.5), 0, ny - 1) + ny * clamp(round((mic_z + Lz/2) / h - 0.5), 0, nz - 1))",
244 "c_worst": "2 * f0__max / sqrt(1/(L1*L1) + 1/(L2*L2))",
245 "h_min": "sqrt(2 * (c_worst*c_worst*dt*dt + 4*sig1__max*dt)) / 0.95",
246 "n1": "max(5, min(256, floor(L1 / h_min) + 1))",
247 "n2": "max(5, min(256, floor(L2 / h_min) + 1))",
248 "h1": "L1 / (n1 - 1)",
249 "h2": "L2 / (n2 - 1)",
250 "mx": "min(n1 - 2, ceil(mode_margin * L1 / (2 * h)))",
251 "my": "min(n2 - 2, ceil(mode_margin * L2 / (2 * h)))",
252 "n_rows": "mx * my"
253 },
254 "shaders": {
255 "medium": {
256 "code": "// The medium: the body's rigid mask, the absorption, and where the object\n// couples into the air. One thread per air cell.\nconst PI = 3.14159265358979;\noverride npts: u32;\noverride nx: u32;\noverride ny: u32;\noverride h: f32;\noverride Lx: f32;\noverride Ly: f32;\noverride Lz: f32;\noverride shellr: f32;\noverride shelld: f32;\noverride thick: f32;\noverride closed: f32;\noverride absorb: f32;\noverride oz: f32;\noverride L1: f32;\noverride L2: f32;\n@group(0) @binding(0) var<storage, read_write> wall: array<f32>;\n@group(0) @binding(1) var<storage, read_write> sig: array<f32>;\n@group(0) @binding(2) var<storage, read_write> prof: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= npts) { return; }\n let ix = i % nx;\n let iy = (i / nx) % ny;\n let iz = i / (nx * ny);\n let x = -Lx/2.0 + (f32(ix) + 0.5) * h;\n let y = -Ly/2.0 + (f32(iy) + 0.5) * h;\n let z = -Lz/2.0 + (f32(iz) + 0.5) * h;\n let sw = 0.45 * h;\n // The shell: a cylindrical ring from z = -shelld to 0, optional bottom.\n let r = sqrt(x*x + y*y);\n let cyl_o = 0.5 * (1.0 - tanh((r - shellr) / sw));\n let cyl_i = 0.5 * (1.0 - tanh((r - (shellr - thick)) / sw));\n let cyl_z = 0.5 * (1.0 - tanh((abs(z + shelld/2.0) - shelld/2.0) / sw));\n let bot = 0.5 * (1.0 - tanh((abs(z + shelld - thick/2.0) - thick/2.0) / sw));\n let shell = min(1.0, max(0.0, cyl_o - cyl_i) * cyl_z + closed * (cyl_i * bot));\n let w = 1.0 - shell;\n wall[i] = w;\n // Open boundary at the domain edge (a quadratic absorbing ramp over the\n // outer 0.15 Ly of each face), plus the body's lossy surface skin.\n let spw = 0.15 * Ly;\n let dx = max(0.0, spw - (Lx/2.0 - abs(x))) / spw;\n let dy = max(0.0, spw - (Ly/2.0 - abs(y))) / spw;\n let dz = max(0.0, spw - (Lz/2.0 - abs(z))) / spw;\n let d = max(max(dx, dy), dz);\n sig[i] = 9000.0 * d * d + absorb * (4.0 * shell * (1.0 - shell));\n // The head radiates from its plane: a thin sheet at z = oz over its\n // extent, cross-section normalized to one.\n let aw = 2.0 * h;\n let sk = exp(-((z - oz)*(z - oz)) / (aw*aw)) / (sqrt(PI) * aw);\n let exx = 0.5 * (1.0 + tanh((L1/2.0 - abs(x)) / (1.5*h)));\n let exy = 0.5 * (1.0 + tanh((L2/2.0 - abs(y)) / (1.5*h)));\n prof[i] = sk * exx * exy * w;\n\n}\n",
257 "constants": [
258 "npts",
259 "nx",
260 "ny",
261 "h",
262 "Lx",
263 "Ly",
264 "Lz",
265 "shellr",
266 "shelld",
267 "thick",
268 "closed",
269 "absorb",
270 "oz",
271 "L1",
272 "L2"
273 ]
274 },
275 "sines_x": {
276 "code": "// Sine mode m of the object grid, interpolated to air\n// x centers: sx[(mode-1) * nx + ix]. One thread per table entry.\nconst PI = 3.14159265358979;\noverride n1: u32;\noverride mx: u32;\noverride nx: u32;\noverride L1: f32;\noverride h1: f32;\noverride Lx: f32;\noverride h: f32;\n@group(0) @binding(0) var<storage, read_write> table: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= mx * nx) { return; }\n let mi = i % mx;\n let ia = i / mx;\n let xw = -Lx/2.0 + (f32(ia) + 0.5) * h;\n let u = (xw + L1/2.0) / h1;\n let j = i32(floor(u));\n var v = 0.0;\n if (j >= 0 && j < i32(n1) - 1) {\n let f = u - f32(j);\n let mm = f32(mi + 1u);\n let s0 = sin(mm * PI * f32(j) / f32(n1 - 1u));\n let s1 = sin(mm * PI * f32(j + 1) / f32(n1 - 1u));\n v = (1.0 - f) * s0 + f * s1;\n }\n table[mi * nx + ia] = v;\n}\n",
277 "constants": [
278 "n1",
279 "mx",
280 "nx",
281 "L1",
282 "h1",
283 "Lx",
284 "h"
285 ]
286 },
287 "impulse": {
288 "code": "// The reciprocal kick: one unit impulse at the microphone cell, entering\n// the way a source enters the first step. One thread.\noverride mic_index: u32;\noverride dt: f32;\n@group(0) @binding(0) var<storage, read_write> p: array<f32>;\n@group(0) @binding(1) var<storage, read> wall: array<f32>;\n@group(0) @binding(2) var<storage, read> sig: array<f32>;\n@compute @workgroup_size(1)\nfn main() {\n p[mic_index] = dt * dt * wall[mic_index] / (1.0 + sig[mic_index] * dt);\n}\n",
289 "constants": [
290 "mic_index",
291 "dt"
292 ]
293 },
294 "step": {
295 "code": "// One leapfrog step of the air: the wall-masked Laplacian (each stencil\n// face scaled by the mask at the neighbour it reads, the rigid condition in\n// divergence form) fused with the update. One thread per air cell.\noverride npts: u32;\noverride nx: u32;\noverride ny: u32;\noverride nz: u32;\noverride h: f32;\noverride dt: f32;\noverride c0: f32;\n@group(0) @binding(0) var<storage, read> p: array<f32>;\n@group(0) @binding(1) var<storage, read> pm: array<f32>;\n@group(0) @binding(2) var<storage, read_write> pn: array<f32>;\n@group(0) @binding(3) var<storage, read> wall: array<f32>;\n@group(0) @binding(4) var<storage, read> sig: array<f32>;\nfn face(ix: i32, iy: i32, iz: i32, pc: f32) -> f32 {\n if (ix < 0 || ix >= i32(nx) || iy < 0 || iy >= i32(ny) || iz < 0 || iz >= i32(nz)) {\n return -pc; // outside the domain: p = 0, open\n }\n let k = u32(ix) + nx * (u32(iy) + ny * u32(iz));\n return wall[k] * (p[k] - pc);\n}\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= npts) { return; }\n let ix = i32(i % nx);\n let iy = i32((i / nx) % ny);\n let iz = i32(i / (nx * ny));\n let pc = p[i];\n let s = face(ix - 1, iy, iz, pc) + face(ix + 1, iy, iz, pc)\n + face(ix, iy - 1, iz, pc) + face(ix, iy + 1, iz, pc)\n + face(ix, iy, iz - 1, pc) + face(ix, iy, iz + 1, pc);\n let lp = wall[i] * s / (h * h);\n let sd = sig[i] * dt;\n pn[i] = wall[i] * ((2.0 * pc - (1.0 - sd) * pm[i] + (c0 * dt) * (c0 * dt) * lp) / (1.0 + sd));\n}\n",
296 "constants": [
297 "npts",
298 "nx",
299 "ny",
300 "nz",
301 "h",
302 "dt",
303 "c0"
304 ]
305 },
306 "plane": {
307 "code": "// Contract the field against the radiation profile over z:\n// one thread per air column.\noverride nx: u32;\noverride ny: u32;\noverride nz: u32;\n@group(0) @binding(0) var<storage, read_write> plane: array<f32>;\n@group(0) @binding(1) var<storage, read> p: array<f32>;\n@group(0) @binding(2) var<storage, read> prof: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= nx * ny) { return; }\n var s = 0.0;\n for (var iz = 0u; iz < nz; iz++) {\n let k = i + nx * ny * iz;\n s += prof[k] * p[k];\n }\n plane[i] = s;\n}\n",
308 "constants": [
309 "nx",
310 "ny",
311 "nz"
312 ]
313 },
314 "xc": {
315 "code": "// Contract the plane against every x sine row: tmp[m + mx * iy].\noverride nx: u32;\noverride ny: u32;\noverride mx: u32;\n@group(0) @binding(0) var<storage, read_write> tmp: array<f32>;\n@group(0) @binding(1) var<storage, read> plane: array<f32>;\n@group(0) @binding(2) var<storage, read> sx: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= mx * ny) { return; }\n let m = i % mx;\n let iy = i / mx;\n var s = 0.0;\n for (var ix = 0u; ix < nx; ix++) {\n s += sx[m * nx + ix] * plane[ix + nx * iy];\n }\n tmp[i] = s;\n}\n",
316 "constants": [
317 "nx",
318 "ny",
319 "mx"
320 ]
321 },
322 "proj": {
323 "code": "// Finish each pattern's projection against its y sine row and write this\n// step's sample: kernels[q * ir_steps + t], q = (m-1) + mx*(n-1).\noverride ny: u32;\noverride mx: u32;\noverride my: u32;\noverride ir_steps: u32;\n@group(0) @binding(0) var<storage, read_write> kernels: array<f32>;\n@group(0) @binding(1) var<storage, read> tmp: array<f32>;\n@group(0) @binding(2) var<storage, read> sy: array<f32>;\n@group(0) @binding(3) var<storage, read> tick: array<u32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= mx * my) { return; }\n let m = i % mx;\n let n = i / mx;\n var s = 0.0;\n for (var iy = 0u; iy < ny; iy++) {\n s += sy[n * ny + iy] * tmp[m + mx * iy];\n }\n let t = tick[0];\n if (t < ir_steps) {\n kernels[i * ir_steps + t] = s;\n }\n}\n",
324 "constants": [
325 "ny",
326 "mx",
327 "my",
328 "ir_steps"
329 ]
330 },
331 "excite_x": {
332 "code": "// Project this axis's excitation profile onto each recorded sine mode:\n// bx[e * mx + (mode-1)]. One thread per (event, mode).\nconst PI = 3.14159265358979;\noverride n1: u32;\noverride mx: u32;\noverride ev_stride: u32;\noverride h1: f32;\noverride L1: f32;\n@group(0) @binding(0) var<storage, read_write> bout: array<f32>;\n@group(0) @binding(1) var<storage, read> ev: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= mx * arrayLength(&ev) / ev_stride) { return; }\n let mi = i % mx;\n let e = i / mx;\n let base = e * ev_stride;\n let strikex = ev[base + 4u];\n let striker = ev[base + 6u];\n let mm = f32(mi + 1u);\n var s = 0.0;\n for (var k = 1u; k < n1 - 1u; k++) {\n let xi = f32(k) * h1;\n let dx = (xi - strikex * L1) / striker;\n let u0 = exp(-dx * dx);\n s += sin(mm * PI * f32(k) / f32(n1 - 1u)) * u0;\n }\n bout[i] = s;\n}\n",
333 "constants": [
334 "n1",
335 "mx",
336 "ev_stride",
337 "h1",
338 "L1"
339 ]
340 },
341 "slots": {
342 "code": "// One thread per (event, recorded mode): the mode's excitation amplitude,\n// its recursion roots, and the acceleration-series slots that drive its\n// kernel row.\nconst PI = 3.14159265358979;\noverride mx: u32;\noverride my: u32;\noverride n_rows: u32;\noverride n1: u32;\noverride n2: u32;\noverride ev_stride: u32;\noverride h1: f32;\noverride h2: f32;\noverride L1: f32;\noverride L2: f32;\noverride dt: f32;\n@group(0) @binding(0) var<storage, read_write> slots: array<f32>;\n@group(0) @binding(1) var<storage, read> ev: array<f32>;\n@group(0) @binding(2) var<storage, read> bx: array<f32>;\n@group(0) @binding(3) var<storage, read> by: array<f32>;\n\nfn cmul(ar: f32, ai: f32, br: f32, bi: f32) -> vec2f {\n return vec2f(ar * br - ai * bi, ar * bi + ai * br);\n}\n// c = a * gain * (z - 2 + 1/z) = a * gain * (z-1)^2 / z, the acceleration\n// series, with z-1 supplied stably (the naive z - 1 cancels).\nfn acc_scale(p: vec4f, zm1Re: f32, gain: f32) -> vec4f {\n let m2 = p.z * p.z + p.w * p.w;\n if (m2 == 0.0) { return vec4f(0.0); }\n let sq = cmul(zm1Re, p.w, zm1Re, p.w);\n let num = cmul(sq.x, sq.y, p.z, -p.w);\n let c = cmul(p.x, p.y, gain * num.x / m2, gain * num.y / m2);\n return vec4f(c.x, c.y, p.z, p.w);\n}\n// c = a * gain * z, the next-displacement series (what a string's bridge\n// slope is read from).\nfn next_scale(p: vec4f, gain: f32) -> vec4f {\n let c = cmul(p.x, p.y, gain * p.z, gain * p.w);\n return vec4f(c.x, c.y, p.z, p.w);\n}\n\n\nfn write_slot(idx: u32, p: vec4f, row: f32, start: f32) {\n slots[idx * 6u] = p.x;\n slots[idx * 6u + 1u] = p.y;\n slots[idx * 6u + 2u] = p.z;\n slots[idx * 6u + 3u] = p.w;\n slots[idx * 6u + 4u] = row;\n slots[idx * 6u + 5u] = start;\n}\n\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n let n_events = arrayLength(&ev) / ev_stride;\n if (i >= n_events * n_rows) { return; }\n let e = i / n_rows;\n let q = i % n_rows;\n let base = e * ev_stride;\n let start = ev[base];\n let f0 = ev[base + 1u];\n let t60 = ev[base + 2u];\n let sig1 = ev[base + 3u];\n let vel = ev[base + 7u];\n let gair = ev[base + 8u];\n let mi = q % mx;\n let ni = q / mx;\n let m = f32(mi + 1u);\n let n = f32(ni + 1u);\n let b = bx[e * mx + mi] * by[e * my + ni] * (-(dt * vel)) * 4.0 / (f32(n1 - 1u) * f32(n2 - 1u));\n let s1 = sin(m * PI / (2.0 * f32(n1 - 1u)));\n let s2 = sin(n * PI / (2.0 * f32(n2 - 1u)));\n let lam = (4.0 / (h1 * h1)) * s1 * s1 + (4.0 / (h2 * h2)) * s2 * s2;\n let cm = 2.0 * f0 / sqrt(1.0 / (L1 * L1) + 1.0 / (L2 * L2));\n let w2 = cm * cm * lam;\n\n let d = (6.91 / t60) * dt;\n let opd = 1.0 + d;\n let g = d + 2.0 * sig1 * dt * lam;\n let x = dt * dt * w2 + 2.0 * sig1 * dt * lam;\n let zRe = (1.0 - 0.5 * x) / opd;\n let zm1Re = -(0.5 * x + d) / opd;\n let nn = dt * dt * w2 - g * d - 0.25 * x * x;\n var p0 = vec4f(0.0);\n var p1 = vec4f(0.0);\n var zm10 = 0.0;\n var zm11 = 0.0;\n if (nn > 0.0) {\n let zIm = sqrt(nn) / opd;\n let mag2 = (1.0 - g) / opd;\n let aRe = 0.0;\n let aIm = b * mag2 / zIm;\n p0 = vec4f(aRe, aIm, zRe, zIm);\n zm10 = zm1Re;\n } else {\n let sq = sqrt(-nn) / opd;\n let r1 = zRe + sq;\n let r2 = zRe - sq;\n if (r1 != r2 && r1 != 0.0 && r2 != 0.0) {\n let det = 1.0 / r1 - 1.0 / r2;\n let a1 = b / det;\n let a2 = -a1;\n p0 = vec4f(a1, 0.0, r1, 0.0);\n p1 = vec4f(a2, 0.0, r2, 0.0);\n zm10 = zm1Re + sq;\n zm11 = zm1Re - sq;\n }\n }\n let ga = gair / (dt * dt);\n write_slot(i * 2u, acc_scale(p0, zm10, ga), f32(q), start);\n write_slot(i * 2u + 1u, acc_scale(p1, zm11, ga), f32(q), start);\n}\n",
343 "constants": [
344 "mx",
345 "my",
346 "n_rows",
347 "n1",
348 "n2",
349 "ev_stride",
350 "h1",
351 "h2",
352 "L1",
353 "L2",
354 "dt"
355 ]
356 },
357 "synth": {
358 "code": "// One thread per slot: a complex one-pole over this tile of samples, the\n// kernel row as input while it lasts and silence after (the closed-form\n// tail). State persists across tiles.\noverride tile: u32;\noverride total_steps: u32;\noverride ir_steps: u32;\noverride n_slots: u32;\n@group(0) @binding(0) var<storage, read> slots: array<f32>;\n@group(0) @binding(1) var<storage, read_write> state: array<f32>;\n@group(0) @binding(2) var<storage, read> kernels: array<f32>;\n@group(0) @binding(3) var<storage, read_write> partial: array<f32>;\n@group(0) @binding(4) var<storage, read> tick: array<u32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= n_slots) { return; }\n let cRe = slots[i * 6u];\n let cIm = slots[i * 6u + 1u];\n let zRe = slots[i * 6u + 2u];\n let zIm = slots[i * 6u + 3u];\n let row = u32(slots[i * 6u + 4u]);\n let start = u32(slots[i * 6u + 5u]);\n var wRe = state[i * 2u];\n var wIm = state[i * 2u + 1u];\n let t0 = tick[0] * tile;\n for (var s = 0u; s < tile; s++) {\n let t = t0 + s;\n if (t >= total_steps) { break; }\n var hv = 0.0;\n if (t >= start && t - start < ir_steps) {\n hv = kernels[row * ir_steps + (t - start)];\n }\n let nRe = zRe * wRe - zIm * wIm + hv;\n let nIm = zRe * wIm + zIm * wRe;\n wRe = nRe;\n wIm = nIm;\n partial[i * tile + s] = cRe * wRe - cIm * wIm;\n }\n state[i * 2u] = wRe;\n state[i * 2u + 1u] = wIm;\n}\n",
359 "constants": [
360 "tile",
361 "total_steps",
362 "ir_steps",
363 "n_slots"
364 ]
365 },
366 "reduce": {
367 "code": "// One thread per sample of the tile: sum every slot's contribution.\noverride tile: u32;\noverride total_steps: u32;\noverride n_slots: u32;\n@group(0) @binding(0) var<storage, read> partial: array<f32>;\n@group(0) @binding(1) var<storage, read_write> trace: array<f32>;\n@group(0) @binding(2) var<storage, read> tick: array<u32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= tile) { return; }\n let t = tick[0] * tile + i;\n if (t >= total_steps) { return; }\n var s = 0.0;\n for (var q = 0u; q < n_slots; q++) {\n s += partial[q * tile + i];\n }\n trace[t] = s;\n}\n",
368 "constants": [
369 "tile",
370 "total_steps",
371 "n_slots"
372 ]
373 },
374 "sines_y": {
375 "code": "// Sine mode n of the object grid, interpolated to air\n// y centers: sy[(mode-1) * ny + iy]. One thread per table entry.\nconst PI = 3.14159265358979;\noverride n2: u32;\noverride my: u32;\noverride ny: u32;\noverride L2: f32;\noverride h2: f32;\noverride Ly: f32;\noverride h: f32;\n@group(0) @binding(0) var<storage, read_write> table: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= my * ny) { return; }\n let mi = i % my;\n let ia = i / my;\n let xw = -Ly/2.0 + (f32(ia) + 0.5) * h;\n let u = (xw + L2/2.0) / h2;\n let j = i32(floor(u));\n var v = 0.0;\n if (j >= 0 && j < i32(n2) - 1) {\n let f = u - f32(j);\n let mm = f32(mi + 1u);\n let s0 = sin(mm * PI * f32(j) / f32(n2 - 1u));\n let s1 = sin(mm * PI * f32(j + 1) / f32(n2 - 1u));\n v = (1.0 - f) * s0 + f * s1;\n }\n table[mi * ny + ia] = v;\n}\n",
376 "constants": [
377 "n2",
378 "my",
379 "ny",
380 "L2",
381 "h2",
382 "Ly",
383 "h"
384 ]
385 },
386 "excite_y": {
387 "code": "// Project this axis's excitation profile onto each recorded sine mode:\n// by[e * my + (mode-1)]. One thread per (event, mode).\nconst PI = 3.14159265358979;\noverride n2: u32;\noverride my: u32;\noverride ev_stride: u32;\noverride h2: f32;\noverride L2: f32;\n@group(0) @binding(0) var<storage, read_write> bout: array<f32>;\n@group(0) @binding(1) var<storage, read> ev: array<f32>;\n@compute @workgroup_size(64)\nfn main(@builtin(global_invocation_id) gid: vec3<u32>) {\n let i = gid.x + gid.y * 4194240u;\n if (i >= my * arrayLength(&ev) / ev_stride) { return; }\n let mi = i % my;\n let e = i / my;\n let base = e * ev_stride;\n let strikey = ev[base + 5u];\n let striker = ev[base + 6u];\n let mm = f32(mi + 1u);\n var s = 0.0;\n for (var k = 1u; k < n2 - 1u; k++) {\n let xi = f32(k) * h2;\n let dx = (xi - strikey * L2) / striker;\n let u0 = exp(-dx * dx);\n s += sin(mm * PI * f32(k) / f32(n2 - 1u)) * u0;\n }\n bout[i] = s;\n}\n",
388 "constants": [
389 "n2",
390 "my",
391 "ev_stride",
392 "h2",
393 "L2"
394 ]
395 }
396 },
397 "precompute": {
398 "buffers": [
399 {
400 "name": "wall",
401 "count": "npts"
402 },
403 {
404 "name": "sig",
405 "count": "npts"
406 },
407 {
408 "name": "prof",
409 "count": "npts"
410 },
411 {
412 "name": "sx",
413 "count": "mx * nx"
414 },
415 {
416 "name": "sy",
417 "count": "my * ny"
418 },
419 {
420 "name": "p",
421 "count": "npts"
422 },
423 {
424 "name": "pm",
425 "count": "npts"
426 },
427 {
428 "name": "pn",
429 "count": "npts"
430 },
431 {
432 "name": "plane",
433 "count": "nx * ny"
434 },
435 {
436 "name": "tmp",
437 "count": "mx * ny"
438 },
439 {
440 "name": "kernels",
441 "count": "n_rows * ir_steps"
442 }
443 ],
444 "setup": [
445 {
446 "shader": "medium",
447 "buffers": [
448 "wall",
449 "sig",
450 "prof"
451 ],
452 "dispatch": "npts"
453 },
454 {
455 "shader": "sines_x",
456 "buffers": [
457 "sx"
458 ],
459 "dispatch": "mx * nx"
460 },
461 {
462 "shader": "sines_y",
463 "buffers": [
464 "sy"
465 ],
466 "dispatch": "my * ny"
467 },
468 {
469 "shader": "impulse",
470 "buffers": [
471 "p",
472 "wall",
473 "sig"
474 ],
475 "dispatch": "1"
476 }
477 ],
478 "loop": {
479 "count": "ir_steps",
480 "batch": 512,
481 "rotate": [
482 [
483 "p",
484 "pm",
485 "pn"
486 ]
487 ],
488 "body": [
489 {
490 "shader": "plane",
491 "buffers": [
492 "plane",
493 "p",
494 "prof"
495 ],
496 "dispatch": "nx * ny"
497 },
498 {
499 "shader": "xc",
500 "buffers": [
501 "tmp",
502 "plane",
503 "sx"
504 ],
505 "dispatch": "mx * ny"
506 },
507 {
508 "shader": "proj",
509 "buffers": [
510 "kernels",
511 "tmp",
512 "sy",
513 "__tick"
514 ],
515 "dispatch": "n_rows"
516 },
517 {
518 "shader": "step",
519 "buffers": [
520 "p",
521 "pm",
522 "pn",
523 "wall",
524 "sig"
525 ],
526 "dispatch": "npts"
527 }
528 ]
529 },
530 "outputs": [
531 "kernels"
532 ]
533 },
534 "render": {
535 "buffers": [
536 {
537 "name": "kernels",
538 "count": "n_rows * ir_steps"
539 },
540 {
541 "name": "bx",
542 "count": "n_events * mx"
543 },
544 {
545 "name": "by",
546 "count": "n_events * my"
547 },
548 {
549 "name": "slots",
550 "count": "n_slots * 6"
551 },
552 {
553 "name": "state",
554 "count": "n_slots * 2"
555 },
556 {
557 "name": "partial",
558 "count": "n_slots * tile"
559 },
560 {
561 "name": "trace",
562 "count": "total_steps"
563 }
564 ],
565 "inputs": [
566 "kernels"
567 ],
568 "eventParams": [
569 "f0",
570 "t60",
571 "sig1",
572 "strikex",
573 "strikey",
574 "striker",
575 "vel",
576 "gair"
577 ],
578 "derived": {
579 "ev_stride": "9",
580 "n_slots": "n_events * n_rows * 2"
581 },
582 "setup": [
583 {
584 "shader": "excite_x",
585 "buffers": [
586 "bx",
587 "__events"
588 ],
589 "dispatch": "n_events * mx"
590 },
591 {
592 "shader": "excite_y",
593 "buffers": [
594 "by",
595 "__events"
596 ],
597 "dispatch": "n_events * my"
598 },
599 {
600 "shader": "slots",
601 "buffers": [
602 "slots",
603 "__events",
604 "bx",
605 "by"
606 ],
607 "dispatch": "n_events * n_rows"
608 }
609 ],
610 "loop": {
611 "count": "n_tiles",
612 "batch": 8,
613 "body": [
614 {
615 "shader": "synth",
616 "buffers": [
617 "slots",
618 "state",
619 "kernels",
620 "partial",
621 "__tick"
622 ],
623 "dispatch": "n_slots"
624 },
625 {
626 "shader": "reduce",
627 "buffers": [
628 "partial",
629 "trace",
630 "__tick"
631 ],
632 "dispatch": "tile"
633 }
634 ]
635 },
636 "trace": "trace"
637 },
638 "visual": {
639 "dims": [
640 "nx",
641 "ny",
642 "nz"
643 ],
644 "extents": [
645 "Lx",
646 "Ly",
647 "Lz"
648 ],
649 "buffers": [
650 {
651 "name": "wall",
652 "count": "npts"
653 },
654 {
655 "name": "sig",
656 "count": "npts"
657 },
658 {
659 "name": "prof",
660 "count": "npts"
661 }
662 ],
663 "setup": [
664 {
665 "shader": "medium",
666 "buffers": [
667 "wall",
668 "sig",
669 "prof"
670 ],
671 "dispatch": "npts"
672 }
673 ],
674 "volumes": [
675 {
676 "buffer": "wall",
677 "style": "solid",
678 "invert": true,
679 "color": [
680 0.62,
681 0.48,
682 0.34
683 ]
684 },
685 {
686 "buffer": "prof",
687 "style": "cloud",
688 "color": [
689 1,
690 0.45,
691 0.2
692 ]
693 }
694 ],
695 "markers": [
696 {
697 "kind": "rect",
698 "corner": [
699 "-L1/2",
700 "-L2/2",
701 "oz"
702 ],
703 "du": [
704 "L1",
705 "0",
706 "0"
707 ],
708 "dv": [
709 "0",
710 "L2",
711 "0"
712 ],
713 "color": [
714 1,
715 0.85,
716 0.3
717 ],
718 "label": "head"
719 },
720 {
721 "kind": "point",
722 "at": [
723 "mic_x",
724 "mic_y",
725 "mic_z"
726 ],
727 "color": [
728 0.3,
729 0.85,
730 1
731 ],
732 "label": "mic"
733 }
734 ],
735 "partials": {
736 "buffer": "slots",
737 "stride": 6,
738 "zRe": 2,
739 "zIm": 3
740 }
741 }
↑↓ move↵openescclose