/ simulated_instruments / bass
Sign in
simulated_instruments / bass
bass / README.md
50 lines · 7.4 KBCodeBlameHistory
3A physically-based simulation of a pizzicato standup bass (double bass), playing a single note or a seeded walking line, sampled as acoustic pressure at a point in space. Each note is a stiff, lossy string two-way coupled at the bridge to a modal model of the corpus; the pluck is a released initial condition, so between events nothing is integrated, and the coupled modes radiate to a listener point through the f-hole Helmholtz monopole and the top-plate dipoles.
5There is no microphone model: no directivity, no capsule response, no gain staging. The output is p(r, t) in pascals.
7## Contents
9| Path | What it is |
10|---|---|
11| `matlab/` | The algorithm in MATLAB syntax, run by [numbl](https://numbl.org) |
afd5157Add instrument/: the bass behind the track-render interfaceJeremy Magland 12| `instrument/` | The model behind the track-render instrument interface used by [midiboot](../../proof-of-concept/midiboot) and by the web app (worker, note mapping, caching; `npm test` there runs the model in node) |
0e92d35Pizzicato standup bass: coupled string-body modal model, numbl MATLAB reference, and interactive web appJeremy Magland 13| `webapp/` | Interactive browser app that executes the MATLAB code client-side |
15The MATLAB code runs from the command line with `cd matlab && numbl run bass_demo.m`, or `bass_validate.m` to run the checks. The web app is a Vite project; see `webapp/README.md`.
17## The model
19The signal chain, with each stage in its own `.m` file:
21```
22single note or seeded walking line bass_notes.m
23 -> body mode table seen at the bridge bass_body.m
24 -> string + body assembled per fingering bass_assemble.m
25 -> one coupled eigensolve per fingering bass_basis.m
26pluck -> initial condition on the coupled modes
27 -> closed-form damped exponentials, choked at
28 the next note
29 -> f-hole monopoles + top-plate dipoles bass_radiate.m
30 -> free-field pressure at the listener
31 -> anti-aliased decimation -> PCM bass_output.m
32```
34Three points are worth stating plainly, because they are where the model commits itself.
f456a3bVoice the defaults deeper and darkerJeremy Magland 36**The string and the body are diagonalized together.** Each fingering (a string and a stop) is one symmetric generalized eigenproblem: the mass-normalized modes of the stiff pinned-pinned string, the modal body, and a bridge-ramp shape that lets the string's end move with the bridge. With that ansatz the elastic coupling integrals vanish and the whole coupling sits in the mass matrix, so the problem reduces through the Cholesky factor of M and is diagonalized by svd, which keeps near-degenerate coupled pairs orthonormal where a nonsymmetric eigensolver does not. The coupling is two-way: the body pulls the open A fundamental about 22 cents flat at the defaults, and a note whose fundamental lands on a strong body resonance comes out as a split, fast-beating pair, which is the wolf-note mechanism, checked in the validation by its square-root-of-mobility splitting. The body itself is the empirical part: a table of twelve measured-plausible resonances (A0 near 60 Hz, the main wood modes from 97 Hz up) with effective bridge masses, Qs, and radiating areas, not a plate solve; above the table the dense modal region enters only as a resistive residual admittance. The bending stiffness of the wound string comes from a single inharmonicity parameter and stretches the high partials sharp, more so on stopped notes, as it should.
38**The pluck is an initial condition, not a force history.** Releasing a string pulled with force F leaves the classic triangular shape, whose projection through the coupled shapes gives every mode an initial amplitude, rolled off by a first-order finite-release filter standing in for the fingertip (the difference between a fleshy jazz pizzicato and a hard snap). From there every coupled mode is a damped exponential, so a note renders in closed form, faster than real time, unlike the sibling `recorder` repository whose oscillation must be integrated sample by sample. When the next note of a walking line lands, the previous note is choked with an extra decay standing in for the player stopping the string. What this gives up is the contact detail: there is no fingerboard collision, so the rattle of a very hard pluck is outside the model, and notes are otherwise independent, which is wrong for true legato slides. Damping is assigned in the uncoupled coordinates and projected: string internal loss, a finger termination on stopped notes (open strings ring longer, as on the instrument), the residual bridge conductance (a resistive end gives a constant decay rate, so the high partials die in under a second while the fundamental rings for ten), and the body Qs.
40**Radiation is a monopole pair plus dipoles, and one confessed shortcut.** A0 is the one body mode that moves net volume through the f-holes, and it radiates as a monopole split between the two f-hole points with their own retardations; every corpus mode displaces the top plate against the air with no net flow and radiates as a point dipole along the plate normal, near-field term kept, so walking behind the instrument flips their phase. The string itself is acoustically invisible. Above the retained body table, the bridge force acting on the resistive residual admittance moves the plate broadband, and that motion radiates as a small dipole: this is how the high partials reach the listener at all, it is the least principled term of the model, and `direct_gain` turns it off.
42## Validation
44`matlab/bass_validate.m` (also `npm run selftest` in `webapp/`) checks the decoupled solve against the closed-form stiff string (including the inharmonic stretch), the mass-orthonormality and stiffness-diagonalization identities of the coupled eigensolve, that a midpoint pluck silences the even partials end to end, that tuning a body mode onto a string fundamental splits it with the square-root-of-mobility scaling of a genuine two-way coupling, the far-field -6 dB per doubling of distance, exact reproducibility of the seeded walking line, and pinned end-to-end diagnostics.
46As in the sibling `maraca`, `triangle`, and `recorder` 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.
48## Defaults
f456a3bVoice the defaults deeper and darkerJeremy Magland 50A 3/4-size bass with a 1.04 m scale, strung G2 D2 A1 E1 at 285 to 310 N (the linear densities follow from tension and pitch, about 7 g/m for the G and 42 g/m for the E). The default gesture is a single open A1 plucked with 15 N at 38% of the string from the bridge, released over 2.6 ms: 61 coupled modes (49 string, 12 body), a fundamental at 54.32 Hz that the body holds about 22 cents flat of A1, a T60 of 8.0 s on that fundamental, and a peak of 0.218 Pa (80.8 dB SPL) at the 133 cm listener before normalization. The defaults are voiced dark and deep: the pluck sits near the end of the fingerboard with a fleshy release, A0 at 58.5 Hz sits just above the open-A fundamental so the f-holes carry it (the trade-off being that the body drains the string faster; moving `a0_hz` back to 60 buys about two seconds of T60 at the cost of low-end weight), the corpus dipoles are long (`dipole_length_m` 0.45), the body Qs run 20% hot, and the broadband direct term is pulled back to 0.7. Together with the deliberately bottom-heavy radiativity of the body table (A0 and the 97 and 113 Hz wood modes carry most of the output), this keeps over 90% of the radiated energy below 80 Hz. Flattening `rad_area_scale` against a lower `dipole_length_m`, or raising `direct_gain`, will thin and brighten it back out. Setting `note_count` to 8 walks a seeded quarter-note line in A major at 96 bpm, each note choked as the next lands.
↑↓ move↵openescclose