EIGENMODE.

Guide · how it was made

Solving the shapes a circle is allowed to make

EIGENMODE is a study of the vibration modes of a clamped circular drumhead — its Bessel eigenmodes. A live three.js membrane displaces itself by the exact mode equation while WebAudio voices each shape's own inharmonic pitch. Its single job: let you see and hear the finite alphabet of shapes one drum can hold.

The figure

Mode J₂₍₁₎ — two diameters, one clover

A static rendering of the resting mode: two nodal diameters split the head into four lobes that alternate in phase. Gold pushes toward you, violet away; the dark seams are the nodal lines that never move, and the rim is clamped flat.

Palette

Six tones, argued from the membrane

The two brights are reserved for lines and marks only — nodal grid, rim and antinode peaks. Body copy runs on a derived light ink that clears 4.5:1 comfortably on the dark ground, so nothing legible ever leans on an accent.

Membrane dark
#0C0F16
The still, un-struck head. A blue-black ground that lets the mode light read as emission.
Panel
#0F131D
A half-step lift for the lower band and controls — separation without a border everywhere.
Mode line / mark
#B07CE0
Violet for the nodal grid, clamped rim and negative antinodes. A LINE token — never body text.
Antinode glow
#E6C766
Warm gold for the peaks that push toward you, and the frequency-ratio accent. Marks only.
Body ink
#CBCDDA
Derived light lavender-grey, ≈11:1 on the ground. Carries every line of real copy.
Ink dim
#969AAF
Secondary voice for lede and captions, still ≈6:1 — quiet, not weak.

Type

A resonant serif, a laboratory mono

Fraunces italic
Fraunces · display
A soft, high-contrast "old-style" serif with optical sizing. Its organic swell suits a physical, acoustic subject — the shapes feel struck and ringing, not engineered.
J₂₍₁₎ · ×2.136 · 205 Hz
JetBrains Mono · text
The instrument's data voice. Tabular figures keep the mode index, α, ratio and pitch steady as you step, and it sets the Bessel labels without ambiguity.

Technique

How the membrane is solved

The surface is a custom polar BufferGeometry — a ring-and-sector grid clamped at radius 1. Each vertex is displaced by the drumhead equation u = J_m(α·r)·cos(mθ)·cos(ωt), where J_m is the Bessel function of the first kind (an ascending power series in code), α is the n-th zero of J_m, and the rim stays flat because J_m(α)=0 by definition. Modes are ordered by their zero α, so stepping through them walks the drum's real overtone series — ratios 1.000, 1.593, 2.136, 2.295… which are audibly inharmonic. Vertex colour is taken from the static shape's sign (gold for positive lobes, violet for negative), so nodal lines fall out as dark seams. A coarse deforming line grid and additive glow-points on a procedural canvas sprite mark the structure and the pulsing antinodes; WebAudio strikes a sine plus two inharmonic partials with an exponential decay for the membrane's voice.

Process

Three iteration passes

1Craft

Copy over the live membrane was losing contrast, so hero and console text gained soft dark scrims (text-shadow) to hold legibility without hiding the surface. A thin canvas sliver was bleeding through the gap above the footer — closed by dropping the footer's top margin. The two brights were demoted to strict line/mark tokens and a derived #CBCDDA body ink (~11:1) was minted for all copy.

2Depth

Added the pulsing antinode glow-points — gold where the surface rises toward you, violet where it falls away — riding the peaks and brightening at maximum displacement, which makes the physics legible at a glance. Each mode change now fires a subtle strike-overshoot (amplitude jumps to 1.35× and eases back) synced to the audio pluck, so the membrane visibly reacts to being struck.

3Hardening

Confirmed prefers-reduced-motion renders a settled full-amplitude mode — a real shape, never a blank — with no animation loop. Verified 375px has no real overflow (wordmark and nav sit inside the viewport box, scrollWidth clean). Re-checked DPR cap, resize, hidden-tab pause and context-loss guard, and a headless Chrome run at 7s came back with zero console errors while a driven mode-step changed the rendered pattern.