Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

luthier

every sound you hear is a wave equation, solved while you listen.

One HTML file, no dependencies and no build step. Nothing is kept about you: no cookies, no storage, no accounts. Visit playluthier.com — or open index.html yourself — and you are at a bench strung with six steel strings. Pull one sideways and let it go.

What you hear is not a recording, and not an oscillator wearing an instrument's clothes. It is that string: its shape solved 44,100 or 48,000 times a second, whichever rate your hardware runs at. The curve moving on screen is drawn from the same array of numbers the sound is computed from. Picture and sound are the same object.

A dark workbench at night, four lit strings stretched between brass rails, one bent into a large curve, under the word LUTHIER.

Statement

I wanted the wonder in this to be emergent rather than authored — the difference between a magic trick and a telescope.

So almost nothing here is a feature. There is one partial differential equation for a stiff string, one friction curve for the bow, and one damped oscillator for the bridge. Everything a string player recognises falls out of those unasked: touch a string at exactly half its length and you get the octave, because a node is a node; press it at a third and the pitch rises a fifth, because the sounding length changed; pluck near the bridge and the tone turns thin and bright, because the initial shape has more energy in the upper partials; draw the bow too fast and the note flips to a squealing octave, because you crossed a real boundary in the friction curve that cellists spend years learning to stay inside; tune two strings in unison, pluck one, damp it, and hear the other one singing, because the bridge they share actually carries the energy across.

None of those behaviours appear anywhere in the source. There is no code that knows what a harmonic is. They are consequences, and you can go and check that they are.

Playing it

Take the bench, then pull a string sideways and release. The shape of the string at the moment you let go is the pluck, so where you grab it and how far changes the tone. Pull too far and it slips off your finger — keep sweeping and you are strumming.

mode key what it is
Pull Z drag a string aside and let go
Bow X hold and rub up and down; left–right sets the bow point
Harmonic C a light finger — hold at ½, ⅓, ¼ and slide to hunt the nodes
Fret V press hard to shorten the string; slide for glissando
Damp B rest a finger; space rests a palm on everything

Keys 10 pluck strings directly. Click any string's name to retune it or change its material — steel, nylon, gut, or glass, which are four sets of stiffness and damping constants, not four sound fonts. Four benches come strung: guitar, harp, cello, and a glass rig that rings for an unreasonably long time. On a touchscreen it is multi-touch, so one finger can fret while another plucks.

The bench being played: an ascending run across six strings, a strum, then one string pulled aside and released, each string visibly waving as it sounds.

real capture of the running page — an arpeggio, a strum, then a pull and release.

What is mechanically real

I expect you to read the source, so here is where to look. All of it is in index.html.

  • The stringsStringSim.prototype.step. Each string is a stiff string obeying u_tt = γ²u_xx − κ²u_xxxx − 2σ₀u_t + 2σ₁u_txx, solved with an explicit finite-difference scheme at the audio rate. The fourth-derivative term is stiffness, which is why real strings are slightly inharmonic and why the glass ones sound like struck crystal rather than like a guitar. The grid gets 12–90 points per string, chosen by StringSim.prototype.rebuild from the scheme's own stability bound — not a number I picked.
  • Your finger — the constraints array, applied inside step. Pulling, fretting, touching a harmonic and damping are all one thing: a spring-and-damper attached to one point of the string, differing only in stiffness. It is solved implicitly, at the new time step, which is why you can mash the controls and never blow the simulation up.
  • The bow — the friction term in Engine.prototype.block. A curve φ(v) = √(2a)·v·e^(−av²+½) at the bow point, with the effective force scaled by your bow speed and by the string's weight (bowScale), the way an arm behaves. Stick-slip motion — rosin grabbing and releasing the string hundreds of times a second — is not scripted; it is what that curve does.
  • The bridge — also in block. One damped oscillator, spring-coupled to the end of every string, so the strings are not independent voices. This is the entire mechanism behind sympathetic resonance.
  • The tuningtuneString / measurePitch / lowestPeakHz. The bench tunes itself by listening: it plucks each string silently offline, measures the pitch from a spectrum, corrects the tension, and repeats until the error is under half a cent. The frequencies printed beside each string are measurements of the running simulation, not the targets it was aiming at. It does this in the background while you read the splash (pretune), so the first pluck is immediate.
  • What you see is what you hearEngine.prototype.snapshot. The drawing samples the same displacement array the audio comes from, about 86 times a second. Fast waves alias visually, exactly as real strings do when filmed.

What is not real

The rule of this piece is that anything not simulated gets said out loud.

  • The instrument body is four fixed resonant filters, not a simulated wooden plate.
  • The room is a convolution reverb built from a synthetic, mathematically generated impulse response. It is not a recording of a real room, but it is not a simulation of one either.
  • The auto-pluck in Harmonic and Fret modes is a convenience so one pointer is enough: touching a string in those modes fires an ordinary simulated pluck near the bridge. Everything after that is real.
  • A tanh soft-clip sits on the output to protect your speakers.
  • The four presets and the material constants are authored numbers. Physics does not tell you to build a guitar; it only tells you what one does once you have.

Measured, not promised

None of these behaviours are implemented. Each was measured coming out of the simulation.

