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

Published: 2026-10-18 | Domain: https://agentstudios.si | Component: `creative.motion.dft_epicycle_audio.v1`

## Outcome first: unified mathematical representation for geometry and sound

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:

- **Radius (amplitude)**: `R_k = |X_k| = hypot(Re(X_k), Im(X_k))`
- **Initial phase**: `phi_k = atan2(Im(X_k), Re(X_k))`
- **Angular velocity**: `omega_k = 2 * pi * k / T_period`

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.

```javascript
// 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);
}
```
