A projectM/Milkdrop-style music visualizer for live coding sessions. Odin + Raylib, GLSL feedback shaders, FFT of the desktop audio, and an OSC listener so the live-coding environment (ChucK) can send semantic events the FFT can't hear — note-ons, kicks, scene channels.
./run.sh # build + run (run from the project root: shaders load from ./shaders)
./build.sh # build only -> bin/vizNeeds Odin (vendor raylib + miniaudio) and PipeWire/PulseAudio. Audio is
captured from the first capture device whose name contains monitor (the
desktop's output loopback). Override with a substring match:
VIZ_AUDIO_DEVICE="alsa_output.pci" ./run.sh| key | action |
|---|---|
| F1 | debug overlay (fps, audio device, OSC traffic, band meters) |
| C | clear the feedback buffers |
| R | force shader reload |
| F | borderless fullscreen |
| address | args | effect |
|---|---|---|
/kick |
[f vel] |
zoom punch + palette flash, decays ~150ms |
/snare |
[f vel] |
edge flash |
/hat |
[f vel] |
center sparkle |
/note |
f freq [f vel] |
waveform/palette hue (one octave = 60°) + pulse |
/viz1–/viz4 |
f 0..1 |
free channels, held until re-sent (/viz1 = palette shift, /viz2 = brightness; 3–4 unused, wire them up in comp.fs) |
int args are accepted anywhere a float is expected; bundles are unpacked.
From ChucK:
OscOut osc;
osc.dest("127.0.0.1", 9000);
osc.start("/kick"); 1.0 => osc.add; osc.send();examples/osc_demo.ck is a full beat + OSC demo to run alongside the app.
Both shaders hot-reload on save while the app runs:
shaders/warp.fs— the feedback pass (previous frame → current): rotation, zoom, ripple, trail decay. This is where the "motion feel" lives.shaders/comp.fs— the composite pass (feedback → screen): palette, spectrum bars, beat flashes, vignette, tone map.
comp.fs gets audioTex, a 512×2 R32F texture: row v=0.25 is the
log-binned spectrum (35Hz–16kHz, 0..1), row v=0.75 is the waveform
(0.5 = silence). A broken edit keeps the last working shader running.
src/main.odin window, ping-pong feedback buffers, shader hot reload, uniforms
src/audio.odin miniaudio capture of the monitor source -> lock-free ring buffer
src/fft.odin Hann + radix-2 FFT, log binning, band energies, auto-gain
src/osc.odin non-blocking UDP OSC 1.0 receiver (messages + bundles)
Per frame: warp.fs re-renders the previous frame (buffer A → B, distorted and decayed), the live waveform is drawn into B as additive geometry, comp.fs composites B to the screen, then A and B swap — the Milkdrop feedback loop.