Skip to content

v1.0.7: fix dLive/GLD/Qu connections, correct fader levels, dB cue storage#20

Merged
johnqherman merged 11 commits into
masterfrom
v1.0.7
Jul 18, 2026
Merged

v1.0.7: fix dLive/GLD/Qu connections, correct fader levels, dB cue storage#20
johnqherman merged 11 commits into
masterfrom
v1.0.7

Conversation

@johnqherman

Copy link
Copy Markdown
Collaborator

No description provided.

Connecting to a dLive sat in Connecting forever with nothing logged. The
handshake sent E0 00 04 02 03 as a "session seed", but that is the console's
half of the port exchange, not the client's: the client sends
E0 00 04 01 03 <ourUdpPort:2> E7 and waits for the console to answer with its
own port before anything else is accepted. Subscribes were being fired at a
console still waiting on that exchange, so it never replied, and nothing timed
out because the TCP connect had already stopped the connect timer.

- Send the correct seed, carrying the local port of a UDP socket bound for the
  purpose, and start the subscribe chain only when the reply lands.
- Keep-alive is E0 00 01 03 E7 over UDP every 1000ms, not the seed over TCP
  every 5000ms; the console drops a session silent for 1.5s.
- Add an RX watchdog and a handshake timeout so a stall reports instead of
  hanging.
- Write our source id at [5..6] of SET frames; 00 00 addressed nothing.
- TcpTransport: carry the socket error enum (Qt renders an unmapped errno as a
  bare "Unknown error"), and abort a busy socket before reconnecting, which
  connectToHost() rejects outright.

Frames recovered from theatremix.exe. test_allenheath_session drives a real
DLiveProtocol against a stub MixRack that answers only a correct seed; it fails
against the old handshake.
Auto-scan never listed a dLive. Two causes:

- The ACE by-name request answers with a handle, not a name. We parsed that
  14-byte handle frame as the identity string, got garbage, matched no model
  prefix, and dropped the console as Unknown. Read the handle back and parse
  the answer to that instead.
- Probes went out from an ephemeral source port. The reference binds 32323;
  consoles may answer to it rather than to whatever port we happened to get.

Also normalize IPv4-mapped senders (::ffff:a.b.c.d): a dual-stack socket
reports them in that form, which then reaches the driver as the literal string
to connect to, and the console never answers on it.

The read-back framing is a best reading of an ambiguous decompilation and wants
confirming against hardware.
Qu was inheriting SQ's driver, but the two share a transport and nothing else.
Per the Qu Mixer MIDI Protocol V1.9+ (Qu-16/24/32):

- The NRPN halves are swapped. Qu puts the channel in the MSB and the parameter
  id in the LSB (BN 63 CH, BN 62 ID); SQ is the other way round. Every fader,
  mute and DCA message was going out with the two bytes backwards.
- Fader is ID 0x17 with a 7-bit value (0 dB = 0x62), not SQ's 14-bit pair.
- Mute is Note On/Off, not an NRPN, so the console ignored ours entirely.
- 4 DCA groups (0x10..0x13) and 100 scenes, not 8 and 300.
- Scene recall sets both Bank Select MSB and LSB.

Fader values come from the doc's Fader / Send Level table, reproduced exactly
at every printed anchor.
DCA_LEVEL_LSB_BASE was 0x67, citing the SQ Level table. That number is not in
it: 0x67 is the SQ+ value (SQ+ MIDI Protocol V2.0.3, which covers SQ5+/6+/7+,
different hardware). SQ Issue 5 p24 and Qu Issue 2 p25 agree on 0x20, so every
DCA fader move was addressing the wrong parameter.

Levels also went out as a plain level*16383, which under the console's Linear
Taper means a fader at unity sent -24.5 dB rather than 0 dB. Encode through the
console's own law instead:

- Linear Taper is exactly linear in dB (0 dB = 15196, 118.7 steps/dB) and
  reproduces every printed anchor within the doc's own rounding.
- Audio Taper is a curve, so it carries the printed table and interpolates.

The law is a console setting (Utility > General > MIDI > NRPN Fader Law) that
cannot be read back, so it is selectable and defaults to Linear, the doc's
standard mode.
X32: a console that answers /info but not /xinfo could never connect, because
the connect gates on the /xinfo reply. The reference probes with /info, and
/xinfo appears nowhere in it. Send both and connect on whichever lands.
Renew /xremote every 4.5s as the reference does, not 8s: the console stops
sending updates ~10s after the last renew, so at 8s a single dropped packet
silently stalls all updates until the next one. The staleness window keeps its
own constant rather than riding on the renew interval.

WING: /$info and /$xremote do not exist. Identity is "/?", which returns
WING,<ip>,<name>,<model>,<serial>,<firmware>, and unsolicited updates need a
"/*s~" subscription the console drops after 10s unless renewed. We were gating
the connect on a reply that would never arrive and renewing a command that does
not exist, so no updates could ever have arrived. Scene recall goes through the
console's own $ctl/lib nodes; the OSC doc does not cover the show library, so
that part is unconfirmed.

