Skip to main content

Playground · research instrument

DSP · Audio

Draw → Fourier Epicycles

Draw a closed shape with your mouse or finger. It gets decomposed into rotating circles — a discrete Fourier series — that chain together to trace your exact path back.

Independent research instrument — not claimed as MakerPortal shipped product code. Methods, equations, assumptions, and limitations are disclosed so you can inspect what the page does and does not establish.

Draw and watch

Draw a closed shape below — a star, a loop, your initial. Let go to see it decomposed.

Anatomy of the instrument

Every pixel above answers to the math below. Here is what each piece of the canvas, the presets, the slider, and the reconstruction is actually doing — and why it is built that way.

The epicycle canvas

  1. 01

    The coordinate mapping. The canvas is the complex plane. Every point (x,y)(x,y) you draw is treated as z=x+iyz = x + i y. The left edge maps to negative real values, the right edge to positive real values, and the Y-axis is the imaginary axis. This is not just a coordinate trick — it is what makes rotation become multiplication by eiθe^{i\theta} with zero matrix overhead.

  2. 02

    Epicycle circles and arms. Each term draws a faint circle of radius rk=∣Xk∣/Nr_k = |X_k|/N centered at the previous term's tip, plus a radial arm from that center to its own tip. Circles smaller than 1.5 px are skipped visually but still contribute to position. The arms chain tip-to-tail in amplitude-sorted order — largest first — so you see the structure emerge from the biggest contributors down to the fine detail.

  3. 03

    How a drawing becomes a path. Mouse or touch events collect raw screen coordinates on pointerdown/pointermove/pointerup. The raw polyline is resampled to N=140N=140 uniformly arc-length-spaced points via resamplePath, erasing drawing speed. The path is then treated as closed: the last point connects back to the first via modulo indexing. That closure edge is the only synthetic geometry — everything else is your hand.

  4. 04

    The terms slider. Maps MM from 1 to min⁡(60,N)\min(60, N). At M=1M=1 you see the single largest circle, spinning about the centroid: the path is shifted to its average position before the DFT, so the DC term is zero and sorts last. Each step adds the next-largest circle. The slider is disabled until a path is finalized. Changing it clears the trail to avoid mixing reconstructions from different term counts.

  5. 05

    The render loop. There is no numerical integration and no differential equation. Each frame computes t=((now−loopStart) mod 9000)/9000t = ((now - loopStart) \bmod 9000) / 9000, steps through MM terms computing xk+1=xk+rkcos⁡(2πfkt+ϕk)x_{k+1}=x_k+r_k\cos(2\pi f_k t+\phi_k) and yk+1=yk+rksin⁡(2πfkt+ϕk)y_{k+1}=y_k+r_k\sin(2\pi f_k t+\phi_k), pushes the final (x,y)(x,y) into a 420-point trail buffer, and draws. Pure trigonometric reconstruction — the epicycles are the formula, not a simulation of the formula.

The DFT — what runs when you lift the pen

Xk=∑n=0N−1z~n e−i2πkn/N,k=0…N−1X_k = \sum_{n=0}^{N-1} \tilde{z}_n\, e^{-i 2\pi k n / N},\qquad k=0\dots N-1

This is the naive O(N2)O(N^2) DFT — two nested loops, no FFT. Each XkX_k is divided by NN before storage, so ∣Xk∣|X_k| is already the circle radius. The frequency mapping assigns k≤N/2k \le N/2 to positive (counter-clockwise) and k>N/2k > N/2 to k−Nk-N (clockwise). Both directions are needed for non-circular shapes.

