/ proof-of-concept / cymbalsim
Sign in
proof-of-concept / cymbalsim
cymbalsim / README.md
15.6 KBPreviewCodeBlameHistoryRaw

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.

Layout#

↑↓ move↵openescclose