Reverse engineering the Helix Stadium XL editor protocol

Published on December 21, 2025

With Christmas approaching and work stopping for the year, naturally with more free time what does a developer with a brand new Helix Stadium XL do when they see the editor communicates over WiFi for editing? You go digging into how it works and here’s what I’ve discovered. I really should be drinking eggnog and doing nothing, but as other devs know it’s hard to stop sometimes, haha. I like to keep busy.

I am writing this as a record of what I observed on my own device and network. It is not an official protocol, it will likely change, and you should always respect Line 6 licensing and support policies. My goal here is simple interoperability and curiosity.

The editor talks to the unit over TCP using an OSC-like message format. There are two ports involved. One is used for device to editor updates and heartbeat traffic. The other is used for editor to device control messages. The OSC messages are not plain UDP, and there is some additional framing plus a handshake layer that the editor performs before it will accept any commands.

I discovered this by capturing traffic while making small, repeatable edits in the editor. I did this on the same Wi-Fi network as the Stadium XL and only touched a handful of parameters to keep the captures short and easy to diff.

  • Attempted to change only one parameter at a time.
  • Switched block models to see how that was represented.
  • Captured traffic with tcpdump on macOS.

Discovery and connection flow

The editor is a native macOS app (if you’re on macOS), not Electron, and it uses Bonjour for discovery. The device advertises itself with a service called _stadiumserver._tcp. You can see it with dns-sd on macOS:

# List devices
/usr/bin/dns-sd -B _stadiumserver._tcp

# Resolve a specific device name
/usr/bin/dns-sd -L p35x1 _stadiumserver._tcp

On my network the service resolved to p35x1.local with a TCP port. That gives you the address and port the editor uses to connect. I saw two ports in use once the editor was running:

  • 2001 for device to editor updates and heartbeats.
  • 2002 for editor to device control messages.

If you capture traffic with tcpdump while adjusting parameters you will see both directions:

sudo /usr/sbin/tcpdump -i en0 -nn -s 0 -U -w /tmp/helix-stadium.pcap host 192.168.50.62

The ZMTP handshake (and why a plain TCP client fails)

At first I assumed I could connect to port 2002 and send OSC immediately. The device reset the connection every time. That turned out to be the key missing piece: the editor performs a ZeroMQ ZMTP 3.0 handshake before any OSC is sent.

Here is the observed flow on both 2001 and 2002:

  • The client sends a 64-byte ZMTP greeting (mechanism is NULL).
  • The server replies with its own 64-byte greeting.
  • The client sends a READY command frame describing its socket type.
  • The server replies with a READY command frame describing its socket type.

Socket types matter:

  • On 2002, the editor behaves like a DEALER and the device behaves like a ROUTER.
  • On 2001, the editor behaves like a SUB socket and then sends a SUBSCRIBE message with an empty topic. In practice that looks like a short data frame with a one-byte payload (0x01).

Once this handshake is completed, the payloads are ZMTP data frames that contain OSC messages. If you skip the ZMTP greeting and READY exchange, the Stadium drops the connection immediately.

The other gotcha is large frames. ZMTP uses a flag bit for “long” frames, which means the payload size is encoded as an 8-byte big-endian integer. It is not a two-byte or 0xFF sentinel length. If you only parse short frames, you will mangle anything sizeable like /getEditBufferState.

This handshake is the missing step that made /SetSnapshotName and other writes start working reliably.

There is also a Remote Access setting on the Stadium itself. If that is set to deny or require a PIN, your connection will either fail or need an extra authorisation step. I have not reversed the PIN flow yet, but it is clearly part of the device’s security surface.

OSC messages inside ZMTP frames

The payloads are OSC-like: string addresses such as /ParamValueSet, a typetag string, then typed parameters. The difference is how the OSC messages are framed.

Port 2002: editor -> device

Each ZMTP data frame on port 2002 contains a raw OSC packet. A typical parameter change looks like this:

/ParamValueSet ,iiiiifi [cmdId, path, block, 0, paramId, value, -1]

Example from my capture when changing a parameter:

/ParamValueSet ,iiiiifi [109, 1, 6, 0, 2, 0.532000005, -1]

That is the editor asking the device to update a specific parameter on a specific block. The cmdId appears to be a monotonic counter. The -1 at the end shows up consistently and looks like a flags or target placeholder.