Ports were right: X32 OSC/UDP 10023 and WING OSC/UDP 2223 are both documented.
A 0..1 position is not a level until a console applies its fader law, so the
same show file played back at different levels per desk: a 0.75 cue meant 0 dB
on a dLive and -24.5 dB on an SQ. Carry dB from the cue to the wire instead,
and let each driver encode it the way its console wants. This is what the
reference does: its Allen & Heath drivers take the level as dB text, clamped to
+10 and -95, with -999 standing in for -inf.

- Cue stores dB under channelLevelsDb. Shows written before this still load:
  their positions convert through the law that was in force then, which is the
  best that can be done, since what a position meant depended on the console.
- setChannelFader/setDcaFader become setChannelFaderDb/setDcaFaderDb, so the
  compiler finds every call site rather than silently reinterpreting it.
- Deletes three invented curves: ACE now passes dB straight to its table,
  Yamaha is dB*100 centi-dB, and WING sends dB to /fdr (its curve was the X32
  law copy-pasted). X32 keeps a conversion because its wire format really is a
  position; the segments are the published law.
- Fades ramp to the bottom of the throw and only then go -inf, rather than
  diving through -900 dB.
- CueEditor edits dB; DCAWidget and the X32 parameter cache follow.
Test call sites follow the setChannelFaderDb/setDcaFaderDb rename.
The console maps NRPN levels through one of two curves, chosen at
Utility > General > MIDI > NRPN Fader Law, and does not report which. Both
curves were already implemented, but the driver always assumed Linear: on a desk
set to Audio Taper every level was wrong.

Carry the choice in MixerConfig so it travels with the show, expose it in the
connection panel for SQ, and hand it to the driver on connect. Defaults to
linear, the console's standard mode, so existing shows are unchanged. The combo
only appears for SQ: Qu-16/24/32 has a single fixed curve and the setting means
nothing to the other consoles.
GLD was driven over the ACE binary protocol on 51321, sharing dLive's driver.
Allen & Heath document a different interface for it, and that is the one to
speak: MIDI over TCP 51325, per the GLD MIDI and TCP/IP Protocol V1.4.

GLD sits between SQ and Qu and matches neither:
- Like Qu, the channel goes in the NRPN MSB and mutes are Note On, but the
  fader NRPN carries no data-entry LSB: three messages, not four.
- Its level table is its own. 0 dB is 0x6B, where Qu's is 0x62.
- Names and colours are SysEx (header 03/06), not NRPN.
- Channels: DCA 1-16 = 10..1F, Input 1-48 = 20..4F.

The doc also corrects two counts we had wrong: 16 DCAs (we said 8) and 500
scenes in 4 banks (we said 250).

The console stamps its MIDI channel into every message and cannot report it, so
it has to match Setup / Control on the desk: carry it in MixerConfig alongside
the SQ fader law, expose it for GLD in the connection panel, and hand it to the
driver on connect. Discovery is unchanged: GLD still does not answer the ACE
identify handshake, so it stays a connect-time selection.

Every frame and the whole level table are tested against the doc's printed
values.
The SD driver spoke Yamaha DM7's MPRO/EEVT/MMIX protocol, pointed at Allen &
Heath's port 51321, under a comment claiming it was reverse-engineered from a
DiGiCo reference. It was not: the frames, the "TYPE:StageMix SYNCDIR:2" client
registration and the object tree are all DM7's, and no DiGiCo console speaks
them. It could never have worked.

DiGiCo's actual network control is Generic OSC, and its shape is unusual: there
is no published address map. The console's own feature is user-defined by
design - the operator writes the messages, "*" stands for the channel - and the
syntax varies by model and software version, with command sets handed out by
DiGiCo support rather than documented. Inbound OSC only acts when External
Control is enabled on the desk.

So the driver is a template. It sends the patterns the operator entered and
nothing for an operation they left blank, because an address we invented would
look like it worked here and do nothing on the console. The patterns and the
receive port live in MixerConfig, so they travel with the show, and the
connection panel takes them for SD consoles.

Capabilities follow: OSC over UDP, port 9000 (the common pairing, not a spec).
The tests cover pattern expansion and, mainly, that an unconfigured operation
stays silent.
Comment-only. The rationale for levels being dB was repeated across LevelDb.h,
Cue.h, Show.h and three driver headers; it now lives once in LevelDb.h and the
rest point there. Class headers come back to the length of the ones around them,
and a few comments that described the edit rather than the code lost that
framing.

Byte layouts, doc provenance and the notes about console settings that cannot be
read back all stay: none of them are visible in the code.

One was wrong rather than just long: GLDProtocol said names longer than the limit
were "the console's to reject, not ours to send", while buildName() truncates
with left(). It now states what the protocol doc says - 8 accepted, 5 displayed.
@johnqherman
johnqherman merged commit b96f135 into master Jul 18, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant