/ 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
srcModel the felt as a Maxwell chain so that the full model has the loss factor of the linear one
testsModel the felt as a Maxwell chain so that the full model has the loss factor of the linear one
.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.jsonModel the felt as a Maxwell chain so that the full model has the loss factor of the linear one
README.mdModel the felt as a Maxwell chain so that the full model has the loss factor of the linear one
THEORY.mdModel the felt as a Maxwell chain so that the full model has the loss factor of the linear one
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 with its loss constants, bounding box, outline, thickness field, an optional stress-free profile), 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. The stand is a setting of the setup rather than of the design, so any plate can be put on it or taken off it.

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, stand 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, an aluminum square, a bowed rectangle, a flat bronze disc, 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), whether it sits on the stand, the loss constants with the coincidence frequency, and the estimated decay. 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.material.lossFactor = 2e-4;      % internal friction of the metal (hysteretic loss factor)
design.material.alpha = 18e-6;          % thermal expansion [1/K]      } thermal constants, for the
design.material.cp = 370;               % specific heat [J/(kg K)]     } thermoelastic damping
design.material.k = 40;                 % conductivity [W/(m K)]       }
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

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

The decay of the sound is not prescribed but derived: from the loss factor of the metal's internal friction (lossFactor, 0 when omitted), the thermoelastic damping that follows from the three thermal constants (all three or none; without them there is no thermoelastic loss) and design.temperature (293 K by default), the radiation of each mode into the air, which the linear model computes from the mode's shape, and the felt of the stand. A script that still sets design.loss or design.stand is refused with a message saying so.

Modes. In linear mode the mode browser lists every mode up to the cutoff frequency with its decay time and, beside it, the decay time each mechanism alone would give: the air (radiation), the metal (internal friction), thermal (thermoelastic) and the felt of a stand; 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.

Stand. The Stand group puts the plate on the stand or takes it off (a free plate), and sets the felt annulus (inner and outer radius, thickness, Young's modulus in compression, loss factor), whether there is a top pad under the wing nut, its preload, and the position of the rod relative to the center of the bounding box. The pads act on the plate nodes within the annulus whether or not the outline has a hole there; the Notes under the group give the total stiffness, the bounce frequency of the rigid plate, the number of pad nodes, the preloads, and the fraction of the stiffness the felt keeps at rest. The felt is viscoelastic, a chain of relaxation times that gives it the same loss factor at every frequency, so the modulus is the value at the bounce frequency and rises slowly above it; both models use the same chain (THEORY.md, Section 3). The stand is on by default, so a preset without a hole, such as the flat rectangle, sits on it until the box is unticked.

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 of the full model next to the estimate it was fitted to.

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, stand, or numerics parameter can be preset in SI units (booleans as 0 or 1), for example ?preset=ride-20&spacing=0.006&duration=3&autostart=1 or ?preset=flat-rectangle&standOn=0; nogpu=1 disables WebGPU.

Tests#

npm test runs eight groups of tests with Node's built-in runner; npm run test:cpu runs the first seven 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 from the parameters with its rates and preloads, the loss constants of the material), 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, the work per cycle of the felt's chain, 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/loss.test.ts: Zener's thermoelastic loss factor peaks where it should, the outline perimeter of a rectangle and a disc, the limits of the average radiation efficiency, the two-parameter fit, the radiation rate of a piston against the closed form and the FFT convolution against the direct double sum for every mode, and the thermoelastic rate of a uniform plate.
  • tests/felt.test.ts: the felt's chain (flat loss factor across the band, storage of one at the reference, the exact step of an arm against a ramp and a sinusoid).
  • 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, the loudness warning before playback, an error in a script, short full runs on a free plate and on a stand, the absence of a high-frequency noise floor in a curved design's full run, 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 (strike, listener, stand, numerics), grid and mask, the plate as evaluated on the grid, material functions, the stand from the parameters and its rates, patches, listener geometry, grid dispersion.
  • src/design.ts, src/mruntime.ts, src/presets.ts: running the script with numbl, the checks on the design struct, evaluation on the grid, and the preset scripts.
  • src/loss.ts: the losses from physical constants (internal friction, Zener's thermoelastic damping, the average radiation efficiency, the outline perimeter) and the two-parameter fit of the full model.
  • 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/damping.ts: the per-mode radiation rate from the mode shape by FFT convolution of the radiation resistance, and the per-mode thermoelastic rate weighted by the local bending energy.
  • 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, the per-mode losses, and the synthesis split 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