Port 2001: device -> editor

On port 2001, the OSC payloads are wrapped in a 12-byte header before the OSC message body. That header includes a version, a sequence value, and the OSC message length. After that you get a plain OSC packet:

/setParamValue ,iiiiiif [sessionId, cmdId, path, block, 0, paramId, value]

This is the device telling the editor what it applied. The device also emits /heartbeat messages on a steady cadence so the editor knows the connection is alive.

The device sends acknowledgements over port 2002 as /status messages:

/status ,iii [cmdId, 0, 1]

That looks like a lightweight success indicator for each command id.

Snapshot names and scribble strips

Once the ZMTP handshake is in place, I was able to write snapshot names directly. The editor sends:

/SetSnapshotName ,iis [cmdId, snapshotIndex, "Name"]

The device replies on port 2001 with:

/setSnapshotName ,iiis [sessionId, cmdId, snapshotIndex, "Name"]

Then it acknowledges on port 2002 with /status ,iii [cmdId, 0, 0].

That was the missing step for scripting snapshot labels and turning the Stadium into a custom “prompter” for song notes. It also confirmed that a lot of “why does nothing happen?” issues are actually handshake related.

Scribble strip labels use /PropertyValueSet with a msgpack blob. I already covered the label key format in the repo tooling, so I will not repeat it here, but it works once the ZMTP handshake is completed.

Model IDs and the bundled modeldefs file

The most useful message for auto-mapping blocks is /ModelSet. When you change a block model in the editor, the editor sends /ModelSet with a model ID. The device then responds with /setModelWithMID using the same ID.

Here is what I captured while swapping models:

/ModelSet ,iiiii [127, 0, 1, 0, 22]
/setModelWithMID ,iiiiiii [66564, 127, 0, 1, 0, 22, -1]

Those numeric IDs do not match the IDs in ModelMetadataStore.sqlite3. Instead they match the IDs in a binary model definition file bundled with the app:

/Applications/Line6/Helix Stadium.app/Contents/Resources/modeldefs/p35md-26002601-1_2_0_0.bin

That file is a msgpack stream. The last object is a big dictionary keyed by model names like Agoura_AmpWhoWatt103 or HD2_DistDerangedMasterMono. Each entry includes an integer id field. That id is what /ModelSet uses.

Once you parse that file, you can map model IDs to model keys and also map parameter IDs to names. The parameter map inside the modeldefs entry looks like this:

"params": {
  "Interval2": {"id": 5, "type": "i", ...},
  "Mix": {"id": 9, "type": "f", ...}
}

So if you see /ParamValueSet with paramId = 5 on a block that you know is HD2_PitchDualPitchMono, you can label it as Interval2 without manual mapping.

I verified this by switching models and then changing a couple of parameters. For example:

  • MID 22 maps to HX2_GateHorizonGateMono and that lined up with the Horizon Gate block I selected.
  • MID 368 maps to HD2_DistDerangedMasterMono and that matched the Deranged Master block.
  • MID 808 maps to Agoura_AmpWhoWatt103 and that matched the WhoWatt 103 amp.
  • MID 750 maps to Agoura_AmpUSPrincess76 and that matched US Princess 76.

Those mappings came straight out of the modeldefs file and the observed /ModelSet packets.

What this enables (and what it does not)

This gives you enough information to build simple tooling that reads and writes parameter values without the official editor. You can discover the device, connect, and then emit the same OSC messages the editor emits.

It does not give you permission to do anything abusive or to bypass licensing. It also does not tell you anything about the DSP or audio path. It is just an editor protocol. If you experiment, do it carefully, use a spare preset, and keep a backup.

Given this info, you could build:

  • A command-line tool to read and write parameter values.
  • A web-based editor that mimics some of the official app’s functionality.
  • An integration with other audio software for remote control.
  • A custom preset management tool that syncs with the device.
  • An educational project to learn about OSC and network protocols.
  • A monitoring tool that logs parameter changes over time.
  • A testing tool to validate parameter changes and device responses.
  • A simple mobile app to control basic parameters remotely.

I will keep exploring, but for now this is a good stopping point. The protocol is relatively clean, the message set is discoverable and it’s fun to reverse engineer. If you build anything interesting with this info, let me know.