A real-time audio generation and visualization PWA using WebGL shaders.
Beatty is a browser-based audio synthesizer and visualizer. It uses GLSL shaders with WebGL2 Transform Feedback to generate audio in real-time while simultaneously rendering visual effects. Both sound and visual shaders are editable via a built-in CodeMirror editor, providing a live coding experience.
- Real-time Audio Generation: Synthesize audio using WebGL2 Transform Feedback
- Audio Visualizer: Real-time visual effects synchronized with audio
- Live Coding: Edit shader code with syntax highlighting (CodeMirror 6)
- Dual Mode: Switch between sound shader and visual shader editing
- Audio Analysis: Frequency analysis for kick, hihat, bass with onset detection
- Preset System: Save, load, import/export shader presets
- Mobile Support: Touch device controls
- PWA: Installable as a Progressive Web App
Mac: Ctrl + (key) / Windows: Ctrl + Shift + (key)
| Key | Action |
|---|---|
P |
Play / Pause |
I |
Initialize (Reset playback) |
C |
Compile shader |
A |
Apply compiled shader |
V |
Visibility (Toggle editor) |
M |
Mode (Switch Sound / Visual) |
D |
Debug overlay |
Escape |
Close modal |
Buttons at the top of the screen:
- Play/Pause, Reset, Compile, Apply, Toggle Editor, Switch Mode, Presets, Help
- BPM: Click on the BPM display in the status bar
- Volume: Click on the VOL display in the status bar
Implement vec2 mainSound(float time) returning stereo audio samples:
vec2 mainSound(float time) {
float beat = timeToBeat(time);
vec2 o = vec2(0.0);
// Kick: every beat
float kickTime = beatToTime(mod(beat, 1.0));
o += vec2(kick(kickTime)) * 0.7;
// Hihat: offbeat
float hihatTime = beatToTime(mod(beat + 0.5, 1.0));
o += vec2(hihat(hihatTime)) * 0.3;
// Snare: beats 2 and 4
float snareTime = beatToTime(mod(beat + 1.0, 2.0));
o += vec2(snare(snareTime)) * 0.6;
return o;
}Available Uniforms:
u_bpm- Current BPMu_sampleRate- Sample rateu_blockOffset- Current block offset
Built-in Constants:
PI- 3.14159265359TAU- 6.28318530718SEMITONE- 1.05946309436 (frequency ratio for one semitone)saturate(x)- clamp(x, 0.0, 1.0)
Built-in Utility Functions:
timeToBeat(time)/beatToTime(beat)- Time/beat conversionmtof(note)- MIDI note to frequency (A4 = 69 = 440Hz)quantize(beat, division)- Quantize beat to gridadsr(time, a, d, s, r, duration)- ADSR envelopelfo(time, rate)- Low frequency oscillator
Built-in Waveforms:
sine(phase),saw(phase),square(phase),triangle(phase)hash21(vec2),hash22(vec2)- High quality hash functions
Built-in Instruments:
kick(time),hihat(time),openHihat(time),snare(time)clap(time),rim(time),tom(time, freq)bass(time, freq, cutoff),subBass(time, freq)lead(time, freq),pad(time, freq, duration)filteredSaw(phase, cutoff)- Saw with cutoff control
Built-in Effects:
distort(x, drive)- Soft clippinglowpass(osc, cutoff)- Simple lowpass filterbitcrush(x, bits)- Lo-fi effectchorus(phase, depth, rate, time)- Thicken sound
Implement vec3 visualMain(vec2 uv, vec2 resolution) returning RGB color:
vec3 visualMain(vec2 uv, vec2 resolution) {
vec2 pos = (uv - 0.5) * 2.0;
pos.x *= resolution.x / resolution.y;
// Circle with kick reaction
float d = length(pos) - 0.3 - u_kickPeak * 0.1;
float circle = smoothstep(0.02, 0.0, abs(d));
vec3 color = hsv2rgb(vec3(u_time * 0.1, 0.8, circle));
color += vec3(0.5, 0.7, 1.0) * u_kickOnset * 0.3;
return color;
}Available Uniforms:
| Uniform | Description |
|---|---|
u_resolution |
Screen resolution |
u_time |
Elapsed time (seconds) |
u_kick |
Kick level (20-80Hz, smoothed, 0-1) |
u_hihat |
Hihat level (5000-12000Hz, smoothed, 0-1) |
u_bass |
Bass level (100-300Hz, smoothed, 0-1) |
u_kickPeak |
Kick peak (decaying, for flash effects) |
u_hihatPeak |
Hihat peak (decaying) |
u_bassPeak |
Bass peak (decaying) |
u_kickOnset |
Kick onset detection (1.0 or 0.0) |
u_hihatOnset |
Hihat onset detection (1.0 or 0.0) |
u_bassOnset |
Bass onset detection (1.0 or 0.0) |
Built-in Constants:
PI,TAU,SEMITONE,saturate(x)
Built-in Utility Functions:
hsv2rgb(vec3 c)- HSV to RGB conversionrot2d(vec2 p, float a)- 2D rotationsdCircle(vec2 p, float r)- Circle SDFsdPolygon(vec2 p, float r, float n)- N-sided polygon SDF
- WebGL2 support
- Web Audio API with AudioWorklet
- ES6 Modules support
-
Serve the files via HTTP server (required for ES modules and AudioWorklet):
# Python python3 -m http.server 8080 # or npx npx serve src
-
Open
http://localhost:8080in your browser -
Press
Ctrl + Pto start playback -
Edit shaders, press
Ctrl + Sto compile,Ctrl + Rto apply
npm run lint # Biome lint check
npm run lint:fix # Auto fix
npm run format # Format code
npm run test # Run testsMIT License