The protocol
Everything between the bridge and the renderer is newline-free JSON over a plain WebSocket. It's deliberately boring, so you can write your own renderer against it.
Messages
Bridge → renderer. Every message carries a type and a unix ts. The renderer is receive-only.
Sent once on connect.
{ "type": "hello", "app": "vbuddy-bridge", "version": "0.1.0" }Traktor's broadcast metadata changed — i.e. a new track went live.
{
"type": "track",
"deck": "A",
"artist": "Overmono",
"title": "So U Kno",
"filename": "Overmono - So U Kno",
"ts": 1753712345.12
}Once per beat, derived from 24 PPQN MIDI clock.
{
"type": "clock",
"bpm": 130.02,
"beat": 2,
"bar": 41,
"playing": true
}A mapped hotcue note fired.
{ "type": "cue", "deck": "A", "index": 3, "note": 38, "channel": 0 }MIDI start / stop / continue.
{ "type": "transport", "playing": false }Clip matching
When a track message arrives, the renderer scores every file in your indexed folder and plays the best match above the threshold.
- 1. Exact filename match on
Artist - Title(extension ignored). - 2. Exact match on
Titlealone. - 3. Normalised token overlap — punctuation, brackets, "(Original Mix)" and remix suffixes stripped.
- 4. No match above 0.55 → the fallback clip plays.
Hotcue mappings always win over auto-sync while their clip is playing.
CLI flags
| Flag | Default | Purpose |
|---|---|---|
| --host | 0.0.0.0 | WebSocket bind address |
| --ws-port | 8765 | WebSocket port the renderer connects to |
| --meta-port | 8000 | Port Traktor broadcasts to |
| --midi | — | MIDI input port name (see --list-midi) |
| --cue-base | 36 | MIDI note of Deck A hotcue 1 |
| --cue-channel | any | Restrict hotcue notes to one MIDI channel |
| --osc | — | Forward events to host:port for Resolume / VDMX |
| --no-meta | off | Run clock-only, skip the broadcast listener |
| --list-midi | — | Print available MIDI inputs and exit |
Write your own renderer
The feed is a plain WebSocket, so anything that speaks WS can consume it — openFrameworks, TouchDesigner, a Processing sketch, or twelve lines of JavaScript.
const ws = new WebSocket("ws://localhost:8765");
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "clock") {
setTempo(msg.bpm);
if (msg.beat === 0) onDownbeat();
}
if (msg.type === "track") {
loadClipFor(msg.filename);
}
if (msg.type === "cue") {
fireLoop(msg.deck, msg.index);
}
};Limits worth knowing
- Metadata is deck-agnostic. Traktor's broadcast stream reports the audible mix, so the bridge tags tracks as Deck A. Hotcue events do carry a real deck, based on the note range you mapped.
- MIDI clock has no bar position. Bar counting starts at the last transport start message, so re-sync by restarting Traktor's clock if the downbeat drifts off your visuals.
- Folder access is browser-scoped. The renderer uses the File System Access API — Chrome and Edge only. Other browsers fall back to a manual multi-file picker each session.