drag the slider: how many harmonics to use, 1 to 127 ← →: nudge the harmonic count by one D: draw your own path — drag, and it closes itself when you let go P: cycle the built-in presets: STAR, CLOVER, GEAR space: pause C: show/hide the circles R: restart the trace The readout shows the harmonic count and the RMS error of that truncation in stage pixels. Start at 1 — a single harmonic is always a circle, whatever you drew — and walk it up.
Draw a closed shape with the mouse and watch its discrete Fourier transform take it apart into a chain of rotating circles whose last tip redraws it. Slide the harmonic count and watch a blob sharpen into your own handwriting. WHAT IT ACTUALLY COMPUTES The path is resampled to 128 points spaced evenly by arc length, each point read as a complex number x + iy, and transformed: X[k] = (1/N) sum over n of p[n] exp(-2piikn/N) Every coefficient is one circle: |X[k]| is its radius, arg X[k] its starting angle, and k its turns per lap — with k past the halfway point read as k-N, which is exactly what makes the second half of the spectrum the circles that turn the other way. Stack them tip to tail, biggest first, and the final tip traces the curve. Arc-length resampling is not cosmetic. Spacing the samples evenly in the index instead would put most of them wherever your hand moved slowly, and the transform would then see a shape distorted by how fast you drew it. THE TWO DECISIONS THAT MADE IT AFFORDABLE A twiddle table. cos(-2pik*n/N) only ever takes 128 distinct values, because the argument is periodic in (k*n) mod N. One 128-entry table turns the inner loop's four trig calls into two list lookups, so the entire 16,384-term transform contains no transcendental calls at all. Cosine is even and sine is odd, so the conjugate needs no second table. Parseval for the error readout. The obvious way to measure how good H harmonics are is to reconstruct all 128 points and compare — 128 * H terms every time the slider moves, which would make the slider unusable. But the discrete exponentials are orthogonal, so the mean squared error of dropping a set of harmonics is exactly the sum of their squared amplitudes. A single prefix sum over the amplitude-sorted spectrum turns the whole error curve into one subtraction and one square root, and it is exact rather than approximate. VERIFICATION tools/mkbanner.py is a numpy port of the same transform, and it draws the thumbnail — so the picture on the browse page is this project's real output. It is also how the maths was checked. The project was run headless, its 183 recorded trace points were pulled back out with the harmonic count set to 60 by dragging the slider, and each was compared against the numpy reconstruction at the same phase: - max and mean deviation 0.0000 px over all 183 points (below 5e-5) - the reconstruction sits on the target: max 0.51 px, mean 0.24 px from the 128 target vertices - the project's own Parseval readout, 0.0487 px, matches numpy to four decimals The most reassuring check was not numerical. For the 5-point STAR the eight strongest signed frequencies come out as 1, -4, 6, -14, 16, -9, 11, -24 — and a shape with 5-fold symmetry can only have harmonics at 1 + 5m. Every one of those is. Nothing in the code knows the star has five points. Measured convergence on STAR, which is what the slider is showing you: | harmonics | 1 | 2 | 3 | 5 | 8 | 16 | 32 | 48 | 64 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | rms error, px | 22.1 | 9.3 | 2.6 | 1.5 | 0.77 | 0.27 | 0.11 | 0.06 | 0.04 | CLOVER is smooth and reaches 0.00 px by 32 harmonics — it is a band-limited curve and the tail of its spectrum is genuinely empty. All original - code, art and sound. See Inside is open.