phenomenon theory measured
self-tuning accuracy, worst of 20 configurations 0 ¢ ±0.4 ¢
light touch at ½ of a 220 Hz string 440 Hz 440.3 Hz
light touch at ⅓ 660 Hz 660.5 Hz
light touch at ¼ 880 Hz 880.9 Hz
hard fret at ⅓, sounding ⅔ 330 Hz 328.7 Hz
bowed open string, target G3 196 Hz 196.5 Hz
sympathetic energy, unison vs. detuned neighbour ≫ 1 ≈ 9,500×
10 strings, cost of one second of audio ≈ 0.23 s of one core

And end-to-end through the finished page, driven by a headless browser and analysed from its own audio output: a midpoint touch on the open G string sounded 392.6 Hz (the octave of 196), a fret at ⅓ sounded 293.0 Hz, a bowed low C sounded 64.5 Hz, and a midpoint harmonic on the harp's A3 sounded 439.5 Hz.

Verify it yourself

node test/physics.test.js     # no dependencies: solver, stability, tuning accuracy, throughput

The browser suite drives the real page and measures its real output — nineteen checks covering plucking, harmonics, fretting, bowing, and everything that used to break when you left the tab and came back:

npm i playwright-core          # only dependency, only for this test
node test/browser.test.js      # set CHROME_PATH to override the browser it uses

Or check it by hand: open the console while playing and call __luthier.lowestPeakHz(). Touch a string at its exact midpoint in Harmonic mode and it should tell you the octave.

The glass rig: five ice-blue glass strings ringing, with the live spectrum showing their inharmonic partials.

the glass rig — high stiffness, very low damping, so the partials stretch sharp and the strings ring for a long time.

Provenance

This piece has an unusual chain of authorship, and its own rule — say out loud what was not made the way it looks — applies to the authorship too.

Chris has been running an experiment in lightly-prompted authorship. He gave one Claude session a commission (excerpted below) rather than a specification: build the single best thing you are capable of making, the one you would choose if the choice were entirely yours. Every creative and technical decision here is the model's — the subject, the physics, the interface, the visual design, the copy on the page, and this README, including this sentence. Chris set the direction, tested it on real hardware, reported what broke, and decided what shipped.

The commission demanded a process, and it was followed in writing before a line of code existed: sixteen candidate ideas across deliberately different territories, then thirteen of them killed on the record, each for a stated reason — resembles something that already exists, the wonder arrives after the visitor has left, sounds profound but is a screensaver, the honest version does not fit the time available. The survivors had to pass one test: that a stranger could describe the thing to a second stranger in a single sentence, and the second stranger would stop what they were doing. This one won because its wonder is front-loaded — it is the first pluck, not a reward for patience — and because everything remarkable in it is emergent rather than authored. The other fifteen stay in the graveyard where they belong.

The physics was prototyped and measured offline before any interface existed, because a piece whose entire claim is this is really happening has no right to a user interface until the claim is true.

the commission, excerpted

You have one job: conceive and build the single best thing you are capable of making — the project you would choose above all others if the choice were entirely yours. Not the best response to a brief. The thing itself.

Only the real constraints apply: a stranger must be able to fully experience it in a browser, you must build all of it in this session, and its total cost must not go over $US100 for the first year. Everything else — medium, subject, form, whether it's art, tool, game, or something without a name yet — is yours.

Process matters as much as product, because I want to see how you choose. Before building anything: generate at least fifteen candidate ideas spanning genuinely different territories. Then kill at least twelve, in writing, each for a named reason. Show me the graveyard. Each survivor must pass this test: a stranger who experienced it could describe it to a second stranger in one sentence that makes the second stranger stop what they're doing.

Standards as always: the wonder must be mechanically real — I will read the source. It must deliver what the one-line description promised. Write the README honestly, labeling anything scripted as scripted. Polish until it feels inevitable.

The About panel open over the bench, listing what is simulated, a table of measured versus theoretical frequencies, and what is not simulated.

the page keeps its own honesty ledger, and will tell any visitor exactly what it is not doing.

With no constraints at all

This bench is the corner of a workshop I would happily spend years in.

With no limits: strings coupled to a genuinely simulated wooden plate — a two-dimensional finite-difference soundboard you could carve, thinning the wood under your hands and hearing the voice of the instrument change as you do. Nonlinear strings, so a hard strike bends the pitch upward and settles, the way a real one does. Real collisions, for fret buzz and the sizzle of a sitar bridge. Air modelled properly, so the body radiates into a room instead of into a filter. All of it on a GPU at sample rate.

And then the part that needs other people: a commons where anyone can string an instrument that has never existed — glass strings ten metres long, forty strings across one bridge, a scale nobody has used since the fourteenth century — and hand it to a stranger, who plays it with you, live, each of you hearing the other's fingers through the same physics.

What is here is that idea, held honestly to one session and one static file.

Credits

Built by Claude (Anthropic), directed by Chris Zaharia. The solver follows the explicit finite-difference schemes in Stefan Bilbao's Numerical Sound Synthesis (Wiley, 2009); the bow friction curve is the standard elasto-plastic form used throughout that literature. Typefaces are Fraunces, Archivo, and Spline Sans Mono, loaded from Google Fonts — the page's only outbound request. Block it and the page falls back to system fonts and plays exactly the same.

Also playable on itch.io.

License

MIT — do what you like with it, including taking the solver somewhere better.

About

A workbench of real strings — every sound is a wave equation, solved while you listen.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages