Drag poles and zeros over a 3D |H(z)| surface and hear the filter you built ring.
The filter is a cascade of up to six biquads, each one a conjugate pair of poles and a pair of zeros on the z-plane. The big view evaluates log |H(z)| over a polar mesh and paints it as a lit, painter-sorted 3D surface: poles raise tent poles, zeros dig wells, and the ring of the mesh at |z| = 1 is the frequency response itself, traced in light. Beside it sit a flat z-plane heat map, the live biquad coefficients and a Bode plot, where a sawtooth or white noise pushed through the very same cascade is measured by FFT and lands right on the theoretical curve. With sound on, that cascade runs sample by sample at 16 kHz, so moving a pole toward the unit circle makes a resonance you can hear ring.
Try it. Drag the poles (x) and zeros (o) on the surface or on the flat z-plane, and drag empty space to orbit. Click the z-plane to drop a pole pair (shift-click for a zero pair), press P or Z to add one at the pointer, and X or right-click to remove one. Pick Comb, Vowel, Lowpass, Highpass, Notch or Wah (keys 1 to 6), switch between sawtooth and noise with Space, and click for sound.
Paste this into Claude Code, Codex or any coding agent to get a simple version running, then take it wherever you like.
Build an interactive pole-zero filter designer with JavaScript and the HTML canvas element. Put everything in a single index.html file with no libraries or build step, so I can open it directly in a browser.
Start simple:
- Make a full-window canvas that stays sharp on high-DPI screens (scale by devicePixelRatio) and resizes with the window.
- On the left, draw the z-plane: axes and the unit circle. Keep a list of poles and zeros as complex numbers, each one standing for a conjugate pair (so the filter stays real). Draw poles as x marks and zeros as circles, mirrored below the real axis.
- Let me drag them, and clamp poles inside radius 0.99 so the filter stays stable.
- Each pole pair p and zero pair q is one biquad section: (1 - 2 Re(q) z^-1 + |q|^2 z^-2) / (1 - 2 Re(p) z^-1 + |p|^2 z^-2).
- On the right, draw the magnitude response: evaluate the product of all sections at z = e^(i w) for w from 0 to pi and plot 20 log10 |H| against frequency on a log axis. Normalize so the peak sits at 0 dB.
Once that works, make it beautiful:
- Behind the z-plane, color every pixel of a small offscreen canvas by log |H(z)| and scale it up, so poles glow and zeros are dark wells.
- Add a button that starts Web Audio only after a click, plays white noise or a sawtooth through an IIRFilterNode (or a ScriptProcessor running the same biquads) and rebuilds the filter as I drag, so I can hear a pole near the unit circle ring.
- Add presets: a lowpass, a notch (zeros on the circle, poles just inside), and three formant resonances that make a vowel from a sawtooth.
Explain the key ideas in short code comments. When you're done, tell me how to open it and suggest three directions I could take it next, such as drawing |H(z)| as a 3D surface over the plane, overlaying a measured FFT of the output, or adding a phase plot and group delay.