/ simulated_instruments / triangle
Sign in
simulated_instruments / triangle
triangle
Go to fileHistoryFork
.githubKeep the model validation out of the deploy workflow
export-srcPitch support by geometric scaling in the exported driver
exportsPitch support by geometric scaling in the exported driver
matlabDefault to a twelve-strike roll
scriptsReplace instrument.json with exported instrument bundles
webappExplicit preview and full renders, as in the cymbal2 app
README.mdDefault to a twelve-strike roll

Triangle#

A physically-based simulation of an orchestral triangle, struck or rolled, sampled as acoustic pressure at a point in space. The instrument is a steel rod bent into an open equilateral triangle; Hertz beater contacts excite the bending, stretching, and torsional modes of the bent free-free rod, computed by a small finite element eigensolve, and each element of the rod radiates as a point dipole to a listener point.

There is no microphone model: no directivity, no capsule response, no gain staging. The output is p(r, t) in pascals.

Contents#

Path What it is
matlab/ The algorithm in MATLAB syntax, run by numbl
webapp/ Interactive browser app that executes the MATLAB code client-side

The MATLAB code runs from the command line with cd matlab && numbl run triangle_demo.m, or triangle_validate.m to run the checks. The web app is a Vite project; see webapp/README.md.

The model#

The signal chain, with each stage in its own .m file:

strike or roll gesture -> Hertz beater contacts     triangle_strikes.m
  -> bending / stretching / torsional modes of the
     open bent rod (finite elements + eigensolve)   triangle_basis.m
      -> coherent sum of element dipoles            triangle_radiate.m
          -> free-field pressure at the listener
              -> anti-aliased decimation -> PCM     triangle_output.m

Three points are worth stating plainly, because they are where the model commits itself.

The modes come from an honest discretization of the bent geometry. The rod centerline is an open polyline (two closed corners, a gap at the third), discretized into about sixty Euler-Bernoulli beam elements with consistent mass and rotary inertia (triangle_fem.m). Because the geometry is planar, the six degrees of freedom per node decouple exactly into two families: in-plane bending plus stretching, and out-of-plane bending plus torsion. The bends are where the families earn their keep: a corner converts out-of-plane bending in one leg into torsion in the next, and it is this conversion, not any hand-tuned inharmonicity, that produces the dense non-overlapping cluster of partials a triangle is heard as. Each family is a generalized eigenproblem, reduced through the Cholesky factor of the mass matrix and diagonalized by singular value decomposition (the reduced matrix is symmetric positive semidefinite, and svd keeps the near-degenerate pairs of this geometry orthonormal where a nonsymmetric eigensolver does not). Shear deformation is neglected, which leaves the modes near 20 kHz a few percent sharp for an 8 mm rod. The rod is fully free-free: the suspension loop is taken as massless and lossless, so the lowest modes ring longer here than on a hand-held instrument, where the loop and the hand take their energy.

The strike is an event, not a simulation. Each beater contact is a Hertz half-sine force pulse whose contact time couples the beater mass to an estimate of the rod mass engaged during the contact, closed by a short fixed-point iteration (triangle_strikes.m). What this buys is closed-form synthesis: between strikes every mode is a damped exponential, nothing is integrated, and a five-second ring renders in about two seconds. What it gives up is the millisecond-scale detail of the contact itself; unlike the sibling woodblock repository, which integrates its mallet contact, the force pulse here is prescribed. A roll is a seeded point process of such events alternating between two contact points, reproducible from its seed.

Radiation is a coherent sum of element dipoles. A thin rod moving transversally radiates as a line of point dipoles (dipole volume twice the displaced volume, the added mass coefficient of a cylinder). Per mode, the element dipoles are summed with their relative retardations at the mode's centre frequency, so the interference across the frame, and its dependence on where the listener stands, is kept. Radiation damping is estimated from the incoherent sum of element dipole powers, fair above the rod's coincidence frequency (about 1.9 kHz here) and an overestimate below it. Modes above max_mode_hz are truncated, and their collective off-resonance response is reinstated as a broadband click from the driving-point impedance of an infinite beam; that is the least principled term of the model, and direct_gain turns it off.

Validation#

matlab/triangle_validate.m (also npm run selftest in webapp/) checks the element routines against the closed-form free-free bar (bending, axial, and torsional frequencies, and the rigid-body count), the mass-orthonormality and stiffness-diagonalization identities of the eigensolve, the Hertz v^(-1/5) contact-time scaling in the light-beater limit, the far-field -6 dB per doubling of distance along a fixed ray, exact reproducibility of the seeded roll, and pinned end-to-end diagnostics.

As in the sibling maraca repository, there is no separate Python reference implementation: the MATLAB code is the reference, and the pinned numbers were recorded from it when the model was built.

Defaults#

A 15.2 cm (6 inch) steel triangle of 8 mm rod with a 15 mm corner opening, rolled with a 22 g steel beater at 1.5 m/s and 20 degrees off the plane: twelve hits at 12 Hz alternating between the middle of the base and the right leg, with 6 ms of timing jitter and 15% velocity spread. That yields 40 modes between 164 Hz and 21.6 kHz (19 in-plane, 21 out-of-plane), a 57 µs mean contact, and a peak of 13.8 Pa (117 dB SPL) at the 45 cm listener before normalization, ringing through a five-second tail. Setting strike_count to 1 gives a single, fully deterministic strike, which is the configuration the physics is easiest to hear in.

↑↓ move↵openescclose