The Stagehand iPad App
Pioneer DJ’s Stagehand iOS app (bundle ID com.pioneerdj.nma) is a front-of-house monitor for an AlphaTheta-era DJM-A9 mixer + CDJ-3000 rig.
It joins the Pro DJ Link network like any other device, but takes a few shortcuts in the handshake, advertises itself with a new device-type byte, and is almost entirely a passive receiver: the mixer and the players push state to it over unicast UDP, and the iPad answers with a small command vocabulary that drives a handful of preference toggles and CDJ transport.
This page documents what was learned about Stagehand by packet-capturing a single-iPad, single-A9, single-CDJ-3000 rig over the course of May 2026. The packet structures here build on the announcement and keep-alive frames described in Mixer and CDJ Startup, and on the various status frames described in Detailed Device Status.
Almost everything below was decoded from captures taken with a Mac in the middle (bettercap ARP-spoof MITM), because Stagehand’s wire traffic is overwhelmingly unicast and is therefore invisible to a third-party Wi-Fi sniffer.
A few findings are marked hypothesis where they were inferred from indirect evidence rather than directly provoked; the rest were verified by single-control diffs against baseline captures.
|
Background
Stagehand presents the operator with a tile per known device (mixer, deck, FX block) and a live view of channel meters, channel-fader and EQ positions, the current loaded track on each deck, and a settings panel for the CDJ-3000s. Stagehand was already known to the author as the front-of-house tool for remotely adjusting A9 mixer settings and running sound checks on the A9. What decoding revealed is that those control writes are a tiny slice of the wire traffic — the bulk is the A9 and CDJ-3000 unicasting dense monitor state to the iPad, with the CDJ transport / preference and A9 settings / sound-check controls being a small command vocabulary bolted on the side. The continuous mixer state (faders, EQ, trim, crossfader, FX knobs) is not under Stagehand’s control: the iPad reads those positions but cannot modify them remotely.
A few things make Stagehand worth careful decoding compared to every other integration path dysentery has previously documented:
-
No virtual CDJ. Stagehand does not pose as a player to receive this data — it just claims a new device-type byte (
05) with a new model code (0x20), drops most of the CDJ / mixer handshake, and the A9 and CDJ-3000 start pushing once they see the marker. -
A far denser state stream than any prior dysentery-documented client receives — per-channel A9 mixer position, EQ, source/color-FX selectors, and crossfader (
0x39); per-channel VU at ~30 Hz (0x58); CDJ settings-panel state (0x69); CDJ waveforms, cue-color palettes, and a ~2.5 kB metadata blob (0x55/0x56,0x3d); a 142.857 Hz network timebase tick (0x20); and a unicast mirror of the broadcast beat-lookahead vector (0x0b). -
Unicast push. None of these streams appear on the broadcast plane, which is why the analysis on this page was performed under
bettercapARP-spoof MITM rather than passive sniffing. -
A small but real command surface back the other way — CDJ transport (
0x07: play / pause / skip / seek), CDJ preference writes (0x6b: on-air-display and quantize verified; wider surface pending decode), and A9 settings / remote sound-check (0x3a: seven opcodes observed, UI mapping still hypothesis).
The two devices Stagehand expects to see on the wire are:
-
A DJM-A9 mixer. The A9 broadcasts the classic mixer handshake (
0x0a→0x00→0x02→0x04→0x06) and then unicasts ~38 packets/sec of state to the iPad. -
A CDJ-3000 player. The CDJ-3000 broadcasts what looks like a normal CDJ handshake but with a slightly different keep-alive (the CDJ-3000 keep-alive variant) and, once Stagehand joins, also unicasts a dense state stream to the iPad in parallel with the broadcasts that the other CDJs see.
Joining the Network
Stagehand sends an abbreviated form of the classic startup sequence.
Where a CDJ runs the full 0x0a×3 → 0x00×3 → 0x02×3 → 0x04×3 handshake and the DJM-A9 runs that exact same five-phase mixer handshake, Stagehand sends only 0x0a×3 → 0x02×3, then transitions straight to broadcasting 0x06 keep-alives.
The 0x00 pre-claim phase and the 0x04 confirmation phase are both omitted.
This is structurally an initial announcement of length 0025 (the same 37-byte form a pre-3000 CDJ broadcasts), but byte 21 (the second protocol-structure byte) is 03 rather than 02, matching the CDJ-3000-era protocol marker.
Byte 24 (the trailing device-type slot) is 05 rather than the 01 of a CDJ or the 02 of a DJM mixer.
Stagehand is the first device this analysis has seen to claim device type 0x05.
The claim packet that follows is also structurally CDJ-3000-shaped, but again ends with 05:
Byte 2e in this packet (the device-number D slot in the mixer’s claim or the CDJ’s claim) is always 3a for Stagehand.
This is a fixed symbolic value, not the number Stagehand actually operates with — see Device Numbers below.
N at byte 2f takes the values 01, 02, 03 across the three iterations, exactly as in a CDJ or mixer claim.
After the third 0x02 packet Stagehand transitions immediately to broadcasting 0x06 keep-alives at roughly 2 second intervals, the same cadence the DJM-A9 uses.
| An empty network is enough to elicit this whole sequence — Stagehand will run through the handshake even with no other devices present, just chasing announce/claim retries. The keep-alives become byte-stable as soon as one full claim cycle completes. |
Device Numbers and Per-Launch Identity
The classic device-number contract is that the value claimed in the 0x02 packet (the D at byte 2e) becomes the device number that appears in every subsequent keep-alive and status packet.
Stagehand is the only device this analysis has seen that breaks that contract: it claims 0x3a (decimal 58) but operates with a random number drawn from a high range on each launch.
The runtime number lives in byte 36 of the 0x06 keep-alive (the slot where a CDJ or mixer puts D).
Across five cold-launches of the Stagehand app over the same captured Wi-Fi network, the observed values were 141, 211, 156, 190, and 199.
That spread is consistent with a random integer drawn from roughly 0x80–0xff excluding the canonical CDJ slots (1–6) and the mixer slot (33).
The reason for the mismatch is presumably to keep Stagehand visible-but-out-of-the-way: the symbolic slot 58 leaves a marker on the wire that any other device on the network can recognise as "a Stagehand instance is present", while the high-range runtime number guarantees no collision with player or mixer slots regardless of how many are connected.
A second piece of per-launch identity lives at byte 39 of the 0x0a announce and 0x02 claim packets: a 4-byte identifier field.
Across the same five cold-launches the identifier was distinct each launch — but lagged by one launch: each launch’s announce contained the value that the previous launch had used in its claim.
This suggests Stagehand caches the random identifier it picks once, uses it for the announce on the next launch, then picks a new one for that launch’s claim.
The mechanism is curious but not load-bearing for any downstream protocol behaviour.
The Dual MAC
Stagehand presents two distinct MAC addresses on the wire:
-
The protocol-layer MAC embedded inside
0x02/0x06payloads is a stable-looking AlphaTheta-OUI value (c8:3d:fc:::**), the same OUI block as real Pioneer/AlphaTheta hardware. This is the MAC the A9 names back to the iPad in its paired keep-alive. -
The link-layer (Ethernet/802.11) MAC visible to ARP is a locally-administered random value (e.g.
5a:2d:07:::**). This is iOS’s standard Wi-Fi privacy MAC, rotated per network.
Both values change between launches.
The A9 binds its outgoing unicast UDP traffic to the protocol MAC’s IP, not to the link-layer MAC — so an arp lookup of the iPad’s IP will never return the MAC embedded in Stagehand’s protocol payloads, only the iOS Wi-Fi privacy MAC.
This is harmless under normal operation but matters when MITM-ing the link: the bettercap target list must use the link-layer MAC, while any decoded 0x06 payload comparison must use the protocol MAC.
Stagehand Keep-Alive
The keep-alive Stagehand broadcasts at ~2 second intervals on port 50000 is a 54-byte packet structurally identical to the CDJ keep-alive, but ends with a different model-code byte.
D at byte 24 is Stagehand’s per-launch runtime number (141–211), not the 3a it stamped in its claim.
Byte 21 is 03 rather than 02 — the CDJ-3000-era protocol marker, which Stagehand carries through all of its broadcast frames.
Byte 34 is 05, marking the device type as Stagehand (same value as the trailing byte of the initial announcement).
The Model Code at Byte 35
Byte 35 of every modern keep-alive is a per-product model code that AlphaTheta-era firmware appears to have added to the legacy Pioneer ProDJLink protocol.
The dysentery-documented original CDJ keep-alive left this slot at 00; the CDJ-3000-compatible variant uses 64, and dysentery already flagged that "having the wrong value there can even cause CDJ-3000s set to player 5 or 6 to repeatedly kick themselves off the network".
That CDJ-3000 special-case generalises: every modern AlphaTheta product appears to stamp its own code.
The values observed so far:
| Code | Device |
|---|---|
|
Legacy / dysentery-era hardware (also used by CDJ-3000 when posing as the legacy NXS-GW persona — see The "VCDJ-3000" Persona). |
|
Stagehand iPad app. |
|
DJM-A9 mixer. |
|
CDJ-3000. |
The numeric pattern (0x20 ASCII space, 0x31 ASCII '1', 0x64 ASCII 'd') does not seem semantic — likely just a per-product enum assigned by AlphaTheta engineering.
Devices that stamp 00 are treated as legacy; devices that stamp anything else are treated as their specific product.
The Unicast Peer Marker
Every unicast packet the A9 and the CDJ-3000 send to Stagehand has the same ten-byte magic and device-name field as their broadcasts, but uses the one-byte type field at byte 0a (the byte after the magic) — so the device name begins one byte earlier at byte 0b, exactly as in the beat packets.
The byte that follows the name field at byte 1e is always 3a in unicast frames addressed to Stagehand, and never appears in broadcast frames.
This 3a byte distinguishes the unicast peer-channel from the broadcast plane and is presumably the receiver’s discriminator: a device that sees a 3a at offset 1e knows the packet was sent unicast to a Stagehand-class peer rather than broadcast to "the network".
The value matches the symbolic device number Stagehand claims in its 0x02 packet (0x3a = 58), which is consistent with the marker being read as "to-Stagehand traffic".
The lone exception is the paired 0x06 keep-alive on port 50000, which keeps the two-byte type field of the broadcast keep-alive (06 02) so that its first 54 bytes are byte-identical to the broadcast form.
See Paired Keep-Alive (0x06 02) below.
DJM-A9 → Stagehand Unicast State
When Stagehand is attached, the A9 unicasts five distinct packet types to the iPad across the three ProDJLink ports, at a combined steady-state rate of ~38 packets/sec.
The iPad answers with a tiny trickle of unicast 0x3a-type packets back the other way — single-digit packets per minute even under heavy provocation.
The asymmetry is the point: the A9 is the talker, Stagehand is the listener.
The breakdown:
| Port | Type | Size | Rate | Purpose |
|---|---|---|---|---|
|
|
|
|
Paired keep-alive naming both the A9 and the Stagehand peer. |
|
|
|
|
Beat-cadence heartbeat with a 17-byte zero payload (parallel to the broadcast beat heartbeat). |
|
|
|
|
|
|
|
|
|
Per-channel mixer state — fader, EQ, trim, color, crossfader. |
|
|
|
|
Short heartbeat — only a sequence counter varies across the body. |
Paired Keep-Alive (0x06 02)
The only A9 → Stagehand packet that keeps the two-byte type field is the 100-byte paired keep-alive on port 50000.
The first 54 bytes are byte-for-byte identical to the A9’s broadcast keep-alive — same MAC, same IP, same device number 21, same model-code byte 31.
The next 46 bytes name the Stagehand peer the A9 is paired with.
The A9’s broadcast keep-alive itself is the legacy mixer keep-alive form with two AlphaTheta-era amendments: byte 34 is 03 rather than 02 (the modern mixer device-type marker), and byte 35 is 31 rather than 00 (the A9 model code).
The second-block header at byte 44 carries the unicast peer marker 3a in place of the A9’s mixer device number, then names the Stagehand peer with its protocol-layer MAC, its IP, the device type 05, and Stagehand’s model code 20.
The pairing is one-way: only the A9 emits this paired form. Stagehand’s own broadcast keep-alives never name the A9 in return. The A9 is keeping the record of "who I am serving"; Stagehand never asserts a partner.
Mixer State (0x39)
The 266-byte 0x39 packet on port 50002 is where Stagehand sources every per-channel fader / EQ / trim / color / crossfader reading.
It is broadcast at ~4 Hz with no apparent triggering by state changes; the per-control bytes update in place from one frame to the next.
The peer marker 3a and the constant 03 follow at bytes 1e–1f, then a length-like field lenr at bytes 20–21 (00 da at baseline = 218 — close to but not equal to the 232 bytes that follow it).
The low byte of lenr at byte 21 also acts as a partial state-counter: it ticks on some control changes but not all (no change on EQ-Hi-cut or crossfader-position; jumped by 3 on a crossfader-assign flip).
Bytes 22–23 (00 e6 at baseline) are unexplained.
Per-Channel Block
Each per-channel block is 24 bytes, repeated for channels 1–4 at offsets 36 / 60 / 84 / 108. Decoded by single-control provocation against a known baseline:
| Offset within block | Control | Encoding |
|---|---|---|
|
|
|
|
|
|
|
|
Always |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Always |
|
|
|
|
|
|
|
|
Always |
The 24-byte stride was verified by predicting CH2’s fader offset from CH1’s: at offset 47 in CH1, then at 47 + 24 = 71 for CH2, which exactly matched.
Global Master / Effects Region
After the four channel blocks comes 40 bytes of reserved space (all-zero in every capture seen so far, including with audio flowing), then a ~94-byte master / effects block beginning at byte ac.
One global control has been pinned down inside that region:
| Offset | Control | Encoding |
|---|---|---|
|
Crossfader position |
|
The remainder of the master / effects block has a visible repeating skeleton (78 00 00 00 01 01 01 06 07 00 00 ff 07 80 00) that almost certainly encodes the master fader, booth knob, headphone level / CUE mix, BEAT FX type / depth / level, Color FX selector, and MASTER EQ — but no master/FX controls were provoked under MITM, so the byte-level mapping is open.
VU Stream (0x58)
The 584-byte 0x58 packet is the most active stream on the wire — ~30/sec, every second of the 103-minute idle capture and the 19-minute provocation capture.
With no audio passing through the mixer, the body is almost entirely zero apart from a small structured prefix (02 24 00 00 00 00 00 a5 38 …).
The rate, the size, and the fact that this is the only A9 → Stagehand stream that runs at audio-frame cadence (~30 Hz matches a typical VU-meter refresh) make per-channel level metering the strongest candidate for the body’s contents. But no live audio capture has been run, so this is unverified — hypothesis (strong).
Short Heartbeat (0x3b)
A 40-byte 0x3b packet on port 50002 carries a 4-byte payload at byte 24.
Across 1,465 instances in the 103-minute capture the only field that varies is byte 25, which behaves as a sequence counter that increments and wraps every ~32 ticks (and a couple of adjacent bytes that look derived from it).
All other bytes are constant.
So 0x3b is a heartbeat, not a state-carrying packet — it appears to keep the unicast channel warm even when no other A9 → Stagehand traffic is moving.
Stagehand → A9 Commands (0x3a)
Across the entire 103-minute capture, Stagehand sent only 14 unicast packets to the A9 — all 40 bytes, all sent in pairs ~10 ms apart, so 7 distinct commands.
Each uses packet type 0x3a (the same value Stagehand stamps as its symbolic device number), names the A9 by its real device number 21 at byte 1e, and carries a 2-byte opcode followed by a 4-byte payload.
The opcodes observed were 00 b6, 00 b4, 00 a2, 00 b1, 00 b7.
The clustering in the 0x00 b0–0x00 b8 range hints at a small command family, but only 00 b4 was sent more than once across the capture and no specific UI action was tied to any opcode by the original capture.
Payloads (arg) are 4 bytes; values seen were 00 0a 00 00, 00 0c 00 00, 00 16 00 00, 00 09 00 00, 00 0b 00 00 — consistent with small integer parameter IDs rather than continuous control values.
The Stagehand UI does not let the operator drive A9 faders, EQ, trim, or crossfader from the iPad (verified by hands-on test), so these opcodes presumably address some other surface — channel-select / FX-toggle / cue-button latches, or the "input source" / "color FX" selectors visible inside 0x39.
A scripted UI tour under MITM is still needed to map opcode → UI action.
CDJ-3000 → Stagehand Unicast State
When a CDJ-3000 joins a Stagehand-attached network it activates a second, denser unicast push channel to the iPad that runs in parallel with the broadcasts the rest of the network sees. The same five-port discipline as the A9 applies (50000 / 50001 / 50002 / 50004), with one new addition: port 50004 carries a 142.857 Hz monotonic counter that none of the broadcasts use.
The full inventory:
| Port | Type | Size | Rate | Purpose |
|---|---|---|---|---|
|
|
|
|
Paired keep-alive, structurally identical to the A9 version but naming the CDJ at the head and the Stagehand peer at the tail. |
|
|
|
|
Beat-cadence heartbeat, parallel to the broadcast 53-byte heartbeat. |
|
|
|
|
Multiplexed telemetry — round-robin sub-types and a lookahead vector. |
|
|
|
|
Beat packet, unicast variant — same body as the broadcast form. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The "VCDJ-3000" Persona
A CDJ-3000 attached to a Stagehand-bearing network publishes three distinct names on the wire:
-
CDJ-3000— the broadcast-facing identity, used in every announce, keep-alive, and status broadcast. -
NXS-GW— an embedded Kuvo gateway persona used to send a sparse0x40heartbeat every ~3 minutes. The model-code byte for this persona is00(the dysentery-era value). Deep Symmetry’s read (per maintainer feedback on this analysis) is that modern CDJ-3000s carry an embedded Kuvo server here: when several CDJ-3000s share a network they negotiate amongst themselves which one offers the Kuvo gateway, and only the elected unit publishes theNXS-GWpersona — presumably so AlphaTheta no longer has to sell clubs a physical Kuvo server box. -
VCDJ-3000— used only in0x56waveform / cue-color / metadata replies to Stagehand. Never broadcast. Stagehand’s UI relies on0x56data for its waveform and cue-color rendering, and every such reply names the source asVCDJ-3000rather thanCDJ-3000.
All three personas share the one physical MAC and IP.
The naming swap is interesting because dysentery already documents the iPad-side 0x56 request flow under the assumption that the responder names itself the same as it does on the broadcast plane — for AlphaTheta-era CDJ-3000s talking to Stagehand, it doesn’t.
A consumer that filters by name will need to recognise all three.
Timing Stream (0x20 on port 50004)
Once Stagehand has joined, the CDJ-3000 unicasts a 0x20-typed packet to the iPad on port 50004 at exactly 142.857 Hz (a precise 7.000 ms tick).
The body carries a 24-bit monotonic counter and a small constant prefix; the counter increments every tick regardless of whether a track is loaded or playing.
The cadence and the steadiness make this look like a shared network timebase — a way for the iPad to derive a sub-millisecond clock reference from a participating CDJ, independent of the per-beat timing carried by 0x28.
The choice of 7 ms specifically (vs. an obvious round number like 8 ms / 125 Hz or 10 ms / 100 Hz) is unexplained.
This is the same type byte and port that dysentery documents under Audio Timing for Touch Audio — the encoding may be related. Confidence: structurally consistent but not verified against the Touch Audio decoding.
Multiplexed Telemetry (0x0b)
The CDJ unicasts a 68-byte 0x0b packet at ~30/sec, which is one of two streams that share that type byte:
-
An 8-slot round-robin sub-stream identified by an
(NN, byte36)pair in the body header. Eight sub-types rotate at ~4.4 Hz each (so collectively ~35 Hz), giving each sub-type its own slow update channel. The semantic content of each sub-type — what specific CDJ-state field it carries — has not been decoded; the round-robin structure was found by clustering on the pair, not by isolating any single field. -
A 1× lookahead vector with the body header
00 01 00 03 …whose payload is byte-equivalent to the lookahead fields in the broadcast0x28beat packet (thenextBeat/2ndBeat/nextBar/4thBeat/2ndBar/8thBeatcolumns). So this is a unicast mirror of the broadcast beat lookahead, presumably so Stagehand can decorate its CDJ tiles with the same upcoming-beat hints the master-clock-aware devices use.
The 8 round-robin sub-types include slow event counters (one byte ticks ~once every 65 s per sub-stream), fast counters (two bytes change every packet — likely frame-cadence), and per-channel slots that stay mostly empty under single-deck capture but probably populate under multi-deck mix. None of this has been provoked.
| This is the same packet type as the Absolute Position packets dysentery documents on the broadcast plane, but the unicast variant carries different content (telemetry / lookahead rather than playhead). The 68 B unicast form sits alongside the 0x0b broadcast form rather than replacing it. |
Waveform / Cue-Color / Metadata Requests (0x55 / 0x56)
Stagehand fetches waveform overlays, cue-colour palettes, and detailed track metadata from the CDJ-3000 via an 8-opcode request/reply protocol on port 50002.
The iPad sends a 0x55-typed request, the CDJ replies with a 0x56-typed packet whose body type is tagged by the opcode that asked.
Op (0x55) |
Reply size | Reply content |
|---|---|---|
|
596 B |
Preview waveform (the small full-track strip Stagehand renders along the bottom of each deck tile). |
|
820 B |
Colored overlay layer #1 ( |
|
820 B |
Colored overlay layer #2 ( |
|
384 B |
Colored detail waveform — 54 columns × 6 bytes. |
|
~2400 B |
Track metadata (still partially decoded — see Track Metadata Push ( |
|
160 B |
4-slot cue-colour palette, variant A ( |
|
~2400 B |
Paired track metadata block. |
|
160 B |
4-slot cue-colour palette, variant B ( |
Two variants of the reply remain opaque: a 196-byte payload (entropy 6.22, vocabulary 85 distinct bytes) that is structurally consistent with a proprietary cue/loop list, and a 316-byte payload (entropy 7.19, exactly 16 AES blocks) that looks like AES-CBC ciphertext. The decryption key for the second one is presumably embedded in the AlphaTheta-Connect mobile app. Hypothesis (strong): paired cue-colour palettes correspond to memory-cue vs hot-cue, or primary-deck vs secondary-deck — they are structurally identical and distinguished only by a single byte at offset 39.
Track Metadata Push (0x3d)
When a track loads on the CDJ-3000, the player unicasts a 2572-byte 0x3d packet to Stagehand carrying a denormalised metadata blob.
Roughly the first ~150 bytes have been decoded: UTF-16-LE string slots for title, artist, album, genre, comment, and label, plus two varying 16-bit fields at offsets 0x2a/0x56 (mirrored — almost certainly the track ID) and 0x2e (purpose unknown).
The remaining ~2370 bytes are unmapped.
The post-string region (offsets 0x900–0xa04) is the densest unknown zone and likely encodes BPM, key, length, hot-cue list, loop list, cue colour tags, rating, and file path — all the fields a "full" track tile would need to render.
There is also a tag byte in the comment slot (03 for raw ASCII in one capture, 01 for UTF-16-BOM + UTF-16 in another) that suggests a TLV-style encoding for the comment, but the full encoding has not been pinned down.
Settings-Panel Snapshots and the Command Channel
A handful of small-packet types on port 50002 carry CDJ settings-panel content to Stagehand and Stagehand command writes back to the CDJ. These were the hardest to decode because they fire in clusters: each user-visible CDJ pref-write generates a 5–10 packet exchange across at least three of these types within ~50 ms.
| Type | Size | Behaviour |
|---|---|---|
|
68 B |
iPad → CDJ heartbeat; body is byte-stable across the whole 21-minute capture (it does not carry commands). |
|
varies |
CDJ → iPad settings-panel snapshot; flag bytes at offsets 36, 37, 46, 47 toggle when the iPad changes a pref. |
|
124 B |
iPad → CDJ pref-write channel — see below. |
|
68 B (also seen) |
CDJ → iPad settings-panel state refetch, fires when the user enters a CDJ settings submenu (~10 sends in 30 min, clustered 8–21 s before each prefs-step the operator made). |
|
varies |
Device-list snapshots from CDJ — listed but not decoded; cardinality suggests they enumerate the network’s other devices. |
|
56 B |
iPad → CDJ transport-command channel — see below. |
Two of these are the Stagehand command channel: the route by which the iPad pushes CDJ preference changes and CDJ transport actions onto the wire. Phase 5 of the original analysis spent three weeks trying to find a TCP control channel for these commands; it turned out there is no TCP, just two UDP types that had been hiding in plain sight because earlier scans were marker-window-driven rather than transition-first.
Pref-Write Channel (0x6b, 124 B)
The 124 B 0x6b packet on port 50002 carries CDJ preference writes from the iPad to the CDJ-3000.
The body type at byte 24 distinguishes a write (0x01) from a poll (0x00); writes carry the new value, polls do not.
The verified writes:
| Body offset | Preference | Encoding |
|---|---|---|
|
On-air display |
|
|
Quantize |
|
Both were confirmed by capturing the iPad → CDJ packet immediately before the user-visible value flipped on the CDJ screen, and by repeating the toggle in both directions to confirm directionality.
Transport-Command Channel (0x07, 56 B)
The 56 B 0x07 packet on port 50001 carries CDJ transport commands.
Byte 2b of the body encodes the action; byte 2d is a press / release flag (0x01 press, 0x00 release).
| Action byte | Operation |
|---|---|
|
Play (paired with |
|
Play (paired with |
|
Skip forward. |
|
Skip backward (also seen for some pause transitions). |
|
Seek forward. |
|
Seek backward. |
Body byte 21 is a coupled sub-type / view-state hash that correlates with the action byte — the same Stagehand UI tap fires both bytes in a single packet.
The 0x07 channel fires for app launch, UI navigation, and the transport actions above; it does not fire for Live-mode toggles, which stay iPad-local.
This protocol is markedly different from dysentery’s existing load-track command — there is no acknowledgment flow, no payload beyond the action byte and the press/release flag, and the channel is open continuously rather than opening a TCP session per command. The simplicity is consistent with a touch-surface controller rather than an automation interface.
Live Mode Stays Local
Live mode is the one Stagehand toggle that stays entirely on the iPad. Across two full MITM captures bracketing six Live-mode taps each, no iPad-originated packet of any kind fired at the moment of any tap. The Live-mode setting therefore does not propagate to the CDJ or the A9 via the wire — it gates something inside the Stagehand UI itself (probably which on-screen controls are active).
CDJ Broadcast Extras Observed Alongside Stagehand
Two broadcasts the CDJ-3000 emits that this analysis hadn’t seen before, and that may or may not be Stagehand-specific:
-
A 5-packet
0x04burst from the DJM-A9 on port 50001 within ~7 s of a new CDJ joining the network. This was discovered when CDJ#2 powered on during a Phase 5a capture; the A9 fired 5 × 100-byte0x04packets in rapid succession. Hypothesis: the A9 maintains a per-CDJ binding table and re-broadcasts the table when a new player enters, so the existing devices can refresh their picture of "who is on the network". -
The
0x00pre-claim phase on the A9 join (the dysentery-documented mixer startup does not include this). Three0x00-typed packets are sent before the0x02claim, each advertising the A9’s MAC address. This may be an AlphaTheta-era MAC-collision check added to the legacy mixer handshake.
Neither was provoked under deliberate control, so the interpretations are hypothesis.
What’s Missing
A lot of the surface above is still under-decoded. The biggest gaps:
-
The
0x39master / effects block (offsets0xac–0x110) is structurally visible but no master/FX controls were provoked under MITM. This is the easiest unmapped surface to pick up — the per-control provocation recipe that decoded the per-channel blocks should work directly. -
The
0x58VU stream almost certainly carries per-channel level meters, but every capture so far has been with no audio passing through the mixer. A capture with audio across all four channels would confirm. -
The Stagehand → A9 command opcodes in the
0x00 b0–0x00 b8range haven’t been mapped to UI actions. A scripted UI tour under MITM (tap each channel select / cue button / FX block in sequence) would build the mapping table. -
The
0x0bround-robin sub-types on the CDJ unicast channel — 8 distinct sub-types are known to exist, but the per-sub-type semantics are open. -
The
0x3dtrack-metadata body past byte ~0x150— the post-string region almost certainly encodes BPM / key / length / cues / loops / colour / rating / file path but has not been correlated against a known-track-set load. -
The two opaque
0x56reply variants — 196-byte (likely cue/loop list, decodable by two-track diff) and 316-byte (likely AES-CBC, decodable only with the AlphaTheta-Connect key). -
Multi-deck SYNC / MASTER handshake between two CDJ-3000s with Stagehand attached — needs a two-deck rig.
-
The device-number-collision behaviour when Stagehand’s random 141–211 happens to collide with a connected device — never directly observed, only the structural avoidance of slots 1–6 and 33.
The biggest open structural question is whether any part of Stagehand’s protocol is encrypted at rest in the AlphaTheta-Connect mobile app — the 316-byte 0x56 reply is a strong candidate for AES-CBC, but the key has not been recovered.
A mobile-app reverse-engineering pass on AlphaTheta-Connect would close that gap and unlock the encrypted metadata variant.