Controls, presets, and reconstruction

  1. 01

    The presets. Six hardcoded paths: a circle (pure k=1k=1), a 5-point star (circles only at k=1,−4,6,−9,11,…k=1,-4,6,-9,11,\dots, one plus a multiple of five), a lemniscate (needs balanced k=1k=1 and k=−1k=-1), a parametric heart, a logarithmic spiral (closed by a return line to center), and an MP monogram (multiple straight segments — Fourier hates straight lines). Each preset exercises a different spectral property.

  2. 02

    The DFT algorithm. O(N2)O(N^2) naive DFT — two nested loops, no FFT. For each frequency kk, the inner loop accumulates re+=x~ncos⁡ϕ+y~nsin⁡ϕre += \tilde{x}_n\cos\phi + \tilde{y}_n\sin\phi and im+=−x~nsin⁡ϕ+y~ncos⁡ϕim += -\tilde{x}_n\sin\phi + \tilde{y}_n\cos\phi with ϕ=2πkn/N\phi=2\pi k n / N, then divides by NN. At N=140N=140, that's ~19,600 complex multiply-adds — instant in JS, and clearer to read than importing an FFT library.

  3. 03

    Term sorting by amplitude. After computing all XkX_k, the array is sorted descending by ∣Xk∣|X_k|. This means the slider peels away importance rather than arbitrary high frequency. For a star, k=5k=5 outranks k=2k=2 despite being higher frequency — symmetry demands it. You see structure emerge, not noise. This is perceptual low-pass filtering, not classical frequency-cutoff low-pass.

  4. 04

    Centroid subtraction. Before the DFT, the mean (cx,cy)(c_x, c_y) is subtracted from every point. This zeroes the DC component so the animation origin sits at the shape's center instead of at some canvas corner. Without this, the k=0k=0 term would dominate with an enormous radius to the page corner, and everything else would be a tiny orbiting detail.

  5. 05

    Path resampling and frequency mapping. The raw polyline is resampled to N=140N=140 uniform arc-length points via resamplePath — a linear walk along each segment at constant step size. This erases drawing speed and leaves only geometry. The frequency mapping fk=k if k≤N/2 else k−Nf_k = k \text{ if } k \le N/2 \text{ else } k-N converts standard DFT ordering (which packs positive frequencies in the first half and negative in the second) into signed integers whose sign is the direction of turn (on this canvas, whose y axis points down, positive turns clockwise on screen). Both directions are essential — a figure-8 needs k=1k=1 and k=−1k=-1 with near-equal amplitude.

Gear behind this build

DSP lab stack · 42 picks

Audio DSP tools42

More gear across every app: the full Gear list →

Essence

Any doodle is just circles spinning at the right speeds.

Draw a star, a heart, a shaky signature. Once you lift the pen, the path doesn't look like a single messy line anymore — it becomes a machine built from pure circular motion, one circle riding on the tip of another, all spinning at integer multiples of a base rate, reconstructing your exact wobble.

Ptolemy tried this to explain planets, stacking epicycles upon epicycles to fit retrograde loops. Fourier proved you can do it perfectly for any closed curve. Same math that powers spectral analysis in Biquadia, just rotated 90 degrees in your mind: instead of decomposing sound pressure over time into sine waves, you decompose x(t)x(t) and y(t)y(t) over time into spinning complex exponentials.

This playground makes that literal. You give it z(t)=x(t)+i y(t)z(t)=x(t)+i\,y(t), a walk in the complex plane. It computes how much of each pure spin ei2πkt/Ne^{i 2\pi k t / N} you need, then plays them back tip-to-tail. The tip draws you.

The intuition — ink as complex numbers

The canvas is the complex plane. Each point you draw is z=x+iyz = x + i y. Why complex? Because rotation becomes multiplication. Multiply by eiθe^{i\theta} and you rotate by θ\theta with no matrix, no bookkeeping. A pure circle at constant speed is just rei(2πft+ϕ)r e^{i(2\pi f t + \phi)} — radius rr, frequency ff, starting angle ϕ\phi.

Your drawing is a loop z(t)z(t) with tt from 0 to 1. Fourier's trick: any such loop, no matter how jagged, is a sum of circles spinning at integer rates k=…,−2,−1,0,1,2,…k = \dots,-2,-1,0,1,2,\dots. k=0k=0 doesn't spin — it's the centroid, the average position. k=1k=1 goes once around during your loop. k=−1k=-1 goes once the other way. k=2k=2 twice as fast, and so on.

Magnitude controls size. If coefficient XkX_k has large ∣Xk∣|X_k|, its circle is big and carries most of the shape. Phase controls orientation — where the circle starts at t=0t=0. Add all circles tip-to-tail and the final tip traces z(t)z(t) exactly. Remove small circles and you still get the gist but lose fine jitter. That is low-pass filtering.

