Web AudioWebGL2GLSLTypeScript
Controls
- Press Play to start the built-in track (sound never starts by itself); Track, Mic and File switch the source
- Mic asks for microphone access only when you press it; the microphone is analysed, never played back
- File opens an audio file from your device, or drop one anywhere on the picture; it plays on a loop and is not uploaded
- Mute and the volume fader set the sound level; the picture keeps reacting at volume 0
- Prism, Ember and Abyss switch the colour preset; Tune opens sliders for beat sensitivity, smoothing, spikes, glow and spin
- YouTube opens three preset songs and a field for your own watch, youtu.be or Shorts link; in desktop Chrome and Edge the orb listens after you share this tab with its audio, elsewhere it follows the song's beat; Never Gonna Give You Up turns the orb pink and gold and the beats spell never gonna
- Move the mouse over the picture to tilt the orb
- Keyboard: Space plays or pauses, T track, I microphone, O open a file, Y YouTube, M mute, up and down arrows change the volume, 1 2 3 presets, Esc closes the panels
- Add ?quality=low to the address to force the light tier
Needs WebGL2 and Web Audio (current Chrome, Edge, Firefox and Safari). The microphone needs a secure page and your permission; if it is blocked the page says so and the track still works. YouTube videos load from youtube-nocookie.com only after you pick one; listening to them needs desktop Chrome or Edge and a tab share with audio (the picture track is dropped at once), otherwise the orb follows a beat at the song's tempo. Files play in any format the browser can decode (MP3, WAV, OGG, M4A in most browsers). Without WebGL2 the poster is shown; if the graphics context is lost, a button restarts the visuals while the sound keeps playing. Drops to a lighter tier by itself when frames get slow. Beat pulses never flash more than three times a second; under reduced motion there are no pulses, the orb barely turns and the picture is a still frame until you press Play.
How it’s built
An audio visualizer: a raymarched orb whose surface is pushed out by the live spectrum, circled by a waveform ring and a crown of spectrum bars. It listens to a track composed in code and synthesised live, to the microphone, or to an audio file from your device.
How it works
- Raw WebGL2 and Web Audio, no engine and no new dependencies. One full-screen fragment shader per frame.
- Analysis: an
AnalyserNode with a 2048-sample FFT (1024 bins). The bins are folded into 128 log-spaced bins from 30 Hz to 16 kHz (so the bass gets as much room as the treble) and uploaded together with 128 waveform samples as a 128 × 2 byte texture every frame. Bass (30–160 Hz), mids (160–2400 Hz) and highs (2.4–12 kHz) are the mean loudness of their bins, followed with a fast attack and a slower release. - The orb: sphere tracing of a displaced sphere. The latitude of each surface point picks the frequency (bass at the equator, treble at the poles), ridges around the orb shape the spikes, and value noise adds a slow wobble driven by the mids. The distance is scaled down so the displaced field stays safe to march (80 steps on the high tier, 44 on the light one). Shading mixes a three-colour preset palette (shifted by the mids), a thin-film fresnel rim, reflections of a procedural studio and sparkles that glint with the highs.
- Surface detail: a fine noise layer that grows with the highs, glowing contour lines of the displacement (the spectrum reads as terrain), a hairline latitude/longitude grid so the spin reads, and darker valleys between the spikes.
- Deep space behind it: a slow two-layer fbm nebula tinted by the preset, three parallax layers of twinkling stars, a banded gas giant with a storm eye and a lane-striped ring (back half behind the planet, front half over it, the planet's shadow on the ring) and two cratered moons. The orb is their sun: every body is lit from the centre with a soft terminator and an atmosphere rim. Bodies shift a little with the pointer and move to the top and bottom on portrait screens.
- Around it: the waveform ring, 96 mirrored spectrum bars, a halo from the raymarch's closest distance, drifting dust that twinkles with the highs, and a shock ring on each beat; ACES tone mapping, vignette and grain finish the frame.
- Beats: a bass sample clearly above its running mean (the ratio is set by the sensitivity slider), on a rising edge, and at least 340 ms after the last beat. The pulse is a 40 ms rise and an exponential fall; it lifts the overall exposure by at most 12 percent. Both limits keep the picture under three flashes a second.
- The built-in track: 104 BPM in A minor, a 16-bar arrangement (intro, drive, breakdown) over an i–VI–III–VII chord loop. Every sound is made on the spot: a sine kick with a pitch drop, snare and hats from filtered noise, a saw and sub bass through a resonant low-pass, a detuned saw pad and a triangle arpeggio through a dotted-eighth echo, with a reverb whose impulse response is generated noise. Notes are scheduled 140 ms ahead on the audio clock from a 25 ms timer, so timing stays tight even when the main thread is busy. The score is a pure, unit-tested function.
- Graph: source → bus → compressor → analyser → master gain → speakers. Volume and mute act after the analyser, so the picture reacts at volume 0. The microphone connects to the analyser only and is never played back (no feedback).
YouTube
- A YouTube video's sound is cross-origin, so it cannot be connected to an
AnalyserNode. On desktop Chromium the same click that picks the video calls getDisplayMedia({ audio: true, video: true, preferCurrentTab: true }); with "Share tab audio" ticked, the tab's audio track goes to the analyser only (never to the speakers, the tab already plays it) and the video track is stopped at once. - Everywhere else, or when the share is declined or has no audio, the orb follows a synthetic beat at the song's published tempo (a kick envelope on each beat, hats on the off-beats), and the player card says it is following the beat, not listening.
- The embed is
youtube-nocookie.com, created only after a click (the player is about 1 MB). Links are parsed to an 11-character ID (watch, youtu.be, Shorts, embed and live links; unit-tested) and only the ID reaches the embed URL. - Never Gonna Give You Up switches to a pink and gold palette and every beat throws the next letter of "never gonna" off the shock ring (CSS transform and opacity; a still caption under reduced motion).
Lifecycle
- No audio before a click: the
AudioContext is created by the first Play, Track, Mic or File press. - Pausing suspends the context. Unmounting stops the synth, the file, every microphone and tab-capture track and closes the context; the YouTube iframe unmounts with the page.
- The frame loop stops off-screen and in hidden tabs. Before any audio plays the orb breathes to a slow idle signal; under reduced motion that is a still frame.
- A quality governor drops to the light tier (lower resolution, fewer steps) when frames stay above 24 ms for two seconds. On context loss a button rebuilds the renderer while the sound keeps playing.