1# Cymbal2 Simulator — web app
3Interactive browser implementation of the physical model described in `../README.md`. The algorithm itself is written in MATLAB syntax, in `../matlab/*.m`, and executed client-side by [numbl](https://numbl.org) inside a Web Worker; the TypeScript here is only the UI, the plots, the WAV encoder, and the marshalling of parameters and tensors in and out of the numbl runtime.
5The signal chain: a prescribed hand stroke drives an Euler-Bernoulli stick with a Hertz-contact mallet head against a von Karman shallow spherical shell on a washer support, integrated step by step in the time domain; the output is the shell's normal acceleration at a pickup point, decimated and normalized.
7The "How it works" button (also linkable as `#how-it-works`) opens an interactive tutorial covering the theory of Samejima (2021) and the implementation: the von Karman equations, the washer and mallet subsystems, the grid, the per-wavenumber solves, and the implicit time stepping, with figures whose parameters can be dragged and several of whose toy systems render audio in the page (a Duffing pitch glide, a sonified Fermi-Pasta-Ulam cascade, a playable mallet strike on a modal target, and the scheme's frequency detuning). The tutorial cites the paper's equation numbers throughout; its implementation lives in `src/tutorial/`.
9## Usage
11```
12npm install
13npm run dev # development server
14npm run build # production build in dist/
15npm run selftest # run ../matlab/cymbal2_validate.m through numbl in Node
16```
18The self-test runs the closed-form and reproducibility checks and the pinned diagnostics; the two full renders inside it make it take a few minutes. The MATLAB code can also be run directly: `cd ../matlab && numbl run cymbal2_demo.m` (or `cymbal2_validate.m`), and runs unchanged in MATLAB proper, where it is several times faster.
20## Where the time goes
22Everywhere. Unlike the sibling `cymbal` app, whose expensive stage (the eigensolves) could be cached and whose synthesis was an oscillator bank, here the sound is a nonlinear time-domain integration of the whole shell, several hundred thousand steps of coupled solves, and every parameter feeds it. The Render preview button runs a cheap pass (no oversampling; the duration and the azimuthal grid are those set, the latter because hard strokes diverge without the full wavenumber ladder), Render full runs the real thing, and either can be cancelled while it runs; the status shows the stage, a progress bar and the elapsed time. This is the same control set the sibling instrument apps carry, and there as here nothing renders on its own. The only piece the worker caches is the cheap linear mode table that the spectrum plot overlays.
24When the browser exposes WebGPU, the time loop runs on the GPU instead of through the interpreter: the worker still executes the unmodified setup stage (grid, per-wavenumber inverses, mallet, stroke schedule) and output stage through numbl, but hands the operators to a WGSL port of `cymbal2_sim.m` (`src/worker/gpuSim.ts`, eight compute dispatches per time step; the design and its measurements are documented in the [cymbal2-webgpu](https://vault1.magland.org/proof-of-concept/cymbal2-webgpu) exploration). This is 20x or more faster than interpreting the loop, even on an integrated GPU, and the diagnostics panel says which path a render took. The GPU integrates in single precision; the contact diagnostics match the double-precision interpreter to a fraction of a percent, and the trajectory of a crash is chaotic either way. Without WebGPU the interpreter path runs as before, and expect a minute or two for a preview and many minutes for a full render. One interaction with the code editor: the WGSL mirrors the shipped `cymbal2_sim.m`, so editing that one file falls back to the interpreter, where the edit is live (a warning in the diagnostics says so); edits to every other `.m` file flow through numbl on both paths. `npm i --no-save webgpu && npx tsx scripts/gpu-selftest.ts` runs the GPU loop from Node on desktop WebGPU and checks the pinned diagnostics.
26## Presets
28The Preset selector at the top of the parameter panel replaces the whole parameter set with a named configuration; picking one and then adjusting shows "custom". The app opens on "Paper demo", the stroke fitted against the demonstration recording published by the paper's authors (the repository's `cymbal2-paper-demo.wav`); the others span the stroke range from a near-linear tap to the hard-crash ceiling, a roll, the rigid clamp of the earlier literature, two mallets, and two other shells. Every preset is checked to integrate stably at the preview tier, which is the binding case; the notes under the selector say where each one's headroom runs out.
30## Reading and editing the code in the app
32The Code button opens the MATLAB source in a CodeMirror editor with MATLAB syntax highlighting. Every `.m` file in `../matlab` is listed, tagged by whether the app executes it or it is a CLI entry point. The sources are editable and an edit takes effect on the next render, so the physics can be changed in place: setting the curvature term `Rs` large in `cymbal2_system.m` flattens the shell into a plate, and zeroing the `Luu` term in `cymbal2_sim.m` removes the stretching nonlinearity, leaving a linear shell that never crashes however hard it is hit.
34Edits live in memory only. They are deliberately not persisted, so a reload restores the code the app shipped with; "Revert file" and "Revert all" undo them sooner, and a dot marks the files that differ from the shipped source. A fault in edited code stops the render where it occurred and is reported with its file and line; nothing is left playable, since the audio on hand would no longer be what the code on screen produces, and reverting recovers. Two cases are worth naming because numbl itself does not treat them as fatal: a file that does not parse is dropped with a warning rather than aborting the run, so the worker parses every source before starting a render and stops there if one of the files the app executes fails; and a numbl error raised while the GPU path is setting up is reported as the fault it is, rather than falling back to the interpreter to fail again slowly.