Three details make this playground feel right:

  • Resampling to 140 points. Your hand produces uneven spacing — fast strokes leave few samples, slow strokes leave many. The code walks the polyline by arc length and emits N=140N=140 uniformly spaced points via resamplePath. That erases drawing speed and keeps only geometry. Constant SAMPLE_COUNT=140SAMPLE\_COUNT=140 keeps the DFT at O(N2)O(N^2) cheap and the animation crisp.
  • Centroid first. Mean cx,cyc_x,c_y is subtracted before transform, computed as c=1N∑nznc = \frac{1}{N}\sum_{n} z_n. The animation origin is that cc. Without this, the k=0k=0 term would dominate with a huge radius to the page corner, and everything else would be a tiny detail orbiting far away.
  • Sorted by amplitude. Raw frequency order is 0,1,2,…,N/2,−N/2+1,…,−10,1,2,\dots,N/2,-N/2+1,\dots,-1. Visually more useful: sort by ∣Xk∣/N|X_k|/N, biggest radius first. Then slider 1→601 \to 60 peels away importance rather than arbitrary high frequency. You see structure emerge, not noise.

Animation runs LOOP_MS=9000LOOP\_MS=9000 ms per cycle — tt sweeps 0→10 \to 1 every nine seconds, slow enough to see each epicycle turn. The pink trail buffer holds TRAIL_MAX=N×3=420TRAIL\_MAX = N \times 3 = 420 points, about three full loops of history, then overwrites. That persistence lets you compare reconstruction vs ideal without infinite ink.

Think like this

A circle of radius rr spinning at kk turns per loop and offset by ϕ\phi is rei(2πkt+ϕ)r e^{i(2\pi k t + \phi)}. Chain 60 of them: first circle's tip becomes second's center. Biggest first. That's all this page does.

Radius and phase decoded

rk=∣Xk∣/Nr_k = |X_k| / N   radius
ϕk=arg⁡Xk=atan2⁡(Im⁡Xk,Re⁡Xk)\phi_k = \arg X_k = \operatorname{atan2}(\operatorname{Im} X_k, \operatorname{Re} X_k)   start angle
fk=kf_k = k   turns per loop; the sign is the direction (positive is clockwise on this y-down canvas)

In code each term stores re, im, freq, amp, phase. amp=re2+im2amp=\sqrt{re^2+im^2} is already divided by NN, phase=atan2⁡(im,re)phase=\operatorname{atan2}(im,re).

Why uniform resample matters

If you kept raw mouse samples, fast strokes would be underweighted. Arc-length parameterization makes tt geometric, not temporal. 140 points captures a 5-point star's 10 corners easily, yet keeps DFT at 1402≈19600140^2 \approx 19600 complex multiply-adds — instant in JS.

The math — DFT to epicycles, no black boxes

1. From stroke to signal

After resampling you have N=140N=140 points (xn,yn)(x_n,y_n), n=0…N−1n=0\dots N-1, closed so zN=z0z_N=z_0. Build zn=xn+iynz_n = x_n + i y_n. Subtract centroid cx+icyc_x + i c_y to place animation origin at shape center. Call the zero-mean signal z~n\tilde{z}_n.

z~n=(xn−cx)+i(yn−cy),cx=1N∑n=0N−1xn,  cy=1N∑n=0N−1yn\tilde{z}_n = (x_n - c_x) + i (y_n - c_y),\quad c_x = \frac{1}{N}\sum_{n=0}^{N-1} x_n,\; c_y = \frac{1}{N}\sum_{n=0}^{N-1} y_n

2. Analysis — Discrete Fourier Transform

DFT asks: how much of each pure spin ei2πkn/Ne^{i 2\pi k n / N} lives inside z~n\tilde{z}_n? Project onto each spin:

Xk=∑n=0N−1z~n e−i2πkn/N,k=0…N−1X_k = \sum_{n=0}^{N-1} \tilde{z}_n\, e^{-i 2\pi k n / N},\qquad k=0\dots N-1e−iθ=cos⁡θ−isin⁡θe^{-i\theta} = \cos\theta - i\sin\theta

Code loops exactly this: re+=x~ncos⁡ϕ+y~nsin⁡ϕre += \tilde{x}_n\cos\phi + \tilde{y}_n\sin\phi, im+=−x~nsin⁡ϕ+y~ncos⁡ϕim += -\tilde{x}_n\sin\phi + \tilde{y}_n\cos\phi with ϕ=2πkn/N\phi=2\pi k n / N, then re/=N,im/=Nre/=N, im/=N. So stored re,im already include 1/N.

