Recorder Simulator — web app#
Interactive 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 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.
The signal chain: a transmission-line solve of the tapered bore with its tone hole lattice under the chosen fingering, a modal fit of the resonances, a sample-by-sample integration of the jet / bore feedback loop (the note is a self-sustained oscillation, not a prescribed one), a coherent sum of aperture monopoles to the listener point, and Kaiser-windowed anti-aliased decimation to 48 kHz.
Usage#
npm install
npm run dev # development server
npm run build # production build in dist/
npm run selftest # run ../matlab/recorder_validate.m through numbl in Node
The self-test runs the closed-form and reproducibility checks, the emergent register and overblow checks, and the pinned diagnostics. The MATLAB code can also be run directly: cd ../matlab && numbl run recorder_demo.m (or recorder_validate.m).
Why rendering is tiered the way it is#
Unlike the sibling instruments, nothing here is closed-form between events: a recorder is a self-oscillator, so the jet loop is integrated sample by sample and a two-second note costs a few seconds. The worker therefore runs each stage as a separate numbl execution and keys its caches by tier: bore, hole, and window parameters invalidate the per-note basis (the transmission-line solve and modal fit); the breath, the jet, and the gesture re-run only the jet integration; the listener position and the direct gain re-run only the radiation, which is a matrix multiply; output conditioning is always re-run. Intermediate tensors stay inside the worker as numbl RuntimeValues. Nothing renders on its own: the Render preview button runs a fast pass (oversample 1), Render full runs the full-quality pass, and either can be cancelled while it runs. The status shows the stage, a progress bar and the elapsed time. While the current result is a preview, the Play and Download buttons and the status badge say so.
The New seed button redraws the breath turbulence without touching any other parameter. The Loop button repeats the last render continuously, swapping in each new render as it completes. Parameters persist in localStorage; "Reset to defaults" restores the values in src/params.ts (which ../matlab/recorder_defaults.m mirrors).
Reading and editing the code in the app#
The 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 takes effect on the next render, so changing the physics in the editor is heard and plotted the same way moving a slider is. Doubling jet_conv's effect by halving the transit delay in recorder_jet.m, for instance, audibly sharpens every note and then flips the register.
Edits 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 failed render leaves nothing playable, so Play and Download go dead until a render succeeds, and reverting recovers.
Layout#
../matlab/— the algorithm:recorder_geometry.m,recorder_bore.m,recorder_basis.m,recorder_notes.m,recorder_jet.m,recorder_radiate.m,recorder_output.m,recorder_rng.m, plusrecorder_render.m/recorder_demo.m/recorder_validate.mfor CLI usesrc/params.ts— parameter schema, defaults, tier assignment, persistencesrc/mfiles.ts— the.msources, bundled at build time (src/mfilesCore.tsholds the hash used for cache keying)src/worker/renderWorker.ts— off-thread numbl execution with tiered cachingsrc/lib/instrument.ts— the hole lattice and fingering chart, mirrored fromrecorder_geometry.mfor the 3D scenesrc/components/— 3D scene (three.js), parameter panel, code panel and editor, plots (jet flow, waveform, spectrum, spectrogram, bore admittance, standing waves)src/selftest.ts— Node harness for the MATLAB validation script
A note on the seeded generator#
The turbulence noise draws from a 32-bit LCG in recorder_rng.m rather than MATLAB's rand, so a given seed produces the identical breath in the numbl CLI, in Node, and in the browser. That is what lets the self-test require two renders to be bit-identical.