Open-source VJ tool powered by Butterchurn (WebGL Milkdrop). Two independent decks, live crossfader, MIDI control, video loops, and a dedicated output window for second monitors or OBS — no C++ or native toolchain required.
- 2 decks A/B — independent Butterchurn (Milkdrop WebGL) instances
- Live crossfader — real-time opacity blend A↔B; output window follows the fader continuously
- Playlists — per-deck auto-cycle (sequential / shuffle), 2–120 s interval, prev / next
- Beat-sync / BPM — bass-energy beat detector drives preset transitions on the beat
- MIDI mapping — CC/note → crossfader, playlist controls (Chromium / Electron only)
- Preset browser — search, favorites ★, 1 754 built-in presets (lazy-loaded)
- Video loops — overlay MP4/WebM clips with opacity, beat-reactive cut/flash/hue/warp
- Overlays — composited SVG/text layers synced to the output
- Output window — detached fullscreen canvas for a second monitor or OBS Browser Source; crossfader, presets, overlays, and video loops sync live from the main window
- Mixer layout — grid preset browser alongside the decks for fast live switching
- Audio sources — mic, device picker, audio file, system audio (all platforms)
- NDI output (Electron, optional) — requires NDI SDK + grandiose native module
- Export / import playlists as JSON
- Keyboard shortcuts — ← → nudge crossfader, Tab switches active deck
| Layer | Tech |
|---|---|
| UI | SvelteKit 2 + Svelte 5 runes, TypeScript |
| Visualizer | Butterchurn (Milkdrop WebGL) |
| Audio | Web Audio API — AudioContext, AnalyserNode, AudioWorklets |
| MIDI | Web MIDI API |
| Build | Vite + @sveltejs/adapter-static (SPA, ssr: false) |
| Desktop | Electron 42 (optional — PCM audio streaming, native window, NDI) |
→ 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 AppImage (Linux) from the Releases page, or build from source:
pnpm electron:dev # Vite dev + Electron in parallel
pnpm electron:build # Build SPA → package with electron-builderLinux / 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, 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. This is a known issue — 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
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/src/
├── lib/
│ ├── engine/
│ │ ├── audio.ts AudioEngine — AudioContext, sources, AudioWorklets, PCM streaming
│ │ ├── bpm.ts BeatDetector — bass-energy beat detection
│ │ ├── deck.ts Deck — Butterchurn instance wrapper
│ │ ├── midi.ts MidiEngine — Web MIDI, CC/note mapping
│ │ ├── overlay.ts Overlay types
│ │ ├── playlist.ts PlaylistEngine — auto-cycle, shuffle, prev/next
│ │ ├── quality.ts Quality tiers (low / medium / high)
│ │ ├── sync.ts Cross-window state sync (BroadcastChannel / Electron IPC)
│ │ └── video-store.ts Video loop clip registry
│ ├── components/ Svelte UI components
│ ├── presets/ Preset registry (butterchurn-presets, search, lazy-load)
│ └── 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)
electron/
├── main.cjs Main process (IPC relay, PCM audio bridge, app:// protocol, NDI)
└── preload.cjs contextBridge → window.electronAPI
static/
├── capture-worklet.js AudioWorklet — taps gainNode, posts Int16 PCM chunks to main
└── loopback-worklet.js AudioWorklet — ring-buffer PCM injection for output window
e2e/
└── app.spec.ts Playwright E2E tests
| Item | Status |
|---|---|
| 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 |
| Packaged desktop app | Linux AppImage only — Windows / macOS users can use the web version |
| Signed installers | Planned |
| 4-deck compositor | Planned |
| Spout / v4l2 output | Planned |
- Butterchurn — WebGL Milkdrop renderer by Jordan Berg
- butterchurn-presets — bundled preset collection
- SvelteKit / Svelte 5
- Electron
- Preset authors — listed in each
.milkfile; used under their respective licenses
MIT — see LICENSE.
Made with ❤️ by kushiemoon-dev