VirtualDJ (Mac) same Mac LAN
┌─────────────────────────┐ UDP ┌──────────────┐ HTTP + SSE ┌──────────────────┐
│ deck 1 WaveShare plugin │ ───────▶ │ server.js │ ─────────────▶ │ browser / tablet │
│ deck 2 WaveShare plugin │ :7778 │ (Node 18+) │ :7777 │ (lighting op) │
└─────────────────────────┘ └──────────────┘ └──────────────────┘
Audio never leaves VirtualDJ. Only the envelope goes out: 3 bytes per 10 ms.
plugin/WaveShare.cppA VirtualDJ 8 Buffer DSP effect (IVdjPluginBufferDsp8). Only this plugin type receives the decoded track and reads samples at any position, which makes it work with streaming (TIDAL) where no file exists on disk.
One instance per deck. Each instance:
OnGetSongBuffer.VirtualDJ calls the plugin only while its deck plays. To scan a track on a paused deck, the playing deck’s audio thread also works for the paused deck’s instance.
Lessons from testing:
q() / qs(), which add deck N.preloadCheck() compares 1024 frames from both decks. Identical audio means VirtualDJ returns the caller’s audio, and pre-load switches itself off. The result goes to server.log.server.log showed plugin: preload OK and a paused deck’s waveform finished without touching it.OnGetSongBuffer reads SongPosBeats and SongBpm (samples per beat), which VirtualDJ fills before each call of the deck’s own audio thread. beatAnchor() turns them into the sample position of beat 0. Two atomics carry it to the sender thread, which appends 8 bytes to the metadata packet. A new track resets the grid.
Pre-load and the master clock (tickRemote) call the plugin from another deck’s or the master’s thread, where these fields belong to the caller. So they never set the grid. A paused deck driven by the master clock shows its waveform, but no beat lines until it plays once.
plugin/WaveShareClock.cppA second, tiny plugin (IVdjPluginDsp8) for the master effect slot. VirtualDJ calls master effects about 100 times per second even while every deck is paused (measured with a probe on 14 Oct 2026, deck N queries returned the paused deck’s length and BPM). Confirmed working on real VirtualDJ the same day.
dlopen with RTLD_NOLOAD, which also keeps it loaded) and looks up WaveShareClockTick.WaveShareClockTick(). It ticks every deck whose own audio thread has been idle for 200 ms, exactly like pre-load: metadata, track detection, scanning.MASTER_CLOCK in WaveShare.cpp. If the pre-load audio check fails (-1), the clock stops scanning audio too.plugin: master clock: bezi to server.log.server/server.jsNode 18+, no npm packages.
trackId resets the column array and bumps rev.live (fresh metadata), pause (positions arrive, metadata stale), off (nothing arrives).server/server.log with local date and time: track changes, finished waveforms, pre-load result, loops, plugin silence.vdjUrl: ''). See Known issues.iface, --iface): all (default) listens on every adapter once, as before. Opt-in auto serves on the cable if it has an IPv4 address, else on Wi-Fi, one adapter name or IP serves only there. Both check every 2 s; a changed address restarts the HTTP server on it and closes all connections, so browsers must open the new address. Cable = any macOS hardware port that is not Wi-Fi (networksetup -listallhardwareports, read again only when the adapter list changes). VPN and virtual adapters come last.server/index.htmlSingle file, no build step, no libraries.
pointer: coarse): pinch to zoom, double tap resets to 16 s, 48 px buttons, full screen button. Page zoom is off (user-scalable=no), so the tablet does not zoom by accident.localStorage.| Issue | Status |
|---|---|
Network Control plugin crashes VirtualDJ on quit (std::terminate in a NetworkSocket thread, 3 crash reports on 13 Oct 2026) |
Server no longer uses it |
get_loaded_song 'filepath' crashes VirtualDJ on a TIDAL track |
Never used |
VirtualDJ wiki lists Documents/VirtualDJ/PluginsArm as plugin folder |
Wrong on current versions. Real path: ~/Library/Application Support/VirtualDJ/PluginsMacArm/SoundEffect |
Units of get_loop_in_time |
Assumed ms, not verified with pitch ≠ 0 |
SCALE in the plugin |
Set by eye |
Beat grid units (SongBpm as track samples per beat) |
Assumed, not verified with pitch ≠ 0 |
Cable detection on macOS (--iface auto) |
By port name. A dock, club Ethernet or a phone tethered over USB counts as cable |
| A track loaded while no deck plays (typical first track of a set) stayed empty until a deck played. VirtualDJ never calls a paused deck’s effect | Fixed by WaveShare Clock on the master (confirmed on real VirtualDJ, 14 Oct 2026). Without it: play any deck, even in the headphones |
| Windows | No build yet |