Skip to main content

One master mixer, four CDJs

The primary club target is one virtual DJM-A9 and four virtual CDJ-3000X players, with two decks initially available to each DJ. The mixer runs in its own container. Each CDJ runs in a separate container and feeds exactly one stereo mixer channel. One reusable Docker image supplies both process roles; this is five containers per club, not a mixer per DJ.

DJ A: human or AI DJ B: human or AI
| |
CDJ 1 container CDJ 3 container
CDJ 2 container CDJ 4 container
| CH1 / CH2 | CH3 / CH4
+---------------+---------------+
|
DJM-A9 mixer container
shared master sample clock
four stereo channel strips
one stereo master output
|
playback / recording

Deck number, mixer channel, and DJ ownership are different concepts. The initial convention is DJ A → decks 1–2 and DJ B → decks 3–4; the current trusted control API can address any deck. Changing which DJ controls a deck must not create a new mixer or change channel wiring. Per-actor authorization, reassignment leases and an audible handoff policy are planned; the emulator does not yet implement the platform's actor lease system.

What is implemented

Source: aidj-club-emulator/crates/emulator. The maintained binary is club-emulator; the original upstream application remains an unchanged reference snapshot.

ComponentExecutable behavior
CDJ processOwns one configured deck's track, play/pause, cue, eight hot cues, loops, seek and pitched tempo; rejects controls for another deck or the mixer
Mixer processPulls audio from four configured player URLs, applies four independent channel strips, and serves a single master stream
Master clockMixer requests 960-frame blocks at 48 kHz, paced every 20 ms; each request carries a mixer clock ID and frame number
Duplicate protectionCDJ returns the cached last block for a duplicate pull; rejects skipped frames and a competing active mixer clock
Channel DSPTrim, basic three-band EQ, sweep filter, channel fader and A/through/B crossfader assignment; shared master gain and output saturation
Control APIBearer-authenticated state, load, command, generic MIDI-message and audio-stream endpoints; commands execute at processing boundaries
Audio outputStereo 48 kHz / 16-bit streaming WAV from the mixer; remote CDJ audio is an internal HTTP block exchange
Alternative local modeA single process with four decks can render offline or use optional CPAL and native MIDI ports

Players do not free-run on four unrelated timers in the distributed setup. Their transport advances when the mixer requests the next block. The mixer frame counter is the common timeline. HTTP is an initial internal transport, not PRO DJ LINK audio or a low-latency professional audio protocol.

The sample clock and the tempo master are separate: the mixer always clocks output, while a selected deck supplies musical BPM/beat context. The combined mixer state includes all four playheads and the selected tempo-master deck. Distributed phase-lock/sync and external master handoff are not implemented; the local single-process engine has an internal sync command.

Control and audio paths

AI/human controller
| HTTP commands or mapped MIDI bytes
v
Mixer control API ----- deck command -----> selected CDJ API
| mixer command |
v v
Shared mixer state track + transport state
^ |
| stereo channel inputs | master-clock pull response
+-------------------------------------------+
|
+--> master WAV stream --> host playback / recording

The distributed mode currently exposes trusted operator control, not autonomous AI planning. aidj-platform's existing two-deck session planner, recorded replay and Next.js B2B page remain separate. Wiring those actors and their leases to the new four-player API is an integration milestone, not completed behavior.

Mixer model and DSP boundary

A9 is the first semantic target: four stereo channels, three-band EQ, shared mixer/master and eventual dual headphone buses. A V10 backend is a later model with different routing and six channels; renaming this four-channel engine does not implement V10.

The current channel DSP is a basic implementation in Engine::mix_external, not a measured A9 response. No Beat FX, X-Pad, send/return, mic processing, separate booth bus or cue A/B output buses are implemented. Crossfader assignments default to through, so four-channel B2B does not silently mute the second DJ. Each channel can be assigned to A or B explicitly.

The intended next boundary is an owned ClubMixer interface with typed trim/EQ/color-FX/fader controls, a beat-FX configuration and explicit master/booth/cue outputs. It should support VirtualDjmA9 first, then a separately modeled V10 and capability-limited physical adapters. That trait extraction and the bosai-dsp adapter are planned, not existing runtime types. Agents will continue to send our commands rather than dependency-specific DSP objects. See the bosai-dsp evaluation.

Cue taps need a selectable monitoring topology and independent cue/master blend. Do not treat the illustrative post-fader cue diagram as a verified A9 circuit: pre-fader listening must remain useful with a channel fader down. Physical routing, levels and FX ordering require measurements/manual-based acceptance tests.

Deployment and multiple clubs

The emulator repository includes Dockerfile, compose.yaml and a deployment guide. A Compose project owns one mixer and four player services. Different project names isolate separate clubs and their internal DNS/network; different published mixer ports expose them to clients. No sound card or privileged mode is needed for internal rendering/HTTP audio.

Native CPAL output and USB MIDI are host facilities. Containers do not become USB CDJs or a DJM audio interface merely by using model names. The default image exposes MIDI messages through HTTP, not a virtual USB gadget or an ALSA/CoreMIDI port.

AI versus AI and AI versus human

ScenarioCurrent pathRemaining work
AI versus AI in one virtual clubTwo controllers address their conceptual pairs through one shared mixer APIConnect actor planner/leases; live four-deck UI and musical evaluation
Human versus AI using virtual decksHuman control client and AI controller use the same API; MIDI bytes can use /midiProduction human interface, MIDI bridge and actor arbitration
Human on physical CDJs, AI on virtual CDJsTarget: route AI stereo outputs into spare physical mixer channels, or capture physical sources into the virtual mixerAudio I/O bridge, per-channel routing, latency compensation, external tempo context, hardware tests
Separate complete clubsStart another five-container Compose projectShared event/network services only if explicitly required

The physical case must choose one final summing mixer. If the DJM-A9 hardware is the master mixer, AI stems must reach its spare channels; the virtual master output is not a substitute for that wiring. If the virtual mixer sums everything, physical CDJ outputs need audio capture adapters. Neither physical audio bridge is implemented yet.

Validation and limits

The automated five-process smoke test checks real HTTP control, loading all four decks, audible mixed output, shared-fader silence, authentication and player isolation. Unit tests cover transport, MIDI mapping, protocol projection, four input strips and duplicate clock pulls. These are local software tests, not a festival reliability or hardware compatibility report.

The container worker allocates/serializes blocks and performs network I/O outside the native CPAL path. It stops if a player fails or the mixer falls more than 100 ms behind its pacing deadline; resilient reconnect, buffering, clock recovery and seamless degraded audio are future work. The output stream is for development/listening; end-to-end latency and audio quality are unmeasured.

Docker build/launch subsequently passed on Docker Engine 28.4.0 (Linux/amd64). tools/smoke_docker.py ran two complete clubs simultaneously: ten healthy containers with no restarts, audio from all four strips, shared-fader muting, MIDI control and cross-club token/audio isolation. It removed the temporary projects afterward. This short functional check complements the local-process test; it is not a long-running reliability test. Real rekordbox discovery, exported-library loading and physical CDJ/DJM interoperability remain unverified or unimplemented as detailed in the hardware matrix.