Files
companion-module-digico-s31/tools
michaelandClaude Opus 5.5 8693fe924a Replace the S31 catalog with the probe's final run
The first copy came from an intermediate run (951 of 1363 addresses). The
final run (995 of 1380) adds a verification pass that re-queries addresses
also seen in other controllers' traffic, and shows that the /Console count
queries never answer: the counts only arrive as a burst on first contact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:16:00 -04:00
..

Developer tools

Two small, dependency-free Node.js tools (Node 22+) for developing the S31 module without a console. They share osc-lite.js, a tiny OSC encoder/decoder.

tools/package.json pins this folder to CommonJS, because the module's own package.json is "type": "module" and would otherwise make Node load these files as ESM. Keep it.

Both tools speak two OSC dialects, chosen with --dialect:

Dialect What it is
sseries (default) The lowercase S-Series command list: /console/ping, /channel/N/fader, ...
sd The "DiGiCo Pad" dialect the real S31 iPad extension actually speaks: /Console/Name/?, /Input_Channels/N/fader/?, ... Verified against a live console.

mock-s31.js - fake S31 console

Emulates the console's OSC extension. It listens on one UDP port and sends all feedback to one configured controller IP:port, whoever asked and from whichever source port, like the real desk.

node tools/mock-s31.js [--dialect sseries|sd] [--listen 8000] [--reply-to IP:PORT]
                       [--channels N] [--auxes N] [--bool-int] [--live]
                       [--types] [--quiet]
