/ proof-of-concept / cymbalsim
Sign in
proof-of-concept / cymbalsim
cymbalsim
Go to fileHistoryFork
.githubcymbalsim: scripted plate and cymbal designer with a modal linear model and a von Karman GPU model
srcLoudness warning applies to the signal about to be played and returns on any change
testsLoudness warning applies to the signal about to be played and returns on any change
.gitignorecymbalsim: scripted plate and cymbal designer with a modal linear model and a von Karman GPU model
index.htmlInteractive tutorial on the physics and the mathematics of the simulator
package-lock.jsonInteractive tutorial on the physics and the mathematics of the simulator
package.jsonInteractive tutorial on the physics and the mathematics of the simulator
README.mdLoudness warning applies to the signal about to be played and returns on any change
THEORY.mdInteractive tutorial on the physics and the mathematics of the simulator
tsconfig.jsoncymbalsim: scripted plate and cymbal designer with a modal linear model and a von Karman GPU model
vite.config.tscymbalsim: scripted plate and cymbal designer with a modal linear model and a von Karman GPU model

cymbalsim#

A browser application for designing a plate or a cymbal and simulating a drumstick strike on it. The design is a short script in MATLAB syntax (material, bounding box, outline, thickness field, an optional stress-free profile, the stand, and the decay times), run in the page by numbl, that the user edits in the page or that an AI writes for them. The sound is computed in one of two ways. The linear model computes the modes of the plate once per design and synthesizes the strike from them in about a second, on the CPU, in a pool of Web Workers. The full model solves the geometrically nonlinear (von Karman) plate equations with in-plane inertia and a spatially varying thickness, stepped explicitly on the GPU through WebGPU; it takes tens of seconds per second of sound and captures the pitch glide and the wash of a hard hit. Both use the same grid, so the linear sound is the small-amplitude limit of the full one. The model and the numerical method are described in THEORY.md.

The focus is on free plates, sheets, discs and ellipses, flat by default, and on a realistic cymbal stand: a rod through the center hole with a felt pad of given radius below the cymbal and optionally another above it, which hold the cymbal level and let it bounce, rock, and (in the full model) lift off.

Running#

Requires Node 20 or later. The linear model runs in any modern browser; the full model and the 3D view need WebGPU (Chrome or Edge 113+; Firefox and Safari with WebGPU enabled).

npm install
npm run dev        # development server
npm run build      # static site in dist/
npm test           # all tests (see below)

Using the app#

The sidebar holds the run controls at its top, then the design script, the strike, listener and numerics parameters, and the mode browser. The main area shows the plate (a lit 3D height field with WebGPU, or a 2D color map; drag to orbit, wheel to zoom), the movie and playback toolbar, and the spectrogram of the listener signal.

Run mode. The select at the top chooses the linear or the full model. In linear mode the modes of the current design are computed as soon as the script compiles (a progress bar shows the slices), and Synthesize produces the sound; after the first synthesis, a change of the strike or the listener parameters re-synthesizes automatically. In full mode Start begins the GPU run; Pause halts it, Stop ends it and keeps the audio and movie, and Continue extends a finished run after the duration has been raised.

Design script. Pick a preset from the dropdown (a flat steel rectangle, a bowed rectangle, a flat bronze disc on a stand, a tapered 16 inch crash, a 20 inch ride, the same crash with a bow and a bell, a steel ellipse, a thunder sheet) and edit it freely; the script is compiled as you type (or with Ctrl+Enter) and the view shows the resulting stress-free shape. Errors are reported under the script, and a summary lists the bounding box, the material, the thickness range, the mass, the grid, the profile's peak-to-peak height and largest slope (with a warning above the shallow-shell range), the stand with its bounce frequency, and the decay times. Revert restores the selected preset.

The script fills in a struct named design. All lengths are meters. The outline, the thickness, and the profile are numbers or function handles @(x, y, r, theta) of the point relative to the center of the bounding box, its distance from the center, and its angle from the x axis; the handles are applied to whole grid matrices, so they use elementwise operators. A script is self-contained: nothing is in scope but numbl's own functions, so a script defines the shapes it needs as ordinary MATLAB functions at the bottom of the file, and the unit constants at the top.

