/ simulated_instruments / recorder
Sign in
simulated_instruments / recorder
recorder
Go to fileHistoryFork
.githubRecorder simulator: MATLAB jet-drive model and interactive web app
export-srcReplace instrument.json with exported instrument bundles
exportsReplace instrument.json with exported instrument bundles
matlabRecorder simulator: MATLAB jet-drive model and interactive web app
scriptsReplace instrument.json with exported instrument bundles
webappRecorder simulator: MATLAB jet-drive model and interactive web app
README.mdRecorder simulator: MATLAB jet-drive model and interactive web app

Recorder#

A physically-based simulation of a soprano recorder playing one to three tongued notes, sampled as acoustic pressure at a point in space. The instrument is a tapered bore with eight tone holes under a baroque fingering chart; an air jet from the windway is displaced by the acoustic flow through the window, amplified along its travel to the labium, and the flow it injects drives the bore resonances that displace it, so the tone is a self-sustained oscillation of the loop rather than anything prescribed. The bore is solved as a lossy transmission line, and every open aperture radiates 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 recorder_demo.m, or recorder_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:

fingering -> tone hole lattice on the tapered bore     recorder_geometry.m
  -> lossy transmission line -> input admittance       recorder_bore.m
      -> modal fit of the resonances                   recorder_basis.m
notes of the gesture                                   recorder_notes.m
  -> breath envelope -> jet / bore loop, integrated
     sample by sample (the sound happens here)         recorder_jet.m
      -> coherent sum of aperture monopoles            recorder_radiate.m
          -> free-field pressure at the listener
              -> anti-aliased decimation -> PCM        recorder_output.m

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

The passive instrument is a transmission line with real tone holes. The bore is a chain of short lossy cylinders (viscothermal wall losses through the standard wide-tube propagation constant, a linear taper standing in for the maker's reaming), each open hole a shunt air mass with end corrections and radiation resistance, each closed hole a shunt compliance, the foot an unflanged open end, and the window an air mass in series. What the jet sees is the admittance of that loop, refitted as a handful of second-order resonators whenever the fingering changes (recorder_basis.m). The hole positions and sizes were tuned numerically so that the ten first-register fingerings of the baroque chart land on an equal-tempered C major scale within a cent of passive resonance; the pinched thumb hole is modeled as a small open hole with the viscous resistance of a narrow crack, which is what damps the fundamental and lets the second register speak. Cross-fingerings (the F5 fork) and the pinched fingerings inherit every simplification here, and the forked F6 plays some 40 cents flat, which is honestly the sort of note it is on the instrument.

The oscillation is emergent, not prescribed. recorder_jet.m integrates the jet-drive loop of Verge and Fabre sample by sample at the oversampled rate: breath pressure sets the jet velocity through Bernoulli, the acoustic flow through the window deflects the jet, the deflection convects to the labium (a delay that varies with breath), grows exponentially, and is low-passed at the jet's own instability cutoff; the flow split at the labium saturates as a tanh, and its injection drives the modal resonators through the jet-drive pressure source, closed by a nonlinear vortex loss. Nothing in the loop says which note to play. The note grows out of seeded turbulence noise where the loop gain crosses one, locks near the bore resonance whose quarter-period matches the jet transit time, sharpens as the breath rises, and overblows to the octave when the transit time leaves the fundamental's range; the validation script checks that register selection and overblowing actually emerge rather than assuming they do. The trade-off is that this stage cannot be closed-form, so a two-second note takes a few seconds to render where the sibling instruments render faster than real time. Two stated limitations: notes are rendered independently and summed, which is right for tongued articulation and wrong for legato, and there is no player, so nothing listens to the pitch and corrects the breath (the breath_track parameter stands in for the player's habit of blowing higher notes harder).

Radiation is a coherent sum of aperture monopoles. The window, every open tone hole, and the foot each carry a complex flow per unit loop flow, taken from the transmission-line solve at each mode's centre frequency, and each radiates as a point monopole with its own retardation to the listener, so the interference between apertures, and its dependence on where the listener stands, is kept. The broadband remainder, breath noise, the attack transient, and everything the modal truncation dropped, radiates from the window as the direct term; that is the least principled term of the model, and direct_gain turns it off.

Validation#

matlab/recorder_validate.m (also npm run selftest in webapp/) checks the transmission line against the closed-form open-open cylinder (with the viscothermal dispersion the closed form needs), the modal fit against the admittance it was fitted to, emergent register selection on three fingerings including the all-closed C5 (the easiest fingering to squeak), emergent overblowing at quadrupled breath pressure, exact reproducibility of the seeded turbulence, the far-field -6 dB per doubling of distance, and pinned end-to-end diagnostics.

As in the sibling maraca and triangle repositories, 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 soprano recorder in C: a 30.2 cm bore tapering from 6.7 to 5.2 mm radius, eight tone holes, a 4.5 mm cutup and a 1.0 mm flue. The default gesture is a single G5 at 668 Pa breath (350 Pa at C5, tracked up with pitch), which locks at 781.9 Hz, five cents flat of equal temperament, over twelve fitted bore resonances; the peak is 1.80 Pa (99 dB SPL) at the 44 cm listener before normalization. Setting note_count to 3 plays a tongued G5 A5 B5 figure. The full chart runs C5 to A6, the top four notes on pinched second-register fingerings that want more breath, as they do on the instrument.

↑↓ move↵openescclose