Flag Meaning
--dialect sseries (default) or sd.
--listen UDP port to bind (the console's receive port). Default 8000.
--reply-to Fixed feedback destination, e.g. 127.0.0.1:9000. If omitted, locks onto the first sender's IP with port 9000 and prints that.
--channels Input count, 1-60. Default 48 (sseries) or 60 (sd).
--auxes Aux busses / sends per channel. Default 16, max 16 (sseries); default 25, max 25 (sd).
--bool-int sseries only: send booleans as int 0/1 instead of OSC T/F.
--live Every 3 s, change a random send level or send on/off and emit it.
--types Also print each message's OSC type tags, e.g. /Input_Channels/1/fader ,f [-1.4375].
--quiet Don't print every message.

Traffic is printed as <- ADDRESS [args] from IP:PORT and -> ADDRESS [args]. Whole-number floats print with a decimal (-10.0) so you can tell int from float.

sseries dialect

No args at a readable address = query (reply with the value); with an arg = set (store, then echo to the controller). /console/resend dumps the whole state at ~2 ms per packet. Values are clamped (fader -150..10, send level -90..10, pan -1..1). Addresses: /console/{ping,channel/counts,resend}, /channel/N/{name,fader,mute,solo,pan}, /channel/N/total/gain (send-only, sent with fader changes), /channel/N/send/M/{level,enabled,pan}, and /digico/snapshots/fire[/next|/previous] (logged only). Inputs are 1-60, aux busses 70..70+auxes-1.

sd dialect

A query is the address plus a trailing /? and no args; the reply is the same address without /? plus the value. A set is the address without /? and one arg; it is stored and echoed back in the console's reply types.

Query Reply type
/Console/Name/? s ("S31 AB")
/Console/{Input_Channels,Aux_Outputs,Group_Outputs,Control_Groups,Matrix_Outputs,Matrix_Inputs,Graphic_EQ,Talkback_Outputs,Multis}/? i
/Console/Aux_Outputs/modes/? one i per aux (2 = stereo, 1 = mono)
/Input_Channels/N/Channel_Input/name/?, /Aux_Outputs/N/Buss_Trim/name/?, /Group_Outputs/N/Buss_Trim/name/? s (read-only). The 25 busses appear under both Aux_Outputs and Group_Outputs; a buss reports its name on the side matching its mode and "" on the other (captured names).
/Input_Channels/N/fader/?, /Aux_Outputs/N/fader/?, /Group_Outputs/N/fader/? f, dB, -150 = -inf, max +10 (group 24 = -1.6875)
/Input_Channels/N/mute/?, /Aux_Outputs/N/mute/?, /Group_Outputs/N/mute/? f 0/1 (a float, not an int)
/Input_Channels/N/Aux_Send/M/send_level/? f, dB, -150..10
/Input_Channels/N/Aux_Send/M/send_on/? f 0/1
/Input_Channels/N/Aux_Send/M/send_pan/? f, 0..1, 0.5 = centre
/Input_Channels/N/Aux_Send/M/send_pre-post/? i (captured as an int, default 0; settable, clamped 0..1 - range is an assumption)
/Input_Channels/N/Group_Send/M/group/? f 0/1 group assign, M = 1..25. Default: inputs 1-8 and 18-21 on group 24, nothing else. The reply type is the single constant SD_GROUP_ASSIGN_WIRE_TYPE in mock-s31.js ('f', assumed like mute/send_on; set 'i' if the real console reports an int).
/Snapshots/Current_Snapshot/? i
/Snapshots/names/? one /Snapshots/name ,iiis [index, cue*100, 0, "name"] per snapshot

Sets accept int or float for mute/send_on (any non-zero = on) and clamp fader/send_level to -150..10 and send_pan to 0..1. Like the real console it stays silent on everything else: all lowercase S-Series addresses, /Console/Resend, /Console/Channels/?, /Input_Channels/N/Aux_Send/M/?, /Input_Channels/N/Aux_Send/?, /Macros/Buttons/?, out-of-range channel/send numbers, and sets of read-only names. (--bool-int has no effect in sd.)

fake-ipad.js - stand-in for the DiGiCo iPad app

Binds the receive port, sends its opening requests, repeats a keepalive every 5 s, prints everything it receives and a packet-count summary every 10 s.

node tools/fake-ipad.js --console IP --send-port 8000 --receive-port 9000
                        [--dialect sseries|sd] [--name A] [--types] [--quiet]
  • sseries: sends /console/ping, then /console/resend; keepalive /console/ping; /console/pong counts as a pong.
  • sd: sends /Console/Name/?, /Console/Input_Channels/?, /Console/Aux_Outputs/?, /Console/Aux_Outputs/modes/?, names of inputs 1-8 and aux outputs 1-4, then /Snapshots/names/? (20 ms apart); keepalive /Console/Name/?; a /Console/Name reply counts as a pong.

Type ADDRESS [arg ...] lines on stdin to send your own messages (true/false = boolean, 5 = int, 5.0 or -12.5 = float, anything else or "quoted" = string; quit exits):

/channel/3/send/2/level -12.5                     # sseries set
/Input_Channels/3/Aux_Send/2/send_on 1            # sd set (echo comes back as ,f [1])
/Input_Channels/3/Aux_Send/2/send_level/?         # sd query
/channel/3/name "Lead Vox"

End-to-end loops on one machine

The module sits between the console and the iPad: the console's feedback goes to the module (port 9000), and the module relays to and from the iPad. Use the ports the module is actually configured with; 9100 and 9200 below are just examples for "the relay's iPad-facing port" and "where the relay forwards feedback to the iPad".

S-Series dialect

# 1. Mock console: listens on 8000, replies to whatever listens on 9000
node tools/mock-s31.js --listen 8000 --reply-to 127.0.0.1:9000

# 2. Start the Companion module (or any relay) listening on 9000, sending to 127.0.0.1:8000

# 3. Fake iPad pointed at the relay (sends to 9100, receives the relayed feedback on 9200)
node tools/fake-ipad.js --console 127.0.0.1 --send-port 9100 --receive-port 9200

SD dialect (what the real S31 speaks)

# 1. Mock console in SD mode: listens on 8000, replies to 127.0.0.1:9000
node tools/mock-s31.js --dialect sd --listen 8000 --reply-to 127.0.0.1:9000 --types

# 2. Start the Companion module (or any relay) listening on 9000, sending to 127.0.0.1:8000

# 3. Fake iPad (SD) pointed at the relay
node tools/fake-ipad.js --dialect sd --console 127.0.0.1 --send-port 9100 --receive-port 9200 --types

No module yet? Skip step 2 and point the iPad straight at the mock to check the tools themselves, e.g. node tools/fake-ipad.js --dialect sd --console 127.0.0.1 --send-port 8000 --receive-port 9000.