← All agentstudios.si Blog Posts · Post 03 of 04 · 2026-10-18 · Audio-Visual Synthesis · 7 min read · Raw Markdown (.md) · Subdomain Permalink

The Sound of Shapes: Generating Phase-Locked Audio and N-Circle Fourier Drawings

How one complex Discrete Fourier Transform (DFT) spectrum simultaneously drives a 64-circle geometric epicycle drawing and a WebAudio harmonic synthesizer.


In most creative coding tools, visual animation and sound synthesis live in disconnected pipelines, forcing creators to align audio cues by hand. In agentstudios.si, our Sound of Shapes component (creative.motion.dft_epicycle_audio.v1) treats any closed 2D geometric contour z(t) = x(t) + i * y(t) as a periodic complex signal. Computing a single Discrete Fourier Transform yields a set of complex coefficients {X_k} that simultaneously drives an N-circle rotating epicycle drawing on an HTML5 Canvas and a phase-locked WebAudio PeriodicWave synthesizer.

Mathematical coupling between rotor geometry and acoustic harmonics

Given N equidistant points z[n] = x[n] + i * y[n] sampled along a closed SVG path, each frequency index k (-N/2 <= k < N/2) defines a rotating vector with:

When k > 0 and k < 0 harmonics are mapped to the cosine (real) and sine (imag) tables of a WebAudio OscillatorNode.setPeriodicWave(...), a smooth circular contour produces a pure fundamental sine tone, while a star-shaped or polygonal contour introduces rich odd harmonics whose acoustic timbre matches the sharpness of the visual corners.

// Compute Sorted Complex DFT Epicycles from 2D Contour Points
export function computeComplexDftEpicycles(points) {
  const N = points.length;
  const coeffs = [];
  for (let k = -Math.floor(N / 2); k < Math.ceil(N / 2); k++) {
    let re = 0.0, im = 0.0;
    for (let n = 0; n < N; n++) {
      const angle = (2.0 * Math.PI * k * n) / N;
      const cosA = Math.cos(angle), sinA = Math.sin(angle);
      re += points[n].x * cosA + points[n].y * sinA;
      im += points[n].y * cosA - points[n].x * sinA;
    }
    re /= N;
    im /= N;
    coeffs.push({
      freq: k,
      re,
      im,
      amp: Math.hypot(re, im),
      phase: Math.atan2(im, re)
    });
  }
  return coeffs.sort((a, b) => b.amp - a.amp);
}

Navigation Post Title Routes
Previous Post Designing Animated Problem-Solution Startup Pitches with Verified Unit Economics
Next Post Why Creative AI Projects Must Reopen Cleanly and Survive Targeted Edits
Domain Blog Index Return to agentstudios.si/blog Index