# Standup Bass 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](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. The signal chain: a single pizzicato note or a seeded walking line (`Notes` above 1), one coupled string-body eigensolve per fingering, a pluck released as an initial condition on the coupled modes, closed-form damped-exponential synthesis with each note choked as the next lands, radiation through the f-hole monopoles and top-plate dipoles, 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/bass_validate.m through numbl in Node ``` The 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 bass_demo.m` (or `bass_validate.m`). ## Why it renders fast Nothing is integrated. A pluck is an initial condition on the coupled modes, so the synthesis evaluates `exp(lambda t)` over each note's span in closed form (chunked only to bound memory) and applies the choke as a change of pole when the next note lands. The cost that remains is one eigensolve per fingering of the gesture, a fraction of a second each, and that is exactly the piece the worker caches: plucking harder, moving the pluck point, changing the tempo, or moving the microphone reuses the cached modes. ## 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. Zeroing the mass-coupling term `gk` in `bass_assemble.m`, for instance, decouples the string from the body and leaves the note quiet, harmonic, and endless. 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. ## How rendering is tiered 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 worker runs each stage as a separate numbl execution and keys its caches by tier: the strings, the body, the damping, and the gesture's pitches invalidate the coupled eigensolves; the pluck, the tempo, and the listener only re-run the projection, the synthesis, 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 eigensolves do not depend on the synthesis rate, previews reuse the full-quality mode basis. The New seed button redraws the walking line without touching any other parameter (a single note is deterministic, so the button is disabled there). 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/bass_defaults.m` mirrors). ## Layout - `../matlab/` — the algorithm: `bass_notes.m`, `bass_body.m`, `bass_assemble.m`, `bass_basis.m`, `bass_radiate.m`, `bass_output.m`, `bass_rng.m`, plus `bass_render.m` / `bass_demo.m` / `bass_validate.m` for CLI use - `src/params.ts` — parameter schema, defaults, tier assignment, persistence - `src/mfiles.ts` — the `.m` sources, bundled at build time and hashed for cache keying - `src/worker/renderWorker.ts` — off-thread numbl execution with tiered caching - `src/components/` — 3D scene (three.js), parameter panel, code panel and editor, plots (notes, waveform, spectrum, spectrogram, bridge admittance, coupled-mode map) - `src/selftest.ts` — Node harness for the MATLAB validation script ## A note on the seeded generator The walking line draws from a 32-bit LCG in `bass_rng.m` rather than MATLAB's `rand`, so a given seed produces the identical line in the numbl CLI, in Node, and in the browser. That is what lets the self-test pin exact onset times and pluck forces.