/ simulated_instruments / maraca
Sign in
simulated_instruments / maraca
maraca
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
matlabNote the upstream numbl fix beside the slice-orientation workaround
scriptsReplace instrument.json with exported instrument bundles
webappMaraca shake simulator: MATLAB model and interactive web app
README.mdMaraca shake simulator: MATLAB model and interactive web app

Maraca#

A physically-based simulation of a maraca shake pattern, sampled as acoustic pressure at a point in space. A few hundred grains inside a closed spherical shell produce a few thousand stochastic grain-shell impacts across the strokes of the gesture; each impact is a Hertz contact that drives the shell's spherical-harmonic modes, and the modes radiate to a listener point through the exact multipole transfer of a sphere.

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 maraca_demo.m, or maraca_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:

shake gesture -> stochastic impact process        maraca_impacts.m
  -> Hertz grain contacts on the shell wall
      -> spherical shell modes (two membrane branches + bending)   maraca_basis.m
          -> exact sphere multipole radiation      maraca_radiate.m
              -> retarded free-field pressure at the listener
                  -> anti-aliased decimation -> PCM     maraca_output.m

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

The grains are not tracked individually. A stroke is a draw from a point process: a Poisson count of impacts whose mean is grain_count * bounces_per_grain, arrival times from a Gamma(3) burst after the hand reverses, Rayleigh-distributed impact speeds, and impact points scattered by a von Mises-Fisher distribution about the pole the grains are thrown at, alternating between the two poles on successive strokes. Grain sizes are lognormal. Each impact is then an independent Hertz contact, so the contact time falls as v^(-1/5) and harder or faster grains excite the shell higher up: timbre varies with dynamics on its own, rather than through a scripted velocity layer. What this buys is speed, a full pattern renders in well under a second, and reproducibility from a seed. What it gives up is any coupling between grains, so a shell packed tight enough for the grains to jam is outside the model.

The shell modes come from membrane theory of a complete sphere, with a bending correction. Each degree l gives two branches from a quadratic in Omega^2; the l = 0 upper root is the breathing mode, and the l = 1 lower root is exactly zero, being rigid translation, which the hand holds. The lower branch flattens toward Omega^2 = 1 - nu^2 at high l, so a flat-plate bending term is added to it, which is what spreads the high-degree modes upward. The interior air enters as a stiffness on the breathing mode alone, since modes with l >= 1 displace no net volume.

Radiation uses the exact multipole transfer of a sphere, -i rho0 c h_l(kr) / h_l'(ka), evaluated at each mode's centre frequency. Because every m of a degree shares one frequency and one transfer, the spherical-harmonic addition theorem collapses the m sum to a Legendre polynomial in the angle between the impact point and the listener, so one damped resonator per retained mode carries the whole degree. Modal truncation then discards the dense high-degree modes whose collective off-resonance response is the immediate forced motion of the wall around the contact, and that broadband part is reinstated separately as a point-driven-plate term. That last term is the least principled piece of the model, and direct_gain turns it off.

Validation#

matlab/maraca_validate.m (also npm run selftest in webapp/) checks the spherical Hankel functions against their closed forms and against finite differences, the breathing mode against its closed form, the Hertz v^(-1/5) contact-time scaling, the far-field -6 dB per doubling of distance, exact reproducibility of the seeded impact process, and pinned end-to-end diagnostics.

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

Defaults#

A 3.4 cm gourd shell, 320 grains at 1.3 bounces each, eight strokes at 4.5 Hz with an accent on every second stroke. That yields 3628 impacts with a 39 µs mean Hertz contact, 11 retained shell modes from 6446 Hz up, a breathing mode at 14751 Hz, and a peak of 14.9 Pa (117 dB SPL) at the 42 cm listener before normalization.

↑↓ move↵openescclose