One mixer container and four CDJ containers
Run from the aidj-club-emulator repository. The primary topology is described in Four-CDJ B2B. Every player feeds its matching mixer channel; the mixer API is the public control/output endpoint.
Docker Compose
Set a token and use a readable directory containing your audio. Only the mixer HTTP port is published; the CDJs communicate on the project's Docker network.
export CLUB_TOKEN='choose-a-local-token'
export CLUB_MEDIA_DIR=/absolute/path/to/music
docker compose -p club-a up --build
The default endpoint is http://127.0.0.1:8788. For another independent club:
CLUB_PORT=8789 CLUB_ID=club-b docker compose -p club-b up -d
The same image runs five roles: cdj1, cdj2, cdj3, cdj4 and mixer. Services run without root or additional capabilities and mount music read-only. CLUB_BIND changes the published host address; the default is loopback. No LAN discovery ports are published by this development Compose file.
Stop a particular club with docker compose -p club-a down. Token/volume/port settings must also be present when resolving Compose configuration for that command. There is no persisted emulator transport/session state yet.
Docker validation
Validated on Docker Engine 28.4.0, Linux/amd64, with Compose v2.39.4. The image builds, and two complete clubs run simultaneously with ten healthy containers and no restarts during the short smoke test. Each of the four strips produces audio; shared faders mute the master while transports continue. MIDI commands, bearer authentication, and isolation between the clubs also pass.
docker build -t prochain-club-emulator:local .
python3 tools/smoke_docker.py
The test generates temporary music, tokens, ports and project names. It removes only its own test containers/networks afterward. It sends no LINK broadcasts and does not validate physical audio devices, rekordbox or long-running stability. CI repeats this test after building the image.
If Docker was just installed and a shell cannot access its socket despite the user already being in the docker group, refresh the login session or run the commands in a shell with the new group membership. The validation session used sg docker -c 'python3 tools/smoke_docker.py'; no socket permissions were changed.
Load and control
The default convention is DJ A → decks 1–2 and DJ B → decks 3–4. The API token currently grants trusted operator access across the booth; this convention is not an authorization boundary.
curl --fail -H "Authorization: Bearer $CLUB_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"deck":1,"path":"track-a.wav","bpm":128,"first_beat":0}' \
http://127.0.0.1:8788/load
curl --fail -H "Authorization: Bearer $CLUB_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"action":"play","deck":1}' \
http://127.0.0.1:8788/commands
curl --fail -H "Authorization: Bearer $CLUB_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"action":"fader","channel":1,"value":0.8}' \
http://127.0.0.1:8788/commands
Repeat load/play for decks 2, 3 and 4 using the appropriate paths and reviewed BPM/first-beat values. The server resolves paths beneath its mounted media root and rejects directory traversal. Loading an already-playing deck requires pausing it first. Tracks preload into memory, with a 30-minute per-file bound; plan RAM for four decoded tracks and an in-progress replacement. An instance does not automatically analyze BPM.
| Endpoint | Behavior |
|---|---|
GET /healthz | Public process/audio-worker health |
GET /state | Shared mixer values and four deck snapshots; bearer required |
POST /load | Local media path, deck, nominal BPM and optional first beat; mixer forwards to the correct CDJ |
POST /commands | Deck transport or shared mixer command; acknowledgement follows execution |
POST /midi | {"bytes":[144,0,127]} maps a Prochain MIDI message to a command |
GET /audio.wav | Continuous stereo 48 kHz / 16-bit master WAV stream |
POST /render | Internal CDJ-only master-clock pull; not a controller endpoint |
HTTP commands execute at the next processing boundary, not at an arbitrary requested sample offset. A forwarded command's combined mixer snapshot may lag by one audio block; poll /state for the next combined observation. A timed-out acknowledgement is ambiguous; inspect state before retrying a non-idempotent command such as toggle.
Examples of additional commands:
{"action":"pause","deck":2}
{"action":"cue","deck":2}
{"action":"set_hot_cue","deck":3,"slot":1}
{"action":"hot_cue","deck":3,"slot":1}
{"action":"loop","deck":4,"beats":4}
{"action":"loop","deck":4,"beats":null}
{"action":"tempo","deck":1,"ratio":1.02}
{"action":"eq","channel":3,"band":"low","value":0}
{"action":"filter","channel":4,"value":0.4}
{"action":"assign_crossfader","channel":1,"assign":"a"}
{"action":"crossfader","value":-1}
{"action":"tempo_master","deck":3}
Tempo ratio is 0.84–1.16 and changes pitch: master-tempo/key-lock is not available in this emulator. EQ/trim values are linear 0–2, with EQ unity at 1; they are not vendor knob values. Channel faders/master gain are 0–1; filter/crossfader are −1–1. Loop sizes are 1/2/4/8/16/32 beats. Unsupported controls are rejected; Beat FX and distributed sync are not silently simulated.
Listen to the shared output
With FFplay installed on the receiving computer:
curl --fail --no-buffer -H "Authorization: Bearer $CLUB_TOKEN" \
http://127.0.0.1:8788/audio.wav | ffplay -autoexit -i pipe:0
The HTTP stream uses WAV headers with unknown lengths. It is a development output route, not a virtual DJM USB audio driver. Slow receivers are disconnected rather than silently dropping samples. PA, booth monitor and headphone routing must be provided by the playback host until dedicated buses/adapters exist.
Run without Docker
cargo build --bin club-emulator
python3 tools/smoke_b2b.py
The smoke test starts four CDJs and one mixer on temporary loopback ports, generates temporary WAV fixtures, verifies audible output and mixer controls, then stops the processes. It does not send PRO DJ LINK broadcasts.
For manual processes, run club-emulator serve --role player --deck N --bind HOST:PORT --media-root DIR four times. Run the mixer with serve --role mixer and four ordered --player http://HOST:PORT arguments. Every service uses the same CLUB_TOKEN. --booth-id labels an instance. serve --role booth is the alternative single-process four-deck mode.
Native local audio/MIDI is separate:
# On Debian/Ubuntu, install build-essential, pkg-config and libasound2-dev first.
cargo run --features native --bin club-emulator -- devices
cargo run --features native --bin club-emulator -- run \
--track a.wav --track b.wav --track c.wav --track d.wav --autoplay \
--midi-input virtual --midi-output virtual
Unix supports virtual MIDI ports; Windows requires an existing MIDI input/loopback port. This is the Prochain mapping in crates/emulator/src/midi.rs, not AlphaTheta HID or the official A9 MIDI map. MIDI channels 1–4 address decks/strips 1–4. Note 0 toggles play, note 1 cues, notes 16–23 trigger hot cues; CC7 is channel fader, CC16–18 EQ, CC19 filter and CC20 trim. On MIDI channel 1, CC21 is crossfader and CC22 master gain. Pitch bend controls tempo. Locally entered mapped commands can emit MIDI; incoming MIDI is not echoed back.
LAN/rekordbox boundary
The default Docker bridge carries the private audio/control protocol. It does not place virtual CDJs on rekordbox's physical broadcast domain. LAN tests need actual assigned IP/MAC identities on an L2-connected network, such as a deliberately configured macvlan network. Docker Desktop/WSL NAT and port publication alone do not supply that topology.
--link-ips, --link-macs, --link-target and --link-decks enable the experimental LINK projection. A standalone four-player booth needs four player IP/MAC pairs plus an optional mixer pair. A distributed player should advertise only its own deck; the mixer can use --link-mixer-only with one pair. The process does not create interfaces. Reuse device numbers only across isolated LINK networks; there must be one mixer identity on a shared segment.
Current projection provides initial announcements, fixed-number claiming/defense, keep-alives, beats, legacy status fields and basic metadata. Wire identities are CDJ-2000nexus and DJM-900nexus; CDJ-3000X/A9 remain the product targets. Automatic number selection/remapping is not implemented. Physical mixer on-air state and externally negotiated tempo must be validated before hybrid use. Direct rekordbox LINK EXPORT loading/NFS, HID identity and hardware remote control are absent.
See the hardware evidence matrix. Docker build and short runtime validation have passed; real rekordbox/physical-booth acceptance and production reliability remain separate gates.
On 2026-09-11, the user reported that rekordbox displayed a LINK icon after a unicast keep-alive probe from WSL to Windows. The probe captured no replies; player identification and track loading remain unverified. A follow-up native player probe bypassed Docker for diagnosis. In this installation, Docker Desktop host networking uses a separate VM, and attempting to bind its container to the WSL interface address failed with Cannot assign requested address. A LINK icon does not validate that container network path.
Screenshot follow-up (2026-09-11): EXPORT mode shows the LINK panel with 1, an adjustable 119.92 BPM and MASTER controls, but no CDJ device icon in the remaining panel. The tempo controls belong to rekordbox's Sync Master section (rekordbox EXPORT manual, Link status panel); this screenshot does not establish receipt of the emulator's 120.00 BPM status or successful player identification.
Hyper-V LAN test (2026-09-11): connected to the user-provisioned Ubuntu 22.04 VM aidj-club, whose eth0 obtained 192.168.100.29/24 through DHCP (gateway 192.168.100.1). Installed Docker Engine 29.8.0 and Compose plugin 5.5.1, and imported the previously tested image. Started aidj-lan-player1 for a bounded 30-minute test using Linux host networking, the VM's actual IP/MAC, deck number 1 and broadcast target 192.168.100.255. Its loopback HTTP health endpoint returned 200. A 10-second capture on eth0 observed seven outgoing discovery packets (UDP 50000, type 6) and 51 outgoing status packets (UDP 50002, type 10), with no incoming LINK packets during that capture. This proves transmission on the VM interface, not reception or recognition by rekordbox. Four-player LAN addressing and per-container DHCP have not yet been provisioned. The base five-service Compose setup remains unchanged.
Follow-up: user still saw no player, and the LINK button disappeared after restarting rekordbox. Twelve-second unicast probes to both Windows LAN addresses (192.168.100.42, .252) and a broadcast probe each captured eight outgoing keep-alives and 60 outgoing status packets, with no incoming LINK packets. Restored the bounded player to broadcast mode. Windows has two same-subnet interfaces with equal route metrics; this is an observation, not an established cause. Windows PktMon access was denied without elevation. Prepared tools/windows/capture-link.ps1 in the emulator repository for a 20-second capture filtered to the player IP and UDP 50000–50002; its PowerShell syntax was validated, but the elevated capture has not run.
Windows capture at 20:37:33 completed but its ETL/text contained only three session metadata events, with no packet or drop events. This is inconclusive without a capture control. Updated the capture script to include ICMP to the VM, generate two pings, and save packet counters. Switched the bounded player to direct unicast toward Windows Hyper-V address 192.168.100.42 for the next capture. The VM retained its DHCP address and HTTP health remained 200.
The controlled Windows capture at 20:39:37 changes the interpretation: both ICMP pings succeeded, and counters recorded 119 matching received packets / 28,014 bytes at Host vNic #3, its WFP filters and TCPIP component 56 on AI-DJ-LAN. The Intel adapter separately recorded the two ping requests/replies. The text export still contained only session metadata, so absence of formatted packet events is not evidence of absent traffic. VM-to-Windows delivery to the network stack is confirmed for this direct-address test; delivery to rekordbox and player recognition remain unverified. At the time of that capture, the emulator sent keep-alives/status but omitted the documented initial announcement and device-number-claim sequence; it also labeled legacy status as CDJ-3000X. These are protocol gaps to investigate, not yet proven causes. No further host networking changes are justified by this capture alone.
Startup negotiation
Each device sends three initial announcements and three packets per claim stage at 300 ms intervals. Status and beats are gated until the number is claimed. A conflicting established owner or defense packet stops the service. Once ready, it defends its number against new claims and acknowledges other devices' final claims. Channel-specific mixer assignment requests get a reply, but an assignment must match the service's configured deck; otherwise startup fails. Assignment waits time out after two seconds. Automatic remapping and emulating a channel-specific network switch are outside this implementation.
Player keep-alives run every two seconds, mixer keep-alives every 1.5 seconds. Peer counts and the player startup-presence flag are maintained. /state adds link_player_model and link_mixer_model to distinguish experimental network identities from product targets. The loopback discovery test verifies both identities, packet order/timing, readiness gating and conflicts; actual rekordbox recognition remains a separate acceptance test.
Live deployment validation: built prochain-club-emulator:link-startup (image manifest sha256:2d3c97bdec8c2440c3905d6764d11e0e975e1b585202444e266ac573af8b8d03) and deployed one bounded 30-minute player on the Hyper-V VM at 192.168.100.29, targeting LAN broadcast 192.168.100.255. Preserved the stopped previous container as aidj-lan-player1-before-startup. The VM capture observed discovery kinds 0a,0a,0a,00,00,00,02,02,02,04,04,04,06…, followed by status; the log reported LINK CDJ-2000nexus number 1 ready, and HTTP health returned 200. No incoming LINK replies were captured in the 12-second window. Mixer discovery was verified on loopback; a separate mixer LAN identity has not been provisioned. This is transmission/startup validation, not confirmed rekordbox interoperability.
The user reported no rekordbox discovery after the updated deployment. A further 15-second direct test to Windows 192.168.100.252 captured the full startup sequence, six outgoing keep-alives and 56 status packets, but no incoming LINK responses. Restored the player to LAN broadcast. The inspected Windows rules showed an unrestricted rekordbox allow rule and no enabled inbound block rules; this does not prove delivery to the application. Both Windows LAN adapters retain equal-priority routes to the subnet and default gateway. A temporary single-adapter test is proposed to check interface selection; it has not been performed.
Single-adapter follow-up: the user disabled the Windows Intel Ethernet interface and restarted rekordbox. LINK now appears consistently at each restart, but there is still no player tile. The Hyper-V AI-DJ-LAN interface remains up. This associates the duplicate-interface change with reliable discovery-button appearance, not successful player connection. User priorities confirmed: Docker-hosted emulator, rekordbox EXPORT discovery and LAN track delivery first; for AI-versus-human B2B, AI audio feeds spare channels of the physical DJM-A9, which owns final summing. The all-virtual four-deck/shared-mixer topology remains the AI-versus-AI target.
After the single-adapter change, a 12-second VM capture confirmed two-way LINK traffic: six rekordbox keep-alives (UDP 50000, kind 6, 54 bytes) and 121 master-status packets (UDP 50002, kind 0x29, 56 bytes) arrived from Windows 192.168.100.42. The keep-alive uses subtype 0x03, software device number 17, and trailer 02 01 00 00 04 08. Both the inherited keep-alive decoder and the new discovery dispatcher accepted only subtype 0x02, preventing peer registration and directed status delivery. Added subtype-3 support with an anonymized captured-packet regression fixture and a real UDP test proving that a separately addressed rekordbox peer receives unicast status. This fixes a verified decoder defect; UI recognition still needs confirmation after deployment.
Deployed the rekordbox subtype-3 decoder fix in image sha256:58a9912e77447adcc38e6085f8ced00697c0aae15d7d600d04f027f489abb7e9. A 15-second VM capture recorded eight incoming rekordbox keep-alives, 151 incoming master-status packets and 55 outgoing CDJ status packets directed specifically to 192.168.100.42, alongside broadcast status. HTTP health returned 200. This confirms peer registration and directed transmission; player-tile recognition is still awaiting user confirmation. The source recheck of Beat Link and OllieCross is recorded in the docs research/upstream-audit page.
Status consistency follow-up (2026-09-11): the user confirmed that the subtype-3 fix still left only LINK and MASTER visible. Corrected the legacy player status to distinguish empty, paused and playing decks: empty media/unknown BPM and beat values, all three play-state fields, BPM validity, master-mode consistency, stopped pitch fields and the Nexus-family marker. The model remains CDJ-2000nexus; these changes do not implement CDJ-3000/A9 firmware or rekordbox track loading. Regression validation passed 49 Rust tests, Clippy and the player/mixer UDP discovery smoke test. Image prochain-club-emulator:link-status (sha256:d8fdaeeb570b43206ebc89abb5ab4100af8f37f7fb672a7ebff651590ab0f997) was deployed to the same bounded VM test. Player recognition remains unconfirmed; protocol consistency alone is not acceptance evidence.
The deployed status build passed HTTP health and a 15-second LAN capture: eight incoming rekordbox keep-alives, 149 incoming master-status packets, 74 CDJ status packets directed to Windows and 75 broadcast CDJ status packets. The captured empty-deck fields match the corrected encoder (play state 0, flags 0x84, secondary state 0x7e, unknown BPM 0xffff, master mode 0, Nexus marker 0x0f). Four directed 48-byte type-0x16 packets also arrived from rekordbox; vynull's public protocol implementation identifies this type as rekordbox status, so it is not evidence of a missing request/response handshake. The documentation production build passed.
UI confirmation (2026-09-11, screenshot 21:25:38): although the accompanying text reported only LINK/MASTER, the screenshot visibly contains a separate player tile at the bottom centre, numbered 1, with MASTER SYNC and an empty track area. This confirms that rekordbox EXPORT now displays player 1 during the legacy-profile test. The tile does not show a readable model name, so it does not validate CDJ-3000X identity. This is the first visual player-discovery acceptance evidence; LAN track loading, audio playback from rekordbox, four-player discovery and mixer recognition remain unverified. The implementation still lacks the rekordbox load-command/NFS client path. Do not infer track delivery from this empty tile or attribute success to one individual packet change without an isolated comparison.
Track titles and full-booth follow-up (2026-09-11)
Corrected the dbserver metadata response against the published menu framing: all ten menu-item numeric arguments now use four-byte fields, the unused secondary label is empty with its own UTF-16 length, the menu header has arguments 1/0, and the footer has no arguments. The TCP regression test checks these structures as well as title delivery. Rust workspace tests and Clippy pass. Deployed prochain-club-emulator:link-metadata and loaded a paused, generated eight-second tone named AI-DJ - Title Test through the authenticated local API (HTTP 200). This is a local metadata test, not a track downloaded from rekordbox; visible title acceptance remains pending. A 15-second capture after loading observed no metadata TCP connections on ports 1051/12523 from rekordbox.
The first attempt to provision a mixer address used an isolated ipvlan namespace and a distinct DHCP client identifier. The router offered the existing .29 address for the shared MAC, temporarily interrupting IPv4 access. Recovered via the VM's existing IPv6 address, removed the test namespace and stopped its DHCP process; IPv4 SSH was restored. Do not use shared-MAC DHCP clients on this router. The full booth needs independently provisioned addresses/MACs; four players and a mixer have not yet been deployed on the LAN.
tools/windows/enable-club-macs.ps1 (Desktop copy aidj-enable-club-macs.ps1) prepares Hyper-V MAC spoofing for child macvlan interfaces. It requires Administrator PowerShell, selects only the VM adapter with the known MAC 00155D64FC01, fails unless exactly one matches, and supports -Disable to undo the setting. Syntax validation passed; execution and subsequent DHCP allocation remain pending. Product targets remain CDJ-3000X/DJM-A9, with explicitly experimental CDJ-2000nexus/DJM-900nexus wire profiles. A model-name change alone does not implement modern compatibility.
Five-device LAN deployment (2026-09-11)
The user enabled Hyper-V MAC address spoofing on the aidj-club VM adapter. Created five Linux macvlan bridge interfaces (aidjlan1 through aidjlan5) on eth0, each with its own MAC and DHCP client. Router leases:
| Role | Address | MAC | Wire identity / device number |
|---|---|---|---|
| CDJ 1 | 192.168.100.28 | 02:ad:1c:00:01:01 | CDJ-2000nexus / 1 |
| CDJ 2 | 192.168.100.27 | 02:ad:1c:00:01:02 | CDJ-2000nexus / 2 |
| CDJ 3 | 192.168.100.26 | 02:ad:1c:00:01:03 | CDJ-2000nexus / 3 |
| CDJ 4 | 192.168.100.25 | 02:ad:1c:00:01:04 | CDJ-2000nexus / 4 |
| Shared mixer | 192.168.100.24 | 02:ad:1c:00:01:05 | DJM-900nexus / 33 |
Stopped the old single-player container before starting aidj-lan-cdj1 through aidj-lan-cdj4 and aidj-lan-mixer, avoiding duplicate player numbers. Each container uses Linux host networking with its own assigned IP, image prochain-club-emulator:link-metadata, a 3600-second test limit, read-only filesystem, dropped capabilities, and bearer-authenticated HTTP at its address on port 18788. The mixer pulls audio from the four player URLs in channel order. All five health endpoints returned 200, and loading the paused AI-DJ - Title Test file on all four players returned 200. This is still a temporary deployment, not reboot-persistent configuration or full CDJ-3000X/A9 compatibility.
The scoped DHCP hook is tools/linux/lan-dhcp-hook.py. It refuses a duplicate local address or a lease outside this test subnet. It installs addresses with noprefixroute and source-specific routing tables/priorities 201–205 so child interface routes cannot override the management interface's route. The first child prefix route briefly displaced management traffic; removing it restored access and source routing prevents recurrence. These table/priority values are reserved for this test and must be checked before reuse elsewhere. The VM management address remains .29; DNS and default gateway are not modified by the hook.
Ubuntu's existing AppArmor policy permits the custom hook at /etc/dhcp/dhclient-script, lease files named /var/lib/dhcp/dhclient-aidjlanN.leases and PID files /run/dhclient-aidjlanN.pid. No AppArmor policy was weakened. The custom script path was verified absent before installation. DHCP clients renew the short leases; if a lease changes, restart the affected service with its new address. Macvlan interfaces, source rules and this deployment need explicit reprovisioning after reboot. Visible titles and complete rekordbox UI acceptance remain separate checks.
Full-booth UI confirmation (screenshot 21:38, 2026-09-11): rekordbox EXPORT displays players 1, 2, 3 and 4, with a central MIXER/MASTER tile. This confirms discovery of all five legacy-profile devices. Each player shows a paused indicator and - other device -; no song-title acceptance is claimed. The local test track source is separate from rekordbox's collection. Track loading from rekordbox, modern CDJ-3000X/A9 model fidelity and audible hybrid playback remain outstanding. A 12-second VM capture observed six announcements per player, eight mixer announcements, six incoming rekordbox announcements and 57–60 directed status packets per device toward Windows. Broadcast frames captured on eth0 used the parent Ethernet MAC despite the distinct advertised device MACs; UI recognition is established, but exact L2 identity fidelity remains a separate limitation of this host-network test.
Four real rekordbox tracks and automatic mixer cycling
On 2026-09-11, queried the Windows rekordbox dbserver from the advertised player address. The collection reported 1,945 tracks. Resolved selected tracks through metadata (0x2002) and track-info (0x2102) queries, then fetched their actual files through rekordbox's UDP portmapper at 50111 and NFSv2 service, using UTF-16LE Windows export/path names. Successfully downloaded Aaron (30,649,157 bytes, 112 BPM), Altes Kamuffel (24,044,156 bytes, 130 BPM), Azure (31,570,381 bytes, 122 BPM), and Queer Fellow (25,222,334 bytes, 126 BPM). An earlier candidate lacked usable analyzed BPM and was rejected rather than assigned an invented tempo.
Added optional authenticated /load fields title and rekordbox_source (device, track_id). The source is retained in transport snapshots, while LINK status reports the actual collection device/slot 4/track ID and no local USB media. Internal IDs remain separate. Deployed prochain-club-emulator:link-source, loaded the four downloaded files onto decks 1–4, and the user confirmed all four titles. The 21:50 screenshot also shows artwork and playing indicators with the shared mixer in the centre. This validates the assisted download/API-load workflow; the UDP drag-to-load command is still not handled.
tools/rekordbox/fetch.py and its README provide the bounded, read-only helper. cycle.py starts 32-beat loops, uses ten-second dwell and one-second linear channel-fader transitions, verifies fader values and mutes/pauses on completion. A ten-minute live demo was started. Observed the complete 1 → 2 → 3 → 4 → 1 cycle. All four decks reported playing; a one-second master-stream sample contained 96,000 PCM values, non-zero RMS 0.014586 and peak 0.074066. These are audio/routing checks, not evidence that the computer speakers or a physical mixer received the stream. Rekordbox's local preview remains separate.
Validation: 49 Rust tests, native/all-target Clippy, and two Python framing/NFS-transfer tests passed. The actual LAN transfer, API source state, complete fader cycle and screenshot supply additional integration evidence. The demo does not yet use fetched beat grids, align musical phrases, perform external tempo-master handoffs, implement full DJM-A9 DSP, or automatically handle rekordbox drag-to-load. The four model identities remain the tested legacy Nexus profiles.
Windows monitoring follow-up: the user heard no audio because only the network mixer stream existed; rekordbox device tiles do not route it to the PC's speakers. Started a temporary audio-only HTTP relay at VM management address .29:18789/audio.wav, allowing only the Windows .42 client, and launched the installed Windows FFplay with default playback device, 25% application volume and a 540-second limit. The relay expires after 600 seconds; it holds the upstream bearer token only in process memory/environment and does not relay control endpoints. FFplay remained running and logged the expected unknown-length WAV warning; audible confirmation was requested. tools/rekordbox/monitor.py and the helper README document this separate monitoring path.
The user then confirmed audible Windows playback ("yes i hear"). This completes the live four-track title/playback/fader-cycle/monitoring test. Local B2B and player/mixer UDP smoke tests and the docs production build also passed. Automatic drag-to-load, modern hardware profiles and physical-mixer output remain outside this acceptance result.
Platform UI and end-of-session state
The aidj-ui /clubs page now provides manual controls for this runtime: four decks, one shared mixer, title/model status, transport, tempo, loops, hot cues, trim/EQ/filter/faders, crossfader, master gain and master-stream monitoring. A server-side LIVE_CLUBS_FILE registry selects among virtual and hybrid setups. See the UI setup instructions. Emulator /state now includes a track_titles map, separate from the external rekordbox collection IDs.
The operator UI remains loopback-only; upstream bearer tokens stay server-side. Physical decks/mixers are displayed without remote controls. AI/human assignments describe deck ownership but do not implement authorization grants or live AI handoffs. Existing /b2b planning/rendering is a separate workflow. In the hybrid target, separate AI deck outputs feed spare channels on the physical DJM-A9; that hardware routing is not implemented by this page.
The user requested stopping the VM test containers at the end of this iteration. Images and downloaded test media are retained for resumption; shutdown does not imply deleting Docker images or powering off the VM. The five child LAN interfaces and DHCP leases remain temporary network configuration. Recheck leases, Windows interface selection and rekordbox address before the next LAN run. The final UI title-map addition is source-tested locally and requires a rebuilt VM image next session.
Shutdown confirmed: no running Docker containers remained on the VM, the cycle/relay processes had exited, and no listener remained on port 18789. No matching Windows FFplay monitor remained. Final validation passed: UI production build, six UI policy tests, desktop/mobile browser checks against four real local player processes and a mixer (titles, Play, master mute), Rust workspace tests, native Clippy and docs production build. Three unrelated UI tests requiring the platform daemon were skipped. The VM image still needs the final title-map/API update when resumed.
Browser master monitoring
“Listen to master” now decodes the authenticated mixer's 48 kHz stereo PCM stream into an AudioWorklet instead of a native streaming-WAV media element. The monitor starts with 40 ms queued audio and bounds its own queue to 250 ms; a backlog overflow discards stale monitor samples and keeps up to the latest 60 ms of a burst. These are browser queue limits, not a guaranteed end-to-end network/device latency. This preview favors staying live over preserving every sample during a stall. The mixer clock, recording path and channel gains remain independent of the monitor.
Stop/reopen monitoring to reconnect at the live edge. Speaker volume defaults to 25% and affects only that browser. The shared master gain and channel faders still affect the actual booth mix. Each strip shows its track and playing/paused state; an empty or paused channel cannot change the currently audible mix. Manual slider values are submitted on release. Channel ownership remains server-enforced on every command; master monitoring uses the same authenticated stream and ongoing membership checks as before.
At the end of a loaded track, the transport button becomes Replay from start. This explicit action seeks that deck to zero, then plays only after seeking succeeds. Both steps recheck deck permissions. CUE still returns to the saved cue and leaves playback paused. A stale Play request at end-of-track now reports that the track has finished instead of presenting a generic connection error. The engine is not restarted and other decks keep their positions.
Kubernetes booth on the local rekordbox LAN (2026-09-12)
Festival B2B now has five additional LAN interfaces through Multus v4.2.4, macvlan and DHCP on the wired Kubernetes node. Each CDJ and the mixer have a separate MAC and DHCP lease. The primary Calico interface continues to carry web controls and player-to-mixer audio. Personal booths remain cluster-only.
| Device | Current DHCP address | LINK number |
|---|---|---|
| CDJ1 | 192.168.100.22 | 1 |
| CDJ2 | 192.168.100.20 | 2 |
| CDJ3 | 192.168.100.21 | 3 |
| CDJ4 | 192.168.100.19 | 4 |
| Mixer | 192.168.100.18 | 33 |
These are observed leases, not permanent reservations. Windows rekordbox is currently on 192.168.100.42/24. Keep rekordbox in EXPORT mode and connect LINK. The supported wire identities remain CDJ-2000nexus and DJM-900nexus. Network discovery does not imply full CDJ-3000X/DJM-A9 compatibility or automatically associate platform-library tracks with rekordbox track IDs.
A chained source-based routing plugin (sbr) keeps LAN routes separate from
Kubernetes control replies. This is required on this single-node deployment:
putting the LAN connected route in the main table breaks kubelet health probes
through macvlan parent/child isolation. The HTTP API binds only to the primary
pod address, while LINK binds to its own LAN address. No HTTP ingress is used
for LINK discovery.
Validation: all five devices became Ready; authenticated track loads and mixer commands succeeded; the two previously loaded tracks were restored paused. LAN capture confirmed mixer broadcasts and directed packets to Windows, plus incoming Windows LINK traffic. The existing Calico configuration hash remained unchanged. The user confirmed that both Live clubs and local rekordbox show the full booth after this Kubernetes deployment.
The pinned manifests, DHCP daemon, MicroK8s mounts, fixed device MACs and rollback instructions live in the GitOps networking directory. Only one numbered booth is exposed on this broadcast segment. Additional LAN booths or physical players require a collision-free device-number/network plan.