How it was made
Sound, drawn in sand
Nodal is a fictional study bench — Chladni plate no.7 — that renders a thing you normally only hear. A steel plate is driven from beneath at a chosen pitch; sand scattered on top flees the shaking antinodes and piles onto the nodal lines that stand perfectly still, drawing the shape of the standing wave. Its single job: let a visitor see a pitch and watch the figure re-form when they change it.
The signature: pitch you can see
The plate is a full-canvas square driven from its centre. Roughly thirteen thousand sand grains live in normalised plate coordinates. Each frame, every grain samples the local amplitude of the plate's standing wave and hops a random step in proportion to how hard that spot is shaking. Where the amplitude falls to zero — the nodal lines — the grains simply stop, because there is nothing left to throw them. Thousands of grains running out of anywhere to fall is the figure.
The wave itself is the classic square-plate model, the antisymmetric combination cos(nπx)cos(mπy) − cos(mπx)cos(nπy). A frequency dial picks the driving pitch; the nearest resonant mode (m, n) sets the field. Cross a resonance and the plate locks; between resonances a rising agitation floor keeps every grain bouncing, so the sand scatters into static — honestly, only certain notes hold a figure. Change the pitch and the old nodes become antinodes: the sand is flung loose and a new drawing settles out of the noise. A short excitation burst on every change makes the leap visible.
Palette — argued from steel and sand
A dark instrument bench, one warm material, one cool signal. Nothing decorative: the bright accent is reserved for the node glow and marks, and body text runs on a derived light steel-ink that clears 4.5:1 on the graphite ground.
Type
Techniques
- Canvas 2D grain migration — ~13k grains advected on a modal displacement field; a Metropolis-style rejection of uphill moves sharpens the nodal lines.
- Additive node-glow pass — settled grains (amplitude below threshold) get a cool halo drawn with globalCompositeOperation = 'lighter', so the nodal lines pick up the signal colour.
- WebAudio drive tone — a sine oscillator through a lowpass, created only on the first user gesture, ramped in gently and re-tuned as you sweep. The visual runs in full silence.
- Resonance model — a discrete mode table; distance to the nearest mode sets a detune floor, so the sand only organises at a lock.
- Nearest-note readout — the driving frequency is mapped to the nearest tempered note (a second-read detail linking the figure back to the sound).
Iteration log
Density, crispness, and a broken mobile header
The first settled figure read thin — nodal lines were faint and a little fuzzy. Raised the grain count to ~13k, lowered the base jitter and strengthened the uphill-move rejection so settled grains stay put and the lines sharpen. At 375px the top nav overflowed to 399px (the four links plus the wordmark ran off-edge — exactly the LEARNINGS masking trap). Fixed by hiding the two in-page anchor links below 640px and dropping the wordmark suffix below 400px. Tightened hero spacing and shrank the plate to 60vh so more of the figure clears the fold.
Node glow, a nearest-note readout, and strike-to-reset
Pushed the additive node-glow up so the still lines carry a faint cool sheen — the sand on the nodes now looks lit, which is the whole point. Added a nearest-note field to the readout (418 Hz → G♯4): the second-read detail that ties pitch to what you'd hear. Added a micro-interaction — clicking the plate strikes it, re-scattering the sand so it re-settles, the way tapping a real plate does.
Reduced motion, focus, and driving the signature under test
Confirmed the reduced-motion path: JS runs ~620 pre-settle steps, paints one static figure and never starts the loop — a settled plate, not a blank one — verified by two identical frames 1.4s apart. Capped DPR at 2, sized to container, re-derived pixel positions on resize (grains live in normalised coordinates, so resize needs no reset), and paused the rAF loop on document.hidden. Every pass drove the real signature in headless Chrome: programmatically sweeping 418 → 968 Hz and asserting the screenshot hash changed as the sand migrated to the new mode's nodal lines. Zero console errors; 375 / 768 / 1440 clean.
← Back to the plate · All of the Generative Assets collection