inch = 0.0254; mm = 1e-3;
D = 16*inch; R = D/2; hole = 13*mm;
design.name = '16 inch flat crash';
design.material.E = 110e9;        % Pa
design.material.nu = 0.34;
design.material.rho = 8800;       % kg/m^3
design.bounds.width = D;          % bounding box [m]
design.bounds.height = D;
design.inside = @(x, y, r, theta) (r <= R) & (r >= hole/2);   % omit for the whole box
design.thickness = @(x, y, r, theta) 1.2*mm + (0.7*mm - 1.2*mm) * ramp(40*mm, R, r);
design.profile = 0;               % optional stress-free height [m]; a handle for a bow or a bell
design.stand.pad.innerRadius = 7*mm;    % the felt annulus
design.stand.pad.outerRadius = 20*mm;
design.stand.pad.thickness = 12*mm;
design.stand.pad.E = 0.5e6;             % Pa, felt in compression
design.stand.pad.lossFactor = 0.3;
design.stand.topPad = true;             % false for a bottom pad only
design.stand.preload = 1*mm;            % compression of the top pad by the wing nut
design.loss.T60 = [100 5; 1000 3.5; 5000 1.2; 15000 0.4];   % decay times [Hz s], or a number, or @(f)

function v = ramp(a, b, x)
v = min(max((x - a) ./ (b - a), 0), 1);
end

design.stand = [] (or no stand field) gives a free plate; design.stand.x and .y move the rod. Pad fields that are omitted take the defaults shown. design.loss.T60 is a decay time in seconds (a number), a table of [frequency T60] rows interpolated in log-log coordinates, or a function handle of the frequency in hertz; the default is 4 s at low frequencies falling to about 1 s at 5 kHz.

Modes. In linear mode the mode browser lists every mode up to the cutoff frequency with its decay time; click a row to draw the mode's shape on the plate (scaled to 1 mm), and filter the list by frequency. On a stand the first modes are the rock and the bounce of the cymbal on the felt. The modal frequencies are also drawn as ticks on the left edge of the spectrogram, in either run mode, so that the partials of a full run can be compared with the modes.

Strike and listener. The strike point is given as fractions of the bounding box (the yellow ring in the view), with the stick speed, its effective mass, the Hertz contact stiffness and exponent, and the tip radius. The listener distance, its angle from the plate's normal, and its azimuth can be changed at any time; the microphone in the view moves with them, drawn nearer than the listener is so that both fit in the frame.

Numerics. The grid spacing (8 mm by default), the cutoff frequency of the modes, the lift-off of the pads in the full model, the sample rate, and the frame rate. The Notes under the group report the grid, the frequencies at which grid dispersion lowers the partials by 3 and 10 percent, the count and time of the modes, the time step and step count of the full model, and the decay times of the fitted two-parameter loss next to the requested ones.

Playback. After a run, drag the time slider to explore the movie; Play starts the audio from the slider position and moves the movie and the spectrogram cursor with it; Design returns to the stress-free shape and, in full mode, Live to the running view. The audio can be played or downloaded as a 16-bit WAV at any time; Absolute level maps 1 Pa to full scale shifted by the gain slider (the peak level in dBFS is shown with a clipping warning), and the two normalized modes scale each run to its loudness or its peak. When the signal to be played is loud enough to hurt in headphones (a peak within 10 dB of full scale, or a short-term level of -20 dBFS RMS over some 50 ms window, which every loudness-normalized hit reaches), Play first shows a warning to turn the volume down, with a Play anyway button. Once accepted, the same signal replays without a prompt; a new recording, another level mode or a change of the gain brings the warning back.

Tutorial. The Tutorial button at the top of the sidebar opens a panel beside the view with a guided tour of the physics and the mathematics of the simulator in nineteen pages: the bending stiffness and the plate equation, dispersion, modes and their scaling laws, the density of modes, the grid and its dispersion error, the eigenvalue computation, decay times, the strike and the driving-point impedance, the modal force and the choice of the strike point, the modal synthesis, radiation and directivity, the spectrogram, the stand, the taper, curvature, the nonlinear model, and the limitations. Each page reads the design in the editor and shows its numbers (the bending stiffness, the wavelength at 1 kHz, the count of modes against Weyl's estimate, the gains of the first modes at the strike point and at the listener, the bounce and the rock of a stand against their formulas), and the buttons under Try it load presets, change parameters, compute the modes, strike and play, so that every formula can be checked against the model; a few pages remember mode lists for a comparison, such as the plate twice as thick against the reference. The panel keeps its page between visits, and ?tutorial=1 or ?tutorial=<page id> opens it from the address.

URL parameters. ?preset=crash-16 selects a preset, mode=full the run mode, and any strike, listener, or numerics parameter can be preset in SI units, for example ?preset=ride-20&spacing=0.006&duration=3&autostart=1; nogpu=1 disables WebGPU.

Tests#

npm test runs seven groups of tests with Node's built-in runner; npm run test:cpu runs the first six without a browser.

  • tests/design.test.ts: the design script (compilation, the checks with their messages, hoisted functions, the mask of a disc with a hole, the orientation of the sampled matrices, the removal of the mean plane, the stand's rates and preloads, the three forms of the decay times, the fit of the two-parameter loss), and that every preset evaluates on the default grid.
  • tests/reference.test.ts: the float64 reference of the discretization (rigid modes of the stiffness, forces as the gradient of the energy, dissipativity of the losses and the pad dashpots, thickness scaling, the stand's stiffness against translation and tilt, the lift-off rule).
  • tests/assemble.test.ts: the assembled stiffness equals the linearized reference force on a flat plate and on a bowed, tapered, holed plate on a stand.
  • tests/modal.test.ts: the banded factorization and its inertia count, the tridiagonal eigensolver, all modes of a small square and a small disc against a dense solve (degenerate pairs included), the free square and disc frequencies against Gorman's and Leissa's values, and a plate on a stand.
  • tests/synth.test.ts: a single mode under the stick follows the closed-form damped sinusoid, the radiation gain of a piston equals the analytic Rayleigh sum, the attack and the tail join, the parts computed by the workers sum to the whole.
  • tests/tutorial.test.ts: every page of the tutorial renders with no design, with a plate, and with its modes, without undefined values and with balanced math delimiters; the actions run against an in-process host; the scaling pages' comparisons give the predicted ratios (2 for twice the thickness, 4 for half the size on a grid of the same cell count) to rounding.
  • tests/gpu.test.ts: serves the app with Vite and drives a headless Chromium through Playwright: the in-page self-test of the WGSL kernel against the float64 reference on five presets (?selftest=1 shows the same numbers in the browser), the linear model's modes and synthesis, the tutorial panel with its typeset mathematics and an action that computes the modes, an error in a script, short full runs on a free plate and on a stand, and a light hit simulated both ways, whose spectra must agree. It needs Playwright's Chromium (npx playwright install chromium) and a machine whose Chromium has WebGPU.

Layout#

  • src/params.ts: simulation parameters, grid and mask, the plate as evaluated on the grid, material functions, the stand's rates, patches, listener geometry, grid dispersion.
  • src/design.ts, src/mruntime.ts, src/presets.ts, src/loss.ts: running the script with numbl, the checks on the design struct, evaluation on the grid, the preset scripts, and the decay times.
  • src/model/reference.ts: float64 reference of the discretization (forces, losses, energy, the time-step estimate).
  • src/model/assemble.ts: the linearized stiffness in banded storage and the lumped mass.
  • src/modal/banded.ts, lanczos.ts, modes.ts: the banded factorization with its inertia count, shift-invert Lanczos on one slice, and the sliced computation of all modes through a job runner.
  • src/modal/synth.ts, worker.ts, pool.ts: the modal synthesis (contact, radiation gains, attack table, tail, movie), the worker, and the pool that runs the eigenvalue jobs and splits the synthesis between the workers.
  • src/sim/shaders/, src/sim/simulation.ts, src/sim/selftest.ts: the WGSL kernels (step, listener sum, extraction), the WebGPU driver, and the in-browser comparison with the reference.
  • src/render/: 2D and 3D rendering of the plate with the markers of the strike, the stand, and the listener, with a 2D canvas fallback.
  • src/audio/: FFT, spectrogram, WAV encoder, playback, level mapping.
  • src/ui/: the script editor, the parameter form, the mode browser; src/main.ts wires it all together.
  • src/tutorial/steps.ts, tutorial.ts: the pages of the tutorial (text with TeX mathematics, live numbers from the application's state, and actions on a small host interface that main.ts implements), and the panel that shows them with KaTeX.
↑↓ move↵openescclose