Watch the music. Native this time.
A native rewrite of Ryan Geiss's Milkdrop visualizer for Linux, written in Rust. No Wine, no DirectX, no Winamp plugin — a real ELF binary that reads your .milk presets and runs them on Vulkan/wgpu.
Sister project to oneamp. One audio player, one visualizer — both native, both Rust, same energy. The plan is for them to merge: when onedrop is ready, oneamp's visualization window hosts it behind a feature gate.
Milkdrop on Linux. Same per-vertex warp math, same .milk preset format, same shader contract — running on a modern GPU stack (wgpu 29 → Vulkan, Metal, DX12, GL) with no compatibility shim. And because we're rewriting a 25-year-old engine in 2026, we may as well do it in Rust so the next 25 years are someone else's problem.
Milkdrop is, to this day, one of the best music visualizers ever shipped. It was tied to Winamp on Windows, written against DirectX 9, and the ecosystem moved on. The Linux options — projectM, Kodi visualizer plugins, MPV with libplacebo — exist, but none of them gives you the feel of running stock Geiss presets the way they were meant to be seen.
projectM is the closest, and it's a serious project. But it's C++, it's been through a few API rewrites, the .milk → GLSL translation has gaps, and it doesn't always feel like 2026 hardware running 2008 art. So this is the ground-up Rust take, with the explicit goal of MilkDrop 2.0d (Geiss, 2008) feature parity, Linux first.
Why MD2 and not MD3? MD 2.0d is the last Geiss-stamped release, and the ecosystem of presets is overwhelmingly MD2-shaped. MD3 is a community fork with added niceties (HardCut, more blend patterns, etc.); we'll backport from it when cheap, but it's not the spec target.
This is v0.10.0. The foundation just landed; the visible visuals are still mostly black. Be warned.
.milkparser — line-based preset parser (header, parameters, per-frame and per-pixel equations, custom waves and shapes, embedded warp and composite shader bodies).- Expression evaluator —
evalexpr 13backend, ~30 math functions (sin/cos/tan/atan2/sqrt/pow/exp/log/clamp/lerp/...),milkif()for legacyif-as-function calls, q1–q32 user variables, custom variables auto-init. - Per-vertex warp pass — the MilkDrop primitive: a 32×24 mesh, CPU-side per-vertex equation evaluation, MD2 warp UV formula (zoom-log → rotation → anisotropic stretch → translation → optional sinusoidal warp), GPU sampling of the previous frame with decay. This is what just shipped in v0.10.0.
- GPU pipeline — wgpu 29 → Vulkan/Metal/DX12/GL. Headless-friendly via lavapipe (CI tested on Linux/macOS/Windows).
- Beat detection — six auto-preset-change modes triggered on bass/treble peaks, configurable thresholds,
F8to cycle. (Backported from MD3's HardCut feature; not on the MD2 spec path but trivial to keep.) - Audio capture —
cpal(optional via theaudio-inputfeature) for system input. FFT analyzer scaffolded. - CLI —
info/validate/render/listsubcommands for.milkfiles. - Workspace — 9 crates, edition 2024, Rust 1.90, 175 tests passing in CI, clippy
-D warningsclean.
What you cannot do yet — but is queued in objectives_to_reach.md:
- Apply the preset's own warp / composite HLSL shaders (they're parsed and stored, but not yet translated to WGSL).
- Draw waveforms or custom shapes on the canvas. With nothing seeding the feedback loop, the warp pass currently feeds black on black.
- Motion vectors, borders, gamma, video echo, brighten / darken / solarize / invert, sprites, text overlays, blur pyramid, procedural noise textures, custom texture loading.
- Render frames to PNG / MP4 from the CLI.
- Wayland layer-shell desktop mode, fullscreen integration with PipeWire monitor sources.
In rough order of priority:
- Waveform rendering — the 8
wave_modes, dots/thick/additive — gives the feedback loop something to chew on. - HLSL → WGSL translator — the preset's own warp and composite shaders, via
naga's HLSL frontend. - Custom waves and shapes — 4 of each per preset, up to 1024 instances per shape.
- Composite filters — gamma, video echo, brighten / darken / solarize / invert, darken_center.
- Blur pyramid (
GetBlur1/2/3), procedural noise (noise_lq/mq/hq,noisevol_*), user texture loading. - Motion vectors, borders.
- Sprites and text overlays (compatible with
MILK_IMG.INI/MILK_MSG.INI). - Linux native packaging —
.deb, AppImage, Flatpak. - oneamp integration — feature-gated drop-in inside oneamp's visualization window.
The full 25-section roadmap with status, current state, and per-item plans lives in objectives_to_reach.md. Per-version detail in CHANGELOG.md.
A fork of projectM. A Winamp plugin. A Windows app. A general-purpose shader playground. A media player — see oneamp for that side of the story.
There are no binary releases yet. Build from source.
Tested on Debian/Ubuntu, Fedora, and Arch with Rust 1.90.
sudo apt install build-essential pkg-config \
libasound2-dev libudev-dev \
libxkbcommon-dev libwayland-dev \
mesa-vulkan-drivers libvulkan1
git clone https://github.com/all3f0r1/onedrop
cd onedrop
cargo build --release -p onedrop-gui
./target/release/onedrop-guiFor the CLI:
cargo build --release -p onedrop-cli
./target/release/onedrop info path/to/preset.milkWhat works in the GUI today.
| Key | Action |
|---|---|
→ / N |
Next preset |
← / P |
Previous preset |
R |
Reset engine state |
F8 |
Cycle beat-detection mode (Off → HardCut1–6) |
Esc / Q |
Quit |
The full Milkdrop 2.0d keymap (Space, H, M, T, Y##, K##, [/], {/}, </>, o/O, I/i, etc.) is queued — see objectives_to_reach.md §18.
Workspace of 9 crates:
| Crate | Role |
|---|---|
onedrop-parser |
.milk file parsing |
onedrop-eval |
Expression evaluation (evalexpr + ~30 math fns) |
onedrop-renderer |
wgpu 29 GPU pipeline (warp mesh, warp pipeline, composite) |
onedrop-engine |
Audio analysis, beat detection, per-vertex warp evaluation, frame loop |
onedrop-cli |
clap-based CLI (info / validate / render / list) |
onedrop-gui |
winit + wgpu app |
onedrop-hlsl |
HLSL → WGSL translation (rudimentary; naga migration queued) |
onedrop-codegen |
Equation → WGSL transpilation, shader caching |
tools |
Internal CLI helpers (preset compatibility tester) |
Per-frame pipeline today:
- Capture audio (
cpalor demo sine) → bass/mid/treb. - Run preset per-frame equations on the evaluator.
- Warp pass: for every mesh vertex, clone the evaluator context, run per-vertex equations, apply the MD2 warp formula, upload the resulting UVs to the GPU. The fragment shader samples
prev_mainat the warped UV, multiplies bydecay, writes tocurrent_main. - Copy
current_main→prev_mainfor next frame's feedback. - Blit
current_mainto the swapchain.
Steps 3 and 4 are the foundation of every Milkdrop preset's motion. The waveform draw, the user composite shader, and post-process filters are the next milestones.
Pre-1.0. The per-vertex warp pass is the first frame-shaped result; everything else listed in objectives_to_reach.md is queued. CI runs on Linux / macOS / Windows with lavapipe headless Vulkan; 175 tests passing, clippy -D warnings clean.
To track or contribute: objectives_to_reach.md for the roadmap, CHANGELOG.md for per-version detail, CONTRIBUTING.md for how to PR.
MIT. See LICENSE.
- Ryan Geiss — Milkdrop, MilkDrop 2.0d. The reference. The whole reason this project exists.
- The MilkDrop3 community fork for the HardCut beat-detection modes we backported as a bonus.
- The projectM team for two decades of cross-platform Milkdrop, even when it hurt.
- The Rust GPU stack —
wgpu,naga,winit,cpal,evalexpr,glam,image— without whom this would still be a pile of HLSL on someone's old hard drive. - oneamp for the sister-project blueprint and the Linux-first energy.