OpenDrop VJ is a live visual instrument built on Butterchurn (a WebGL port of the Milkdrop engine). Four independent decks — each assignable to bus A, B, or off — mix through a single live crossfader, and a GPU compositor layers, blends, and chroma-keys them on top of each other in real time. Started as a 2-deck web toy, it has grown into a full VJ rig: 16,375 presets, snapshot/timeline automation, per-deck parameter editing, MIDI/OSC/Ableton Link control surfaces, and professional outputs (NDI, Spout, a Linux virtual webcam). It runs in any browser with no install, or as an Electron desktop app that unlocks the native I/O.
Mixing & decks
- 4 independent Butterchurn decks, each routed to bus A, B, or off, mixed through one live A↔B crossfader
- Per-deck playlists — auto-cycle sequential/shuffle, 2–120 s interval, prev/next
- Beat / volume-peak trigger per deck — auto-advance the playlist on the beat or on a volume peak
- Q-var live editing — override a preset's own internal q1–q32 variables per deck in real time (NestDrop-style)
- Time param sliders — per-deck speed/zoom/rotation/warp engine multipliers layered on top of any preset
GPU compositor & overlays
- 4 blend modes (normal / additive / screen / multiply) + per-layer LumaKey and ColorKey chroma keying, all GPU-side
- Text and media overlays — image/video sprites or text layers, with transform, blend mode, beat-reactive scaling, spin, and drift
- Overlay auto-cycling queue — sequential or shuffled rotation through a set of overlays
Automation & performance state
- Snapshots / macros — 8 slots capturing and recalling a full "look" (color + compositing) with smooth interpolation
- Timeline / keyframes — sequence the 8 snapshot slots on a looping wall-clock timeline
- LFO×4 (sine / saw / square / sample-and-hold), routable to any control, plus a strobe effect
- Share a set via URL — encode the current visual state into a link; the recipient gets a one-click import
Presets & video
- 16,375 built-in presets — search, 5-color favorites, cached lazy-rendered thumbnails
- Presets cloud — a private per-device library for your own custom presets, backed by a small Cloudflare Worker + R2 (no account, just a portable token)
- Video loops — beat-reactive flash/hue/scale, with an optional CDN-backed library
Control surfaces
- MIDI — CC/note/pitchbend mapping (14-bit CC support) with bidirectional LED feedback to your controller
- OSC input over UDP (Electron)
- Ableton Link tempo sync (Electron)
- Remote control from a phone or tablet over LAN, token-authenticated (Electron)
- Fully customizable keymap
Output & capture
- Output window — detached fullscreen canvas for a second monitor or an OBS Browser Source
- NDI output (Electron, requires the NDI SDK)
- Spout output (Electron, Windows only)
- Virtual webcam via v4l2loopback (Electron, Linux only)
- Multi-display targeting and adjustable quality tiers (with an "invisible mode" perf mode for when the output isn't on screen)
→ opendrop.kushie.dev — no install required (Chrome / Edge recommended).
pnpm install
pnpm dev # → http://localhost:1420Click ▶ Start to initialize the audio context, then pick a preset in the browser panel.
Download the latest build for your platform from the Releases page
— Windows (.exe), macOS (.dmg, Intel and Apple Silicon), and Linux (.AppImage) — or build from source:
pnpm electron:dev # Vite dev + Electron in parallel
pnpm electron:build # Build SPA → package with electron-builderNative modules (NDI, Spout, Ableton Link) need a one-time rebuild against Electron's Node ABI:
pnpm electron:rebuild # grandiose (NDI)
pnpm electron:rebuild:spout # Windows only
pnpm electron:rebuild:link # Ableton LinkLinux / Wayland (Hyprland etc.) — pass flags when launching the packaged app:
./OpenDrop-VJ.AppImage --ozone-platform=wayland --no-sandboxClick the audio source button in the app — behaviour adapts per platform:
| Platform | Web | Electron |
|---|---|---|
| Windows | Screen picker → "Share system audio" (Chrome) | Native loopback — no picker |
| Linux | Device picker → .monitor source (PipeWire / PulseAudio) |
Same |
| macOS | Tab audio only | Install BlackHole → device picker |
Linux tip: bash scripts/setup-audio.sh creates a named PipeWire virtual source ("OpenDrop - Son du PC")
instead of using a raw .monitor device.
Click Open Output in the app. The fullscreen canvas follows the crossfader, presets, compositing, overlays, and video loops in real time.
Known limitation: on Linux (Electron), audio reactivity in the output window may require re-selecting the audio device once after the first open. Re-pick the device once and it stays reactive for the session.
- Add a Browser Source in OBS
- URL:
http://localhost:1420/output(dev) or the packaged app URL - Width / Height: match your stream resolution
- NDI — requires the NDI SDK installed, plus the
grandiosenative module - Spout — Windows only, vendored SpoutDX
- Virtual webcam — Linux only, via v4l2loopback + ffmpeg (
scripts/setup-v4l2.sh)
Enable the remote server from the Electron app to control OpenDrop from a phone or tablet on the
same network — a token-authenticated WebSocket server serves a touch UI at /remote.
pnpm check # svelte-check (TypeScript + Svelte)
pnpm test # Vitest unit tests
pnpm test:coverage # Coverage report
pnpm test:e2e # Playwright E2E (requires pnpm dev running)
pnpm build # Production SPA → build/ (runs presets:build first)| Layer | Tech |
|---|---|
| UI | SvelteKit 2 + Svelte 5 runes, TypeScript |
| Visualizer | Butterchurn (Milkdrop WebGL) |
| Compositor | Custom WebGL2 blend + LumaKey/ColorKey pipeline |
| Audio | Web Audio API — AudioContext, AnalyserNode, AudioWorklets |
| Control | Web MIDI API, OSC (UDP), Ableton Link, WebSocket remote |
| Build | Vite + @sveltejs/adapter-static (SPA, ssr: false) |
| Desktop | Electron 42 (optional — native audio loopback, NDI/Spout/virtual-cam output, OSC, Ableton Link, remote server) |
| Cloud | Cloudflare Worker + R2 (private custom-preset library) |
src/
├── lib/
│ ├── engine/
│ │ ├── audio.ts AudioEngine — shared AudioContext/AnalyserNode feeding all decks
│ │ ├── deck.ts Deck — single Butterchurn instance wrapper
│ │ ├── deck-manager.ts DeckManager — 4 slots, lazy init, pause/resume
│ │ ├── compositor.ts GPU compositor — blend modes, LumaKey/ColorKey, color params
│ │ ├── overlay.ts Overlay type — media (image/video) or text layers
│ │ ├── overlay-queue.ts Overlay auto-cycling queue
│ │ ├── snapshot.ts SnapshotEngine — 8-slot look capture/recall
│ │ ├── timeline.ts TimelineEngine — sequences snapshots on a loop
│ │ ├── q-vars.ts Per-deck live q1–q32 overrides
│ │ ├── time-params.ts Per-deck Time (speed/zoom/etc.) engine multipliers
│ │ ├── beat-trigger.ts Per-deck beat/volume-peak playlist trigger
│ │ ├── lfo.ts LfoEngine — routable LFOs
│ │ ├── clock.ts Shared BPM/phase/beat clock
│ │ ├── bpm.ts BeatDetector — bass-energy beat detection
│ │ ├── midi.ts MidiEngine — Web MIDI input + LED feedback
│ │ ├── commands.ts Central command registry (every controllable parameter)
│ │ ├── keymap.ts Default keyboard bindings
│ │ ├── quality.ts Quality tiers + invisible-mode perf throttling
│ │ ├── playlist.ts PlaylistEngine — auto-cycle, shuffle, prev/next
│ │ ├── share-set.ts Encode/decode a visual state to a shareable URL
│ │ ├── cloud-presets.ts Private per-device cloud preset library client
│ │ ├── sync.ts Cross-window state sync (BroadcastChannel / Electron IPC)
│ │ └── video-store.ts Video loop clip registry (IndexedDB)
│ ├── components/ Svelte UI components (DeckCard, MixerLayout, sidebars, overlays…)
│ ├── presets/ Preset registry (16,375 presets, search, lazy-load, thumbnails)
│ └── video-loops/ Built-in video loop manifests + loader
└── routes/
├── +page.svelte Main VJ controller (Stage / Mixer layouts)
├── output/+page.svelte Fullscreen output canvas (OBS / second monitor)
└── remote/+page.svelte Touch UI for phone/tablet remote control
electron/
├── main.cjs Main process — IPC relay, PCM audio bridge, NDI/Spout/v4l2, OSC,
│ Ableton Link, remote WS server, multi-display targeting
└── preload.cjs contextBridge → window.electronAPI
workers/
└── presets-cloud/ Standalone Cloudflare Worker + R2 backing the presets cloud
static/
├── capture-worklet.js AudioWorklet — taps gainNode, posts PCM chunks to main
└── loopback-worklet.js AudioWorklet — ring-buffer PCM injection for output window
e2e/
└── *.spec.ts Playwright E2E tests
| Item | Status |
|---|---|
| Signed installers | Planned — Windows/macOS/Linux builds are all currently unsigned (expect an OS security warning on first launch) |
| Audio reactivity in output (Linux) | Requires re-picking the device once per session — known issue |
| Web MIDI | Chromium / Electron only (not Firefox / Safari) |
| System audio on macOS (browser) | Tab audio only — install BlackHole for full capture |
| Spout output | Windows only |
| Virtual webcam output | Linux only |
- Butterchurn — WebGL Milkdrop renderer by Jordan Berg
- butterchurn-presets — bundled preset collection, plus a merged MilkDrop community megapack
- SvelteKit / Svelte 5
- Electron
- grandiose — NDI bindings
- SpoutDX — Spout SDK (vendored, Windows output)
- @ktamas77/abletonlink — Ableton Link bindings
- Cloudflare Workers — presets cloud backend
- Preset authors — listed in each preset file; used under their respective licenses
MIT — see LICENSE.
Made with ❤️ by kushiemoon-dev

