ascii audio interpreter
pick an example or upload a track
overview
live ascii visualizer built for the wnur 89.3 fm website. audio drives a flow field of particles that write into a monospace grid: bass hits add impulse, mids warp the field, treble adds detail. try the demo above with the example tracks or upload your own file. on this page, ink color follows the frequency content (low bands warm, high bands cool) and shifts with the mix over time. the production version is live on wnur.org behind the station name, tuned calmer and in wnur's purple palette. this page runs a vanilla port in ascii-audio-interpreter.js with the same flow-field tuning as the live hero canvas.
features
- particles follow a swirling flow field and deposit ink as ascii characters (
.:-=+*#%@) - bass, mids, and treble each do different things to the motion and detail
- colors shift with the spectral mix over time
- works with uploaded files or the example tracks in the demo above
- live on wnur.org reacting to the broadcast stream in real time
how it works
- the web audio api reads frequency and time-domain data from an audio source through an
AnalyserNode. - hundreds of particles follow a swirling flow field. their paths deposit ink into a 2d grid aligned to monospace character cells.
- each animation frame, the grid renders as ascii characters (
.:-=+*#%@). denser cells get heavier symbols and colors mapped from each cell's stored hue. - on wnur.org, the hero canvas sits behind the station name and crossfades to black type while the stream is playing. that integration is not part of this page.
algorithms
not one named algorithm. each frame chains signal processing, particle simulation, and grid rendering. no machine learning, no full fluid simulation.
signal processing
fft: theAnalyserNoderuns a fast fourier transform and the code reads magnitude bins from the spectrumfrequency-band aggregation: fft bins get averaged over hand-tuned ranges (bass, mid, treble, etc.), like a filter bank but computed directly on fft outputspectral flux: sum of positive frame-to-frame bin changes, a standard mir feature for catching transients and onsetsrms: root-mean-square amplitude from the time-domain waveform bufferdynamic range compression: theamplify()power curve maps raw levels into a 0-1 control range
simulation
particle system: hundreds of lagrangian tracer particles, each with position and velocityvector field advection: particles follow a precomputed procedural flow field instead of forces or collisionsprocedural noise field: layeredsin/costerms (curl-noise-like, but not perlin or simplex)explicit euler integration:position += velocityeach framevelocity steering: linear interpolation of velocity toward the field direction (a simplified reynolds-style alignment rule)
grid and rendering
discrete convolution: a 3×3 kernel deposits ink into neighboring cells as particles moveexplicit diffusion (laplacian stencil): four-neighbor averaging each frame, a discrete heat-equation step on density and hueexponential decay: cell density gets multiplied by a constant < 1 every frame so trails fadecircular mean: hue is averaged viaatan2(Σ sin, Σ cos)so hue wraps correctly at 0/1quantization: continuous density maps to discrete ascii glyphs (.:-=+*#%@)color lookup table: a 36×7 hsl table maps stored hue and intensity to rgb
the math
each frame, an AnalyserNode fills frequency and time-domain buffers. those numbers drive particle motion, how much ink lands, and color. desktop demo tuning lives in ascii-audio-interpreter.js.
audio
- fft bins get grouped into bass, mid, treble, vocals, synth, and air, plus eight equal slices (one per particle band). each group mixes mean and peak energy, then scales with a band-specific gain.
- compressive curve used everywhere:
amplify(v, g) = min(1, (max(0, v) * g)^0.82) - example bands:
bass = mixRange(freq, 1, 20, 7),mid = mixRange(freq, 16, 96, 6.5) - spectral flux (positive bin-to-bin change vs last frame) and time-domain rms feed an
activityscore that gates when the field goes fully reactive
motion
kickandsplatterare smoothed envelopes, not raw levels. kick reacts to bass jumps; splatter also picks up volume spikes, flux, and vocal hits.- kick (reactive):
kick = max(0, 0.78*kick + 6*bassHit + 5*transient) - particles follow a synthetic curl field: position relative to a drifting center, scaled, then passed through layered
sin/costerms. mids warp a ripple term on top. - velocity blends toward the field direction each frame. bass, rms, and kick raise travel speed. a hit adds
v += (cos θ, sin θ) * kick * 0.32.
ink and color
- each monospace cell stores
densityandhue. particle paths deposit ink through a 3×3 kernel. strokes step along the path so trails stay continuous. - new ink merges hue by weight:
hue = (prev*hue + w*inkHue) / (prev + w) - each frame, density decays and diffuses to four neighbors. hue diffuses too, averaged on a circle so color bleeds with the ink.
- density maps to
.:-=+*#%@. when playing, a 36×7 hsl lookup table colors cells from stored hue and density. particle hue starts atband / 8(warmer low, cooler high) and shifts with the spectral mix.globalHueShiftdrifts the whole palette with the track.
stack
- canvas 2d + requestanimationframe (no webgl)
- web audio analyser with custom band aggregation for bass / mid / treble
- react + typescript on wnur.org; this project page runs an independent vanilla js port
related work
the full wnur website write-up is here: wnur website.