audio_reactive · PasmaReactiveCircle
Reactive Concentric Rings (FFT-Band-Driven)
N concentric rings around the canvas centre, each driven by a different FFT band (low / mid / high). Each ring's radius, thickness, and hue are modulated by the magnitude of its band; a global 1 Hz kick impulse momentarily inflates every ring (the beat-driven throb). Rings also slowly rotate with per-ring phase offsets — Pasma's 'interesting rotating things' approach, where simple concentric structures turn into one another over time. The colour ramp uses the FFT-band magnitudes as a 3-stop HSV gradient; the result is read against a dark radial vignette, like Pasma's *Epihyperderpflardioids* project on a phone screen.
Original: http://piterpasma.nl/articles/rotating
▶ Try the live demo (JS port) ▶ Run reference.py via Pyodide 10 formulas have instant JS ports. The Pyodide path runs the actual Python in your browser — first load is ~6 MB, then instant.
Math essence
rings = linspace(ring_min*W/2, ring_max*W/2, N). For each ring i: low = Σ low_amp_k * sin(2π low_freq_k * t + phase_k); mid, high analogously. radius_i = base_radii[i] + ((1-u)*low + u*high) * W * 0.04 + kick * W * 0.5; thickness_i = base_thick + 0.7 * tanh(mid * 4). ink_i = HSV(hue_base + u * hue_per_ring*6 + 180*(mid-low), 0.65+0.35*tanh(high*4), 0.85+0.15*tanh((low+mid+high)*3)). Draw ring at radius_i with thickness_i; canvas *= 0.92 per step.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 400 | 200–800 | Square canvas side length in pixels. |
num_rings | int | 18 | 3–48 | Number of concentric rings. |
ring_min | float | 0.1 | 0.02–0.45 | Innermost ring radius as a fraction of canvas half-width. |
ring_max | float | 0.46 | 0.3–0.49 | Outermost ring radius as a fraction of canvas half-width. |
ring_thickness | float | 1.4 | 0.5–4 | Base ring thickness in pixels. |
low_freq | tuple | 0.21,0.51,0.83 | — | Low-band centre frequencies. |
mid_freq | tuple | 1.1,1.9,2.7 | — | Mid-band centre frequencies. |
high_freq | tuple | 3.5,5.1,7.4 | — | High-band centre frequencies. |
low_amp | tuple | 0.2,0.14,0.08 | — | Low-band amplitudes. |
mid_amp | tuple | 0.14,0.1,0.07 | — | Mid-band amplitudes. |
high_amp | tuple | 0.08,0.06,0.04 | — | High-band amplitudes. |
kick_freq | float | 1 | 0.1–6 | Beat / kick frequency (Hz). |
kick_strength | float | 0.06 | 0–0.2 | Strength of the kick impulse (as fraction of canvas half-width). |
rotation_speed | float | 0.07 | -0.4–0.4 | Per-step rotation rate of the ring arcs (radians/step). |
hue_per_ring | float | 18 | 0–90 | Hue shift per ring (degrees, multiplied by 6 internally). |
bg_color | tuple | 4,4,10 | — | Background colour. |
fg_color | tuple | 235,235,245 | — | Foreground tint. |
seed | int | null | 0–999999 | RNG seed. |
Reference
First 60 lines of reference.py. Full source:
library_external/audio_reactive/PasmaReactiveCircle/reference.py
from __future__ import annotations
from typing import ClassVar
import numpy as np
class PasmaReactiveCircle:
"""Pasma-style reactive concentric circles.
N concentric circles around the canvas centre. Each ring has
its own "frequency band" — a synthetic sin chain (low / mid /
high) whose instantaneous magnitude drives (a) the ring's
radius offset, (b) its thickness, and (c) its hue. A global
"kick" signal (1 Hz impulse train) momentarily inflates
every ring's radius, like a beat-driven throb. Rings also
have a slowly rotating phase, evoking Pasma's "interesting
rotating things" article (parametric curves that slowly turn
into one another).
The colour ramp uses the FFT-band magnitudes as a 3-stop
gradient (low→blue, mid→magenta, high→white). Background is a
dark radial vignette so the rings read as bright halos, the
way Pasma's *Epihyperderpflardioids* project reads on a
phone screen.
"""
family: ClassVar[str] = "audio_reactive"
sketch: ClassVar[str] = "pasmaareactivecircle"
def __init__(
self,
dim: int = 400,
num_rings: int = 18,
ring_min: float = 0.10,
ring_max: float = 0.46,
ring_thickness: float = 1.4,
low_freq: tuple[float, ...] = (0.21, 0.51, 0.83),
mid_freq: tuple[float, ...] = (1.1, 1.9, 2.7),
high_freq: tuple[float, ...] = (3.5, 5.1, 7.4),
low_amp: tuple[float, ...] = (0.20, 0.14, 0.08),
mid_amp: tuple[float, ...] = (0.14, 0.10, 0.07),
high_amp: tuple[float, ...] = (0.08, 0.06, 0.04),
kick_freq: float = 1.0,
kick_strength: float = 0.06,
rotation_speed: float = 0.07,
hue_per_ring: float = 18.0,
bg_color: tuple[int, int, int] = (4, 4, 10),
fg_color: tuple[int, int, int] = (235, 235, 245),
seed: int | None = None,
) -> None:
self.dim = dim
self.num_rings = num_rings
self.ring_min = ring_min
self.ring_max = ring_max
self.ring_thickness = ring_thickness
self.low_freq = np.array(low_freq, dtype=np.float32)
self.mid_freq = np.array(mid_freq, dtype=np.float32)
self.high_freq = np.array(high_freq, dtype=np.float32)
self.low_amp = np.array(low_amp, dtype=np.float32) Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/audio_reactive/PasmaReactiveCircle/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from audio-reactive
Daily Sketch Curve Morph (Synthetic Gesture)
audio_reactive · LiebermanDailySketch
A parametric polar / hypotrochoid curve whose three radii (R, r, d) are continuously morphed by independent sin-wave chains — Zach Lieberman's signature 'use mathematical functions like sin() and tan() in place of randomness' rule from his Daily Sketches project.
data.matrix Glitch Grid (Binary Cellular Automaton + Beat Faders)
audio_reactive · IkedaGridMatrix
A 32×32 binary cell grid plus a thin frame of beat-fader bars around the edges.
FFT-Spectrum Particle Field (Synthetic Audio)
audio_reactive · PasmaFFTSpectrum
A particle field whose velocity is driven by three FFT-magnitude bands (low / mid / high) computed from a sliding window of synthetic audio.
SND Grid Sequencer (Multistability Pulse Grid)
audio_reactive · SNDGridSequencer
A 16×16 grid where each cell has its own activation period (inner-loop ticks) and phase offset, modelled after Mark Fell's *Multistability* algorithm: nested inner/outer loops, impulse at each inner-loop wrap, phase-reset by the outer loop.