Lab · Playground
A harmonograph drawing machine
Four decaying pendulums steer a pen; frequencies near simple ratios draw the spirograph-like curves Victorian harmonographs were famous for.
A harmonograph is a Victorian parlour machine: pendulums swing a pen and a table in different directions, and the pen draws as everything slowly runs down. Mathematically each pendulum is a damped sine wave, and the pen’s position is the sum of two of them in each direction:
x(t) = A1·sin(f1·t + p1)·e^(−d1·t) + A2·sin(f2·t + p2)·e^(−d2·t) y(t) = A3·sin(f3·t + p3)·e^(−d3·t) + A4·sin(f4·t + p4)·e^(−d4·t)
With no damping and one pendulum per axis this is a Lissajous figure — the shape you see on an oscilloscope fed two sine waves. When the frequencies are in a simple ratio (2 : 3, 3 : 4) the curve closes on itself; nudge one slightly off and the figure slowly rotates, which is what makes real harmonograph drawings look like woven ribbons. The damping pulls everything into the centre.
The machine draws a new pattern every few seconds with frequencies chosen near small whole-number ratios. Click to start a new one at once.
// Things to change: RATIOS, DETUNE, DAMP, and SPEED.
const RATIOS = [1, 2, 3, 4, 5]; // frequencies are picked from these
const DETUNE = 0.012; // how far off the exact ratio — the source of the slow rotation
const DAMP = [0.004, 0.012]; // damping range per pendulum
const SPEED = 160; // points drawn per frame
const DT = 0.02; // time step between points
let P, t, prev, hue;
function setup() {
createCanvas(min(windowWidth, 640), 320);
colorMode(HSB, 360, 100, 100, 100);
newDrawing();
}
function pendulum(amp) {
return { a: amp, f: random(RATIOS) + random(-DETUNE, DETUNE), p: random(TWO_PI), d: random(DAMP[0], DAMP[1]) };
}
function newDrawing() {
const r = min(width, height) * 0.24;
P = [pendulum(r), pendulum(r), pendulum(r), pendulum(r)];
t = 0;
prev = null;
hue = random(360);
background(35, 12, 97);
}
const swing = (q, t) => q.a * sin(q.f * t + q.p) * exp(-q.d * t);
function draw() {
translate(width / 2, height / 2);
strokeWeight(0.8);
for (let i = 0; i < SPEED; i++) {
const x = swing(P[0], t) + swing(P[1], t);
const y = swing(P[2], t) + swing(P[3], t);
if (prev) {
// Colour drifts along the drawing; alpha keeps overlapping passes readable.
stroke((hue + t * 0.4) % 360, 70, 55, 45);
line(prev.x, prev.y, x, y);
}
prev = { x, y };
t += DT;
}
// Start over once the pen has nearly stopped.
const left = P.reduce((s, q) => s + q.a * exp(-q.d * t), 0);
if (left < 6) newDrawing();
}
function mousePressed() {
if (mouseX >= 0 && mouseX <= width && mouseY >= 0 && mouseY <= height) newDrawing();
}
Things to change
-
RATIOS = [2, 3]restricts the machine to 2 : 3 and 3 : 2 figures — trefoils and pretzels. -
DETUNE = 0gives closed, perfectly repeating Lissajous figures;0.05turns them into fast spirals. -
DAMP = [0, 0]stops the decay: a pure Lissajous figure that never fades (set a limit ontthen, or it never starts over). -
For a rotary harmonograph, add a fifth term that turns the paper: replace
x, ywithx*cos(0.01*t) - y*sin(0.01*t)andx*sin(0.01*t) + y*cos(0.01*t). -
SPEED—2000draws each figure almost instantly;20lets you watch the pen move.