A rectangular stick of rosewood is not a note. Struck, it rings at
1 : 2.7565 : 5.4039 : 8.9330
which is a fundamental, something a minor seventh and a half above it, something a fifth above that, and so on up — an interval set with no name, because nothing in music wanted it. A marimba bar rings at 1 : 4 : 10: two octaves, then two octaves and a major third. The difference between those two lists is a hollow cut in the underside of the bar, and putting it there is a craft.
The mechanic is that cut. You have a router, a blank, and one rule: wood only goes one way.
Where the numbers come from
A bar lying on cords is a free-free beam, so its partials are the roots of
cos(βL)·cosh(βL) = 1 βL = 4.73004, 7.85320, 10.99561, 14.13717, …
and ω ∝ (βL)², which is where 2.7565 comes from — it is (7.85320/4.73004)², and
it is a fact about rectangles rather than about wood. The demo does not use that
formula. It assembles the beam out of 24 Hermite cubic elements with a thickness
that varies element by element, and solves K φ = ω² M φ for real, because the
moment you cut an arch into the bar there is no closed form left. The closed form
is what the solver is checked against:
| elements | DOF | partial 1 ¢ | partial 2 ¢ | partial 3 ¢ | partial 4 ¢ | ms/solve |
|---|---|---|---|---|---|---|
| 12 | 26 | 0.0284 | 0.2112 | 0.7916 | 2.0991 | 1.29 |
| 16 | 34 | 0.0091 | 0.0681 | 0.2582 | 0.6942 | 6.39 |
| 20 | 42 | 0.0037 | 0.0281 | 0.1072 | 0.2900 | 3.23 |
| 24 | 50 | 0.0018 | 0.0136 | 0.0521 | 0.1414 | 5.08 |
| 32 | 66 | 0.0006 | 0.0043 | 0.0166 | 0.0452 | 12.48 |
| 48 | 98 | 0.0001 | 0.0009 | 0.0033 | 0.0090 | 57.77 |
Errors in cents, against the transcendental roots. The shipped mesh is wrong by a seventh of a cent on a partial you can barely hear, and solves in five milliseconds — which is the whole reason the meters can follow the router instead of updating when you let go.
The one line the mechanic is made of
Rayleigh’s quotient, differentiated. For a mode with eigenvalue λ and shape φ
normalised so φᵀMφ = 1, element e holds strain energy Uₑ = φᵀKₑφ and kinetic
Tₑ = φᵀMₑφ. Stiffness goes as thickness cubed and mass goes as thickness
linearly, so
dλ/dhₑ = (3Uₑ − λTₑ)/hₑ df/f = dλ/2λ
Read that as a sentence: removing wood lowers a partial where that partial stores strain, and raises it where that partial only carries mass. Both are properties of the mode shape. Different partials have different mode shapes. So one cut, at one place, moves every partial by a different amount and sometimes in a different direction — and that is not a quirk of bars, it is the entire reason tuned percussion is possible at all.
It also costs nothing to compute. The eigenvectors are already in hand, so the demo draws the sensitivity of all three partials at all 24 stations, live, every frame, for free.
Why the arch is where it is
| partial | nodes (fraction of length) | strain peak | mass peak | strain share at centre | df/f per mm cut at centre |
|---|---|---|---|---|---|
| 1 | 0.2246, 0.7754 | 0.479 | 0.979 | 0.10406 | −0.0063 |
| 2 | 0.1326, 0.5000, 0.8674 | 0.729 | 0.979 | 0.00308 | −0.0002 |
| 3 | 0.0953, 0.3557, 0.6443, 0.9047 | 0.188 | 0.021 | 0.07678 | −0.0038 |
The second partial is antisymmetric. Its centre node is at exactly 0.5, and a node of an antisymmetric mode carries no bending moment either — so at the middle of the bar, partial 2 stores 0.3% of its strain and partial 1 stores more than anywhere else. The ratio of the last column is thirty to one.
That is the lever. Cut at the middle and the fundamental falls thirty times faster than the partial above it:
| gouge depth | thinnest | f₁ Hz | partial 2 | partial 3 | f₁ moved by |
|---|---|---|---|---|---|
| 0.0 mm | 20.0 mm | 564.1 | 2.7566 | 5.4041 | 0 |
| 2.0 mm | 18.0 mm | 519.8 | 2.9107 | 5.7097 | −142 ¢ |
| 4.0 mm | 16.0 mm | 472.1 | 3.0965 | 6.1202 | −308 ¢ |
| 6.0 mm | 14.1 mm | 421.0 | 3.3237 | 6.6807 | −507 ¢ |
| 8.0 mm | 12.1 mm | 366.6 | 3.6062 | 7.4664 | −746 ¢ |
| 10.0 mm | 10.1 mm | 309.0 | 3.9668 | 8.6139 | −1042 ¢ |
| 11.5 mm | 8.6 mm | 263.8 | 4.3113 | 9.8679 | −1316 ¢ |
| 13.0 mm | 7.1 mm | 217.0 | 4.7509 | 11.7061 | −1654 ¢ |
The arch does not raise the second partial to four. It lowers the first one past a second partial that is barely listening. The interval opens as a side effect, and the price is 1300 cents of pitch — which is why the blank has to start an octave and a half sharp, and why the deep throat under a marimba bar is not what it looks like it is for.
There are places where cutting makes it sharper
The same gouge, taken symmetrically at five stations:
| cut at | f₁ moves ¢ | f₂ moves ¢ | f₃ moves ¢ | partial 2 | partial 3 |
|---|---|---|---|---|---|
| 0.0600 | +92.3 | +16.9 | −54.8 | 2.6390 | 4.9638 |
| 0.1500 | −14.2 | −170.3 | −234.0 | 2.5189 | 4.7598 |
| 0.2243 | −144.3 | −294.0 | −242.7 | 2.5281 | 5.1055 |
| 0.3200 | −286.6 | −302.0 | −143.8 | 2.7321 | 5.8685 |
| 0.5000 | −602.9 | −117.3 | −149.5 | 3.6491 | 7.0219 |
Cut near the end of the bar and the fundamental goes up almost a semitone. The ends are where mode 1 has its displacement antinodes and almost no bending: the wood there is ballast, not spring, and taking ballast off a spring makes it faster. Every station in the first two columns is a sign change looking for a place to happen, and the demo draws all of them as a strip you can read before you commit.
What it costs to be wrong
The bar roughed to an 8.5 mm arch, then over-cut by 1 mm at one station, then tuned as hard as a solver can tune it from there:
| slip at | which is | best pitch ¢ | best partial 2 ¢ | best partial 3 ¢ | verdict |
|---|---|---|---|---|---|
| 0.5000 | the throat | −0.37 | 25.73 | −52.44 | scrap |
| 0.3500 | the shoulder | −0.03 | 1.77 | −4.42 | recovers |
| 0.2243 | a node of partial 1 | −0.02 | 2.53 | −4.83 | recovers |
| 0.1200 | the shoulder | −0.03 | 2.15 | −5.74 | recovers |
| 0.0300 | the end | −0.05 | 2.05 | −5.40 | recovers |
One station in five is fatal, and it is the one the arch was going to be cut at anyway. The throat is the useful place to cut for exactly the reason it is the dangerous one: it is the only station with a thirty-to-one lever on it, so an extra millimetre there moves the fundamental and the ratio together, by an amount that no other station has the authority to answer. A slip at the end is nothing — there is still a throat left to spend.
How much is too much, at the throat:
| over-cut | best pitch ¢ | best partial 2 ¢ | best partial 3 ¢ | verdict |
|---|---|---|---|---|
| 0.20 mm | −0.03 | 1.39 | −2.93 | recovers |
| 0.50 mm | −0.01 | 1.07 | −2.47 | recovers |
| 0.70 mm | −0.12 | 6.67 | −18.64 | scrap |
| 0.90 mm | −0.27 | 15.95 | −39.85 | scrap |
| 1.00 mm | −0.37 | 25.73 | −52.44 | scrap |
| 2.00 mm | −7.57 | 231.30 | −452.60 | scrap |
| 3.00 mm | −271.50 | −145.80 | −1208.85 | scrap |
Between half a millimetre and seven tenths, at one station, after everything else has gone right. The gouge cuts at 8.1 mm a second, so it crosses that margin in 74 milliseconds — less than a reaction time. The scraper cuts at 0.99, and takes 0.61 seconds to do the same damage. That factor of eight is the entire argument for having two tools: not that the fine one is more accurate, but that it is slow enough for the mistake to be interruptible.
That is the shape of the whole mechanic. The strength of a control and the cost of misusing it are the same number, read twice.
What is physics here, and what isn’t
- Derived, and checked against the closed form: the βL roots and the 2.7565
that falls out of them; the FEM’s agreement with them to a seventh of a cent;
Rayleigh’s
(3U − λT)/h; the node positions at 0.2242 and 0.1321/0.5; the thirty-to-one lever at the throat; the sign flip near the ends; the whole sensitivity strip, which is the derivative and not a fit to it. - Measured, not asserted: that 1 : 4 : 10 is reachable at all. A gradient descent that may only ever remove material finds a profile for every note tried, landing at ratios 4.00356 and 9.99105 with the fundamental 0.016 cents off. Its job is not to tune — it is to establish that the target is not a lie. It is behind the “finish it” button.
- Chosen: rosewood at E = 16 GPa and ρ = 830 kg/m³; a 45 × 20 mm section; the ±5 ¢ and ±12 ¢ tolerances; the floor at 18% of the blank, below which the bar splits; and the blank’s headroom of 2.56×, which is not arbitrary — below about 2× there is not enough pitch to pay for the arch and the bar is not hard to tune, it is impossible, which the measurements show and the demo ships one of as a scenario.
- Not modelled, and it matters: shear and rotary inertia. Euler–Bernoulli runs sharp, by an estimated 16 ¢ on the fundamental and 137 ¢ on the fourth partial for this section — so the absolute hertz are a bar-shaped fiction. The ranking of stations is a property of the mode shapes and survives intact, which is the thing the mechanic is about. A real tuner also works on a bar whose width and length are already fixed by a resonator, and has torsional modes to avoid; neither is here.
- Not modelled, and it doesn’t matter to the lesson: wood being anisotropic and full of grain that makes E vary along the bar; damping, which sets how long the note lasts and not what it is; the tube resonator underneath, which amplifies the fundamental and is why partial tuning is audible at all.
- A game, not physics: the router removes a parabola per pass, the bar splits at a fixed fraction rather than failing in shear, and you may strike it as often as you like for free. A real tuner strikes, listens, and loses the pitch memory between taps.
Reuse
src/undercut.mjs is framework-free with no canvas in it.
makeBar/carve/cloneBar/stations— the bar and the one irreversible verb.carve(bar, at, radius, depth)takes a parabolic bite and setsbar.brokenif it goes through the floor.modes(bar, count)→{freqs, shapes, lambdas}— assemble, Cholesky, Jacobi, drop the two rigid-body modes. Shapes come back M-normalised, which is what makes the sensitivity formula come out in one line.sensitivity(bar, m, k)— Rayleigh’s derivative per element, signed so that positive means removing here raises it.energySharesis the two curves it is the difference of;nodesOfandmodeCurveare for drawing.exactUniformHz/exactUniformRatios/EXACT_BETA_L/shearCorrection— the closed forms, for checking the numerics against something that is not itself.lengthForNote(hz, {headroom})— why marimba bars get shorter rather than thinner as they go up.makeGame,evaluate,tuned,TOL,noteHzare the game layer.autotune(game)— descent with a backtracking line search, on an asymmetric cost where being sharp is work remaining and being flat is damage.
The demo is demo/index.html with its own copy of the module (ADR-0002). Drag
on the bar to cut, or ←/→ to move the router and space to cut (hold
shift for fine steps). g swaps the gouge for the scraper, 1/2/3 pick
which partial the overlay is about, s strikes it, n is the next blank, r
starts over. It sizes itself to its viewport, so the site’s full-screen button
just works.
It makes a sound, and the sound is the four frequencies the eigensolver just returned, additively, with higher partials quieter and shorter — no samples and no fudge factors. A blank thuds. A half-cut bar is a marimba with something wrong with it. A tuned one is a marimba. That is the readout that needs no legend, and it is the reason to build this as a thing you can hold rather than a chart.
node scripts/measure.mjs regenerates every table above. node scripts/screenshot-demo.mjs regenerates the thumb and media and is the smoke
test: 13 assertions, any page error fails the run, and it checks the live demo’s
own readouts against the closed forms — that the blank in the browser rings at
2.75656 against the analytic 2.75654, that there exist stations where cutting
raises the fundamental and stations where it lowers it, and that the demo can be
driven to 1 : 4 : 10 with the fundamental inside 0.4% of the note.






