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 only the centroid (the DC term, which does not spin — it is the average position). 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=((nowloopStart)mod9000)/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=0N1z~nei2πkn/N,k=0N1X_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 kN/2k \le N/2 to positive (counter-clockwise) and k>N/2k > N/2 to kNk-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 (spikes at k=±5,±10,k=\pm5,\pm10,\dots), 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 kN/2 else kNf_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: positive = counter-clockwise, negative = clockwise. 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 · 37 picks

Audio DSP tools37

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)+iy(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=1Nnznc = \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 1601 \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 010 \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=argXk=atan2(ImXk,ReXk)\phi_k = \arg X_k = \operatorname{atan2}(\operatorname{Im} X_k, \operatorname{Re} X_k)   start angle
fk=kf_k = k   turns per loop, negative = clockwise

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 140219600140^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=0N1n=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=(xncx)+i(yncy),cx=1Nn=0N1xn,  cy=1Nn=0N1yn\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=0N1z~nei2πkn/N,k=0N1X_k = \sum_{n=0}^{N-1} \tilde{z}_n\, e^{-i 2\pi k n / N},\qquad k=0\dots N-1eiθ=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 kNk-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+1NkXkei2πktc+kkeptrkei(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=argXkr_k = |X_k|/N,\quad \phi_k = \arg X_k

In animation t=((nowloopStart)modLOOP_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+rkcosangkx_{k+1}=x_k+r_k\cos ang_k, yk+1=yk+rksinangky_{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 get low-pass filtering. Dropping high |k| 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,,XN1],  N=140\mathbf{X} = [X_0,\dots,X_{N-1}],\; N=140

140 complex coefficients, DC removed for origin. Sorted view keeps M60M\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={kkN/2kNk>N/2f_k = \begin{cases} k & k\le N/2\\ k-N & k > N/2 \end{cases}

fk>0f_k > 0 counter-clockwise, fk<0f_k < 0 clockwise. 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. Slider is literally a bandwidth knob.

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 206020\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. Slider 121\to2 jumps from dot at centroid to perfect circle. Pure tone in audio terms.

Star: 5 points = 5-fold symmetry. Spectrum spikes at k=±5,±10,k=\pm5,\pm10,\dots. 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 — dot at centroid cc. You removed all rotation, left only DC. Slightly anticlimactic, proves DC handling works.

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/14043%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 star, k=5k=5 outranks k=2k=2 despite higher frequency, because symmetry demands it.

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)eiω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 };
}

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 gives you the exact input shape back. Using fewer terms — controlled by the slider — is low-pass filtering: you drop the smallest, fastest circles and get a smoothed approximation. This is the same principle behind JPEG compression (dropping high DCT coefficients) and MP3 encoding (dropping inaudible high frequencies).

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