Interpretation: Xk/NX_k / N is amplitude and phase of the circle that spins kk times per loop. In raw DFT ordering, k>N/2k > N/2 corresponds to negative frequency k−Nk-N, i.e., clockwise spins. The code converts: freq = k <= N/2 ? k : k-N. That's why you see both positive and negative freq in sorted terms — you need both directions to make non-circular shapes.

3. Synthesis — epicycle reconstruction

Rebuild the path at continuous time t∈[0,1)t\in[0,1) by summing circles:

p(t)=c+1N∑kXk ei2πkt≈c+∑k∈keptrkei(2πfkt+ϕk)p(t) = c + \frac{1}{N}\sum_{k} X_k\, e^{i 2\pi k t} \approx c + \sum_{k\in\text{kept}} r_k e^{i(2\pi f_k t + \phi_k)}rk=∣Xk∣/N,ϕk=arg⁡Xkr_k = |X_k|/N,\quad \phi_k = \arg X_k

In animation t=((now−loopStart) mod LOOP_MS)/LOOP_MSt = ((now - loopStart) \bmod LOOP\_MS)/LOOP\_MS, LOOP_MS=9000LOOP\_MS=9000. Each frame computes angk=2πfkt+ϕkang_k = 2\pi f_k t + \phi_k, steps xk+1=xk+rkcos⁡angkx_{k+1}=x_k+r_k\cos ang_k, yk+1=yk+rksin⁡angky_{k+1}=y_k+r_k\sin ang_k tip-to-tail. Trail pushes final (x,y).

Use MM terms instead of NN and you keep only the biggest circles. On most drawings that is close to low-pass filtering, since the fast circles are usually the small ones (the order is by size, not by |k|). Dropping them removes fast wobble — exactly how JPEG discards high DCT coefficients, how audio codecs shave brilliance, how Biquadia's filter shelves high frequencies. Keeping only biggest ∣Xk∣|X_k| rather than lowest |k| is even smarter: biggest circles first, perceptually optimal compression.

Sharp corners need high |k|. Truncate and you see Gibbs phenomenon: ringing overshoot near discontinuities, about 9%9\% overshoot that never vanishes, only compresses in width as you add terms. That's why a star point looks rounded with few terms, then develops a tiny ripple that hugs the tip as you crank to 60.

State that actually travels

X=[X0,…,XN−1],  N=140\mathbf{X} = [X_0,\dots,X_{N-1}],\; N=140

140 complex coefficients, DC removed for origin. Sorted view keeps M≤60M\le 60 of them. Slider maps MM to visual fidelity. No FFT — naive O(N2)O(N^2) DFT, 19.6k ops, faster to write than to import FFT and clearer to read.

Frequency mapping

fk={kk≤N/2k−Nk>N/2f_k = \begin{cases} k & k\le N/2\\ k-N & k > N/2 \end{cases}

fk>0f_k > 0 and fk<0f_k < 0 turn in opposite directions (on this canvas, whose y axis points down, fk>0f_k > 0 turns clockwise on screen). Need both to trace arbitrary loops. A perfect circle needs only k=1k=1 (or -1). A figure-8 needs k=±1k=\pm1 balanced.

Low-pass as smoothing

pM(t)=c+∑j=1Mrjei(2πfjt+ϕj)p_M(t) = c + \sum_{j=1}^{M} r_j e^{i(2\pi f_j t+\phi_j)}

Energy compaction: usually top 8 circles hold 90%+90\%+ of power for cartoon shapes. Rest is edges, hand tremor, sampling noise. The slider sets how many of those circles are drawn, biggest first.

Why Gibbs rings

step discontinuity  ⟹  f^M overshoots ≈1.089…\text{step discontinuity} \implies \hat{f}_M \text{ overshoots } \approx 1.089\ldots

A sharp corner is high-frequency. Truncate Fourier series and partial sum overshoots by ≈9%\approx 9\%. Add more terms: overshoot moves closer to corner, doesn't vanish. Draw a star and push slider 20→6020\to60 — watch tips ripple, that's Gibbs live.

Playbook — what to draw, what the slider means

Start with presets

Circle: one dominant k=1k=1 circle, others near zero. At M=1M=1 the one circle already traces it. Pure tone in audio terms.

Star: 5 points = 5-fold symmetry. Its circles sit only at k=1,−4,6,−9,11,…k=1,-4,6,-9,11,\dots, one plus a multiple of five, because the star repeats every fifth of a turn. With 5-8 terms you get lumpy star; 15+ sharpens points but Gibbs ripples appear at tips. Shows why high |k| equals edge detail.

Infinity / figure-8: needs k=1k=1 and k=−1k=-1 with almost equal amplitude — counter and clockwise battles. Two terms give an ellipse, three give a pinched loop. Nice demonstration that you can't make a figure-8 from only CCW circles.

Heart: smooth bulge plus sharp cusp at bottom. Low kk capture bulge, cusp needs high kk. Watch bottom point evolve: M=5M=5 rounded, M=20M=20 pointed, M=40M=40 slight ringing either side.

Spiral: not closed, but code closes it with a line back to center. That closure edge is a discontinuity, so spectrum spreads wide — needs many terms, low-pass looks like shrinking coil. Good stress test for Gibbs.

MP monogram: multiple straight segments + bowl. Fourier hates straight lines and corners — needs many high frequencies. Demonstrates compression limit: text is hard.

The terms slider decoded

M=1M=1 — the largest circle alone, centred on cc. The centroid is subtracted before the DFT, so there is no spinning DC term to show: the first circle is the shape's best single-circle fit.

M=2M=2 to 3 — best-fit ellipse(s). Two largest circles make an ellipse, third adds offset bulge. Any smooth loop looks roughly elliptical at this level — same reason first two principal components capture most variance.

M=5M=5 to 10 — recognizable. Star shows 5 points, heart shows asymmetry, infinity pinches. This band is sweet spot for "aha" — enough structure, not too much clutter.

M=15M=15 to 30 — details, corners start to bite. You trade cleanliness for fidelity. Good range to see Gibbs begin.

M=40M=40 to 60 — nearly pixel-perfect, includes hand tremor and resampling artifacts. At 60 you kept 60/140≈43%60/140 \approx 43\% of coefficients — close to lossless. Beyond 60 diminishing returns; remaining terms are near noise floor.

Draw your own — tips

  • Draw slowly and close the loop near start — closure gap is linearly interpolated and creates a high-frequency edge.
  • Single non-intersecting loop beats scribble. Self-intersections still work, but spectrum smears — more high |k| needed.
  • If you want clean circles, keep it convex. If you want ringing, draw sharp zig-zag — that's Gibbs you can trigger on demand.
  • Loop time LOOP_MS=9000LOOP\_MS=9000 is fixed for perception, not tied to draw speed. That decoupling is deliberate — geometry, not timing.

Tweaks that matter

SAMPLE_COUNT=140SAMPLE\_COUNT=140

Uniform arc-length resample, O(L)O(L) walk. 140 balances detail vs N2N^2 cost and visual clutter. Raise it and you get hairline fidelity but slider needs more terms; lower and sharp corners alias.

LOOP_MS=9000LOOP\_MS=9000 ms

Full period for tt wrap. Chosen so k=10k=10 circle completes 10 turns in 9 s — you can still follow it by eye. Faster would blur, slower would drag. Trail TRAIL_MAX=420TRAIL\_MAX=420 = 3 loops covers persistence without unbounded memory.

Sorting by amp not |k|

Low |k| first would give classic low-pass. Sorting by ∣Xk∣/N|X_k|/N gives perceptual low-pass — biggest visual contributors first. For the star, five-fold symmetry zeroes every rate except one plus a multiple of five, so the sort runs k=1k=1, then k=−4k=-4 and k=6k=6, and never reaches k=2k=2 at all.

Honesty — what is real

DFT formula exact, implemented as double loop matching textbook definition, no FFT trickery. Centroid computed as mean then subtracted — origin is true DC. Complex exponentials use cos/sin via Math.hypot/atan2 for phase. Magnitude and phase decode directly to radius and start angle — no hidden scaling. Sorting stable and deterministic.

Honesty — what it simplifies

Resampling erases time — your fast vs slow strokes become equal length. Closure forced by modulo wrap: last point to first via straight line, even if you left a gap. No windowing, no pre-filter, so sharp closure edge leaks high frequency. Uniform sampling not perceptually weighted. Canvas is float32 raster, not analytic curve — trail anti-aliased by browser. O(N²) DFT doesn't scale to thousands of points, but we never go there. Trail is approximate history, cleared on slider change to avoid mixing reconstructions.

Why this matters for audio DSP

Same transform, different domain. Biquadia's spectrogram shows Xk(t)X_k(t) sliding over windows of audio. Here tt is not time but position along loop. Sorting by amp is like finding dominant harmonics — fundamental vs overtones. Low-pass by dropping high |k| is identical to low-pass filter in audio: it smooths transients, causes pre-ring near attacks, which is Gibbs in time domain. Once you see it on a star, you'll hear it on a snare.

Two gotchas worth knowing

N=140 limits the capture fidelity

With N=140N=140 sample points the Nyquist limit is kmax⁡=70k_{\max}=70. Any shape feature finer than about 1/701/70 of the loop perimeter is undersampled and will alias into lower frequencies — sharp corners, rapid zig-zags, or hand tremor faster than that spatial bandwidth. Shapes drawn in a single quick flick with few mouse samples will be coarsely interpolated. The resampling also erases your stroke speed, which is usually desirable but means fast-sketched detail is lost. Try the MP preset at M=60M=60: the straight vertical strokes of the letters never fully sharpen because N=140 cannot encode a true step.

DFT vs. continuous Fourier series

This page uses a discrete Fourier transform on 140 samples, not the continuous Fourier integral ∫f(t)e−iωtdt\int f(t) e^{-i\omega t} dt. The DFT implicitly assumes your signal is periodic with period NN — that's why the path is forced closed. If you draw a spiral that doesn't naturally close, the straight-line closure edge injects a broadband discontinuity whose high-frequency energy spreads across many terms. A continuous Fourier series of a truly smooth closed curve would have exponentially decaying coefficients; the DFT gives you polynomial decay at best, limited by the sampling resolution. Also, the DFT lacks windowing: the hard closure acts like a rectangular window, causing spectral leakage that shows up as noise in the smaller ∣Xk∣|X_k| terms.

If you want to see the same transform applied to actual audio signals — same math, different domain — read the Biquadia internals: the Biquadia deep-dive →

JavaScript, the DFT and reconstruction

These functions run in every browser on this page. The DFT is a textbook double-loop — no FFT, no libraries. Drop it into any canvas project. If you are feeding real-time audio through an audio interface, the same logic ports directly to C or Python for live spectral analysis.

DFT + epicycle reconstruction

function computeDFT(points) {
  const N = points.length;
  const cx = points.reduce((s, p) => s + p.x, 0) / N;
  const cy = points.reduce((s, p) => s + p.y, 0) / N;
  const signal = points.map(p => ({ re: p.x - cx, im: p.y - cy }));
  const terms = [];
  for (let k = 0; k < N; k++) {
    let re = 0, im = 0;
    for (let n = 0; n < N; n++) {
      const phi = (2 * Math.PI * k * n) / N;
      re += signal[n].re * Math.cos(phi) + signal[n].im * Math.sin(phi);
      im += -signal[n].re * Math.sin(phi) + signal[n].im * Math.cos(phi);
    }
    re /= N; im /= N;
    const freq = k <= N / 2 ? k : k - N;
    terms.push({ re, im, freq,
      amp: Math.hypot(re, im),
      phase: Math.atan2(im, re) });
  }
  terms.sort((a, b) => b.amp - a.amp);
  return { terms, centroid: { x: cx, y: cy } };
}

function reconstruct(terms, centroid, t, M) {
  let x = centroid.x, y = centroid.y;
  for (let i = 0; i < M; i++) {
    const { freq, amp, phase } = terms[i];
    const ang = 2 * Math.PI * freq * t + phase;
    x += amp * Math.cos(ang);
    y += amp * Math.sin(ang);
  }
  return { x, y };
}

Two worked examples

Both are solved when the page is built, by the same resampling, DFT and reconstruction functions the canvas runs, and checked in the site's test suite against a separately written DFT, Parseval's theorem and the closed-form series of a square. Lengths are pixels on the 600 × 500 canvas, angles are radians, and kk is whole turns per loop (positive turns clockwise on this y-down canvas, negative the other way). Errors are measured at the sampling instants unless a row says otherwise.

A · The circles of a square

A square 200 px on a side (half-side aa = 100 px), centred on the canvas, resampled to NN = 16 points and rebuilt from its 4 largest circles. The formula holds for any N that is a multiple of 4.

rk=82 aN2sin⁡2(πk/N)  (k≡1 mod 4):r1=116.1 pxr_k = \frac{8\sqrt{2}\,a}{N^2 \sin^2(\pi k/N)} \;(k \equiv 1 \bmod 4):\quad r_{1} = 116.1\ \text{px}
Circle 1 radius (k = 1)
116.1 px
Circle 2 radius (k = −3)
14.32 px
Circle 3 radius (k = 5)
6.393 px
Circle 4 radius (k = −7)
4.594 px
Start angle of every circle
-2.356 rad
Circles with non-zero radius, of 16
4 circles
RMS error with 4 circles
0 px
Farthest the curve strays between samples
3.358 px
Circles for an RMS error of 1 px
4 circles

Turning the square a quarter turn maps it onto itself, which forces every circle's rate to be 1 plus a multiple of 4: here 1, -3, 5, -7. The other 12 circles have radius zero, so 4 circles return all 16 samples. Between the samples the 4 circles draw a smooth loop, not straight edges, and it strays up to 3.36 px from the square.

B · How many circles does the Star preset need

The Star preset (five spikes, outer radius 150 px, inner radius 60 px) resampled to NN = 140 points, the page's own sample count, and rebuilt from its 3 largest circles.

Erms(M)=∑j>Mrj2:Erms(3)=3.454 pxE_{\mathrm{rms}}(M) = \sqrt{\textstyle\sum_{j> M} r_j^2}:\quad E_{\mathrm{rms}}(3) = 3.454\ \text{px}
Circles with non-zero radius, of 140
28 circles
Circle 1 radius (k = 1)
101.6 px
Circle 2 radius (k = −4)
25.84 px
Circle 3 radius (k = 6)
11.53 px
Series energy in 3 circles
99.89 %
RMS error with 3 circles
3.454 px
Largest single error with 3 circles
11.02 px
Circles for an RMS error of 2 px
6 circles

Five-fold symmetry leaves only rates that are 1 plus a multiple of 5: 1, -4, 6, -14, 16 and on, 28 of the 140 circles. The first circle alone is a circle of radius 101.6 px, so 3 circles hold 99.89 % of the series energy and still miss the star by 3.45 px RMS: energy share hides the error because the spikes are small next to the body. The RMS error of M circles is the root of the summed squared radii of the circles left out, so it falls as the radii shrink: 6 circles reach 2 px and all 28 reach zero at the samples.

Frequently asked questions

What is a DFT and how does it decompose a drawing?

The Discrete Fourier Transform (DFT) asks: how much of each pure spin e^(i·2π·k·n/N) lives inside your signal? It takes your N sample points as a complex signal z_n = x_n + i·y_n and projects them onto N complex exponentials — one for each integer frequency k. Each output X_k is a complex number whose magnitude tells you the radius of a circle spinning k times per loop, and whose angle tells you where that circle starts. The shape is literally the sum of those circles drawn tip-to-tail.

What are epicycles and why do they trace any closed shape?

An epicycle is a circle whose center rides on the circumference of another circle. Ptolemy stacked them to model planetary retrograde motion centuries before calculus existed. Fourier proved mathematically that any closed curve — no matter how jagged — can be expressed as an infinite sum of circles spinning at integer multiples of a base frequency. The playground computes a finite approximation using N=140 terms: the largest circles carry the overall silhouette, and the smallest circles add fine detail. The final tip of the last circle is exactly one point on the reconstructed path.

How does the Fourier series relate to this epicycle visualization?

A Fourier series represents a periodic function as a sum of sines and cosines. The epicycle visualization is the same idea rotated into the complex plane: each term X_k·e^(i·2π·k·t) is a circle of radius |X_k| rotating at frequency k. The real part gives x(t), the imaginary part gives y(t). So your drawing on the 2D canvas is literally a periodic complex-valued function z(t) = x(t) + i·y(t), and the DFT gives you its Fourier coefficients. It is the exact same math as a spectrogram, just with the time axis traded for position along the curve.

Why does N=140 limit the capture fidelity? What is the Nyquist limit here?

With N=140 sample points, the highest frequency you can represent without aliasing is k = N/2 = 70 (the Nyquist frequency). Any shape detail smaller than about 1/70 of the loop is undersampled — sharp corners, tight zig-zags, or hand tremor faster than that spatial frequency will alias into lower frequencies. The resampling to 140 uniform arc-length points also erases your drawing speed: fast strokes and slow strokes become equally dense along the path, so only the geometry matters. Shapes with features finer than ~1/140 of the total arc length will be blurred or lost.

How does signal reconstruction work from DFT coefficients back to the drawing?

Reconstruction is the synthesis step: start at the centroid, add the largest circle first (its tip becomes the center of the next), then chain circles tip-to-tail in amplitude-sorted order. At any time t between 0 and 1, each term contributes r_k·cos(2π·f_k·t + φ_k) to x and r_k·sin(2π·f_k·t + φ_k) to y. The final (x, y) is a point on the reconstructed curve. Using all N terms passes exactly through every one of the N sample points; between them the rebuilt curve can stray slightly from the straight segments you drew. Using fewer terms — controlled by the slider — keeps the biggest circles and drops the smallest, which gives a smoothed approximation. Because the order is by size rather than by speed it is not strict low-pass filtering, though on most hand-drawn shapes the small circles are mostly the fast ones. This is the same principle behind JPEG compression (dropping high DCT coefficients) and MP3 encoding (dropping inaudible high frequencies).

What is a Fourier epicycle?

A Fourier epicycle is one term of the discrete Fourier series of a closed path: a circle with a radius, a whole number of turns per loop (negative turns the opposite way) and a starting angle. Chained tip to tail, the circles trace the path. In the first worked example above, a square 200 px on a side sampled at 16 points comes out as 4 circles: the largest has radius 116.1 px and turns 1 time per loop, the next has radius 14.32 px and turns 3 times per loop in the opposite direction.

How do I make an epicycle drawing of my own shape?

Draw a closed shape on the canvas with a mouse or finger, or press one of the six presets (Circle, Star, Infinity, Heart, Spiral, MP). When you lift the pen the page resamples your stroke to 140 evenly spaced points, closes the loop, computes the DFT and plays the largest circles back tip to tail; the Terms slider sets how many circles are used, up to 60. The page takes drawings from the pointer only: it does not import image or SVG files and does not export the list of circles.

Is this a Fourier series drawing generator?

It is the analysis half of one. It turns a closed shape into a Fourier series of 140 samples (a radius, a turns-per-loop rate and a start angle for each circle) and animates the result, so you can see how a drawing breaks into circles. It does not turn text or images into a drawing, and it shows the circles on screen rather than exporting them. The JavaScript section lists the DFT and reconstruction functions, which you can copy into your own canvas project.

How many epicycles does it take to draw a shape?

It depends on the shape and on how close counts. For the five-pointed Star preset at 140 samples, 6 circles bring the RMS distance between the samples and the rebuilt points down to 2 px, while 28 circles are needed to get all 140 samples back exactly. With only 3 circles the star keeps 99.89% of the series energy but is still 3.454 px off in RMS terms. A square sampled at 16 points needs 4 circles to hit its samples exactly, yet the curve between samples still strays up to 3.36 px from the straight edges.

Where are Fourier epicycles used?

As a drawing, epicycles are a teaching and demonstration device: they show what a Fourier series does to a closed curve, and Ptolemy stacked circles in the same way to model planetary motion. The mathematics underneath is used widely: the DFT turns audio into spectra, and compression schemes such as JPEG and MP3 drop small high-frequency coefficients. Describing a closed outline by the leading coefficients of its complex DFT is known as Fourier descriptors in shape analysis. This page is a teaching tool and does none of those jobs itself.

Shareable still

The instrument, captured—not illustrated.

This 16:9 frame is rendered from the real browser instrument above. It is the page's canonical preview for image search, link unfurls, and posts that need to show what the tool actually does.

Download 1280 × 720 JPEG
Draw → Fourier Epicycles — live MakerPortal instrument screenshot
Canonical capture · real UI · no generated scientific artwork