Aether Field has no scripted animation. Everything you see is a real 2-D fluid simulation, driven by the music, with thousands of particles riding the flow like dye in water. Here's the whole machine, equation by equation.
The velocity field u(x,t) obeys the incompressible Navier–Stokes equations —
the same equations that govern smoke, water, and weather:
Each frame the solver runs four steps on a coarse grid. The trick that makes it unconditionally stable is semi-Lagrangian advection: instead of pushing values forward, every cell traces backward along the flow and samples where it came from.
A raw velocity field expands and collapses (it has divergence). To make it read as a real fluid we solve a Poisson equation $\nabla^{2}p = \nabla\cdot\mathbf{u}$ by relaxation and subtract its gradient. The divergence and the in-place (Gauss–Seidel) update are:
≈22 Jacobi/Gauss–Seidel sweeps per frame roughly halve the field's divergence — the normal operating point for a real-time solver.
An FFT of size 2048 gives 1024 frequency bins per frame. From them we extract a feature vector $A(t) = [\,\text{rms},\ \text{bass},\ \text{mid},\ \text{treble},\ \text{centroid},\ \text{flux},\ \text{onset},\ \dots]$.
The centroid is the spectrum's "center of mass" (perceived brightness); flux measures how fast the spectrum is changing. A beat is an adaptive threshold on flux:
So quiet and loud tracks both fill the visuals, every feature is normalized against slowly-contracting, instantly-expanding rolling extremes, and a long-term level is an exponential moving average:
Audio never moves particles directly. Instead the song reduces to a 48-band log-spaced spectrum, and the central black hole's surface becomes a circular spectrum analyzer — bass at the bottom, treble at the top, mirrored left/right. Each band $b$ injects a radial wave, measured by distance $r$ from the surface:
Because the wave is injected as a body force, the projection step turns it into genuine outward-propagating rings. Other bands steer global behavior: sub-bass breathes the whole field, low-mid speeds the accretion swirl, mid + flux sharpen vorticity, and energy sets the viscosity $\nu = \mathrm{mix}(\nu_{\text{high}}, \nu_{\text{low}}, E)$.
The core is a gravity well. A tracer at distance $d$ from the core (radius $r_0$) feels a direct pull that falls off faster than Newtonian, and is consumed if it crosses the event horizon:
With Adaptive mode on, the Responsiveness dial $R\in[-1,2]$ scales how hard the core reacts to the music and flips the force:
When $R<0$ the pull reverses — particles are pushed away and nothing is swallowed: a white hole (rendered as a bright orb instead of a dark void). The four quadrant speakers work the same way but push outward by default, scaled by each speaker's band; negative responsiveness makes them attract:
Particles don't bounce on random forces — that looks like noise. They are tracers that follow the fluid, exposing its streamlines and eddies, with a little inertial lag so trails curl smoothly:
The velocity $\mathbf{u}$ lives on a coarse grid but particles live at continuous positions, so we bilinearly interpolate the four surrounding cells:
A particle's hue is the local fluid "temperature" — a weighted blend of how fast and how spinny the flow is, plus a little audio:
Defaults $a{=}0.5,\ b{=}0.25,\ c{=}0.32,\ d{=}0.2,\ e{=}0.2$. Velocity and vorticity vary across the field so they lead; the audio terms are the same everywhere, so they're kept small to avoid washing the whole field one color. $T$ indexes a 256-entry color ramp (deep blue → cyan → green → gold → hot pink → white-gold), smoothstep-interpolated.
Vorticity is the local spin; in 2-D it is the scalar curl. Numerical diffusion erases small eddies, so vorticity confinement re-injects energy back into spinning regions:
The dotted frequency ribbons are sine waves whose amplitude per band is the spectrum. They run on distance from the center, so each axis folds into mirror-image halves that radiate out of the core (a Gaussian envelope makes them swell at the center and flatten at the edges):
The fluid grid is tiny and cheap, so it always runs on the CPU. The scalable cost is the particles, and there are two ways to render them.
A JavaScript loop over every particle: sample the velocity (bilinear), integrate, then
drawImage a pre-baked additive glow sprite. Simple and exact, but each particle costs CPU
time, so it's capped at a few tens of thousands. Density presets (Low … Max) pick the count.
Particle state lives in GPU buffers and is advanced by a transform-feedback vertex shader — the same advection equations, run for every particle in parallel. This scales to 100k–1M+ tracers.
To advect on the GPU, the CPU's velocity / vorticity / dye grids are packed into a single RGBA floating-point texture each frame. The shader samples it with the exact coordinate transform the CPU's bilinear sampler uses:
The shader integrates position/velocity into a second buffer (ping-pong), then a render pass draws additive point sprites colored through the temperature ramp (uploaded as a lookup texture). The particles are drawn to an offscreen GL canvas and blitted additively into the 2-D canvas at the same layer the CPU draw occupied — so trails, glow, and layering match either way.
Because a GPU field can hold 10–100× more particles, naive additive blending washes out to a flat glow. So per-particle brightness is scaled toward a roughly constant total light, keeping fast particles bright and slow ones dim at any count:
Built with vanilla JavaScript and the Web Audio API — no game engine, no shaders authored by hand beyond
the particle passes. The deep reference for every symbol here lives in the project's
README.md. Math typeset with KaTeX.