96aec9cTriangle strike simulator: MATLAB model and interactive web appJeremy Magland 1# Triangle 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 Hertz beater contact (or a seeded roll of them), the bending, stretching, and torsional modes of the open bent rod from a finite element eigensolve, a coherent sum of element dipoles to the listener point, and Kaiser-windowed anti-aliased decimation to 48 kHz.
7## Usage
9```
10npm install
11npm run dev # development server
12npm run build # production build in dist/
13npm run selftest # run ../matlab/triangle_validate.m through numbl in Node
14```
16The self-test runs the closed-form and reproducibility checks and the pinned diagnostics. The MATLAB code can also be run directly: `cd ../matlab && numbl run triangle_demo.m` (or `triangle_validate.m`).
18## Why it renders fast
20Nothing is integrated. Between strikes every mode is a damped exponential, so the synthesis evaluates `exp(lambda t)` over each inter-strike segment in closed form (chunked only to bound memory) and adds each strike's modal velocity jump as it lands. The cost that remains is the eigensolve of the two finite element families, about a second, and that is exactly the piece the worker caches: striking somewhere else, changing the beater, or moving the microphone reuses the cached modes.
22## Reading and editing the code in the app
24The 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: an edit re-renders after the usual debounce, so changing the physics in the editor is heard and plotted the same way moving a slider is. Removing the bending correction sign flip in `triangle_fem.m`, for instance, decouples torsion from out-of-plane bending and audibly thins the cluster of partials.
26Edits 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 is reported with its file and line (numbl skips a file it cannot parse and then fails at the call site, so the worker captures the parse warning and reports that instead), the last successful render stays on screen, and reverting recovers.
28## How rendering is tiered
30Changing any parameter triggers a debounced fast preview (oversample 1); the Render button runs the full-quality pass. While the current result is a preview, the Play and Download buttons and the status badge say so. The worker runs each stage as a separate numbl execution and keys its caches by tier: rod geometry, material, damping, and discretization parameters invalidate the mode basis (the eigensolve); the strike and roll parameters, the beater, and the listener only re-run the strike process and the radiation; pre-delay, normalization, and bit depth only re-run the output stage. Intermediate tensors stay inside the worker as numbl RuntimeValues. Because the eigensolve does not depend on the synthesis rate, previews reuse the full-quality mode basis.
32The New seed button redraws the roll timing and velocities without touching any other parameter (a single strike is deterministic, so the button is disabled there). The Loop button repeats the render continuously, swapping in each new render as parameters change. Parameters persist in localStorage; "Reset to defaults" restores the values in `src/params.ts` (which `../matlab/triangle_defaults.m` mirrors).
34## Layout
36- `../matlab/` — the algorithm: `triangle_geometry.m`, `triangle_fem.m`, `triangle_basis.m`, `triangle_strikes.m`, `triangle_radiate.m`, `triangle_output.m`, `triangle_rng.m`, plus `triangle_render.m` / `triangle_demo.m` / `triangle_validate.m` for CLI use
37- `src/params.ts` — parameter schema, defaults, tier assignment, persistence
38- `src/mfiles.ts` — the `.m` sources, bundled at build time and hashed for cache keying
39- `src/worker/renderWorker.ts` — off-thread numbl execution with tiered caching
40- `src/lib/outline.ts` — the open-triangle centerline, mirrored from `triangle_geometry.m` for the scene and the mode-shape plot
41- `src/components/` — 3D scene (three.js), parameter panel, code panel and editor, plots (strikes, waveform, spectrum, spectrogram, mode map, mode shapes)
42- `src/selftest.ts` — Node harness for the MATLAB validation script
44## A note on the seeded generator
46The roll process draws from a 32-bit LCG in `triangle_rng.m` rather than MATLAB's `rand`, so a given seed produces the identical pattern in the numbl CLI, in Node, and in the browser. That is what lets the self-test pin exact impulse sums and hit times.