Name matrix inputs, fix the meter scale, add output meters

Found on the live S31 (read-only, plus meter subscriptions):

- Matrix inputs are named at /Matrix_Inputs/N/Channel_Input/name (an
  address missing from DiGiCo's 2014 list). Their names now appear in
  every matrix-send dropdown, preset and the new matrix_in_N_name
  variables, falling back to "Matrix In N" when empty. Matrix inputs also
  get trim and phase.
- Console names are space-padded where the surface wraps them; every name
  is now trimmed and its whitespace runs collapsed ("LEAD V RESI").
- Meter values are inverted: 127 is silence and lower is louder, about
  1 dB per step below full scale (silent channels read 127, the room mic
  36-48). The module treated 127 as peak. Level meters now read dBFS
  (approximate), gauges span -60..0 dBFS, signal and peak thresholds are
  in dBFS; gain reduction reads dB of reduction, 127 meaning none.
- Bus and matrix outputs have meters (/Aux_Outputs, /Group_Outputs and
  /Matrix_Outputs/N/Output/meter): new signal, peak, value and gauge
  feedbacks and "Buss meters" / "Matrix meters" presets.

3002 presets, 2.7 MB. npm test: 425 checks pass on Node 25 and on
Companion's Node 22. Deployed and checked on the booth Companion 5.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 10:25:39 -04:00
co-authored by Claude Opus 5.5
parent 9f64d20a24
commit 52777f3d7c
11 changed files with 436 additions and 152 deletions
+24 -8
View File
@@ -3,7 +3,7 @@ import { ConsoleLink } from './console-link.js'
import { Relay } from './relay.js'
import { decode, describe, foldPattern } from './osc.js'
import { State } from './state.js'
import { GEQ_RANGE, LEVEL, PARAMS, K, addr, adjustedLevel, clamp, indexProblem, meterId, meterSubscription, parse, query, sameValue } from './protocol.js'
import { GEQ_RANGE, LEVEL, PARAMS, K, addr, adjustedLevel, clamp, indexProblem, meterId, meterSubscription, outputMeterId, outputMeterSubscription, parse, query, sameValue } from './protocol.js'
import { getActionDefinitions } from './definitions/actions.js'
import { getFeedbackDefinitions, feedbackIdsForEvent, watchKey, watchKeyForEvent } from './definitions/feedbacks.js'
import { getVariableDefinitions, getAllVariableValues, getEventVariableValues, describeChange } from './definitions/variables.js'
@@ -216,7 +216,7 @@ export default class DigicoS31Instance extends InstanceBase {
this.lastDefs = {} // JSON of what was last pushed, per kind of definition
this.definedVariables = new Set()
this.presetStats = null
this.meterWanted = new Map() // meter id -> { n, meter }
this.meterWanted = new Map() // meter id -> { ent: 'input' | 'buss' | 'matrix', n, meter }
this.meterDirtyIds = new Set()
this.peakUntil = new Map() // meter id -> time until which its peak hold is re-checked
this.meterTimer = null
@@ -1048,7 +1048,7 @@ export default class DigicoS31Instance extends InstanceBase {
const s = this.state.summary()
this.log(
'info',
`load complete: ${s.inputs} inputs, ${s.busses} busses (${s.aux} aux and ${s.group} group named), ${s.matrix} matrix outputs, ` +
`load complete: ${s.inputs} inputs, ${s.busses} busses (${s.aux} aux and ${s.group} group named), ${s.matrix} matrix outputs, ${s.matrixIn} matrix inputs, ` +
`${s.cgs} control groups, ${s.geqs} graphic EQs, ${s.snapshots} snapshots`,
)
if (!this.connectedFlag || s.total === 0) this.reloadOnReconnect = true
@@ -1121,7 +1121,8 @@ export default class DigicoS31Instance extends InstanceBase {
for (const [id, value] of ev.pairs) {
if (!this.watchers.has(watchKey.meter(id))) continue
this.meterDirtyIds.add(id)
if (value >= 127) this.peakUntil.set(id, now + this.timing.peakWatchMs)
// within 20 dB of full scale on a level meter: a peak hold may be running (gain reduction meters have none)
if (value <= 20 && this.meterWanted.get(id)?.meter === 'level') this.peakUntil.set(id, now + this.timing.peakWatchMs)
}
return
}
@@ -1138,6 +1139,7 @@ export default class DigicoS31Instance extends InstanceBase {
onStructural(ev) {
if (ev.kind === 'bussTypes' && this.state.loaded) {
for (const n of this.state.existing('buss')) this.enqueue(this.state.bussValueQueries(n))
this.resubscribeMeters() // a buss meter lives on the buss's aux or group side
}
this.scheduleRebuild()
}
@@ -1225,20 +1227,34 @@ export default class DigicoS31Instance extends InstanceBase {
// ---------- meters ----------
/** The [address, args] that subscribes one wanted meter; a buss meter follows the buss's current aux / group side. */
meterSubscriptionFor({ ent, n, meter }) {
if (ent === 'input') return meterSubscription(n, meter)
return outputMeterSubscription(ent, n, this.sideOf(ent, n))
}
/** Subscribe to an input meter ('level', 'gate' or 'comp' gain reduction) the first time a meter feedback for it runs. */
subscribeInputMeter(n, meter = 'level') {
const id = meterId(n, meter)
this.subscribeMeter(meterId(n, meter), { ent: 'input', n, meter })
}
/** Subscribe to a buss ('buss') or matrix output ('matrix') level meter, lazily like the input meters. */
subscribeOutputMeter(ent, n) {
this.subscribeMeter(outputMeterId(ent, n), { ent, n, meter: 'level' })
}
subscribeMeter(id, entry) {
if (this.meterWanted.has(id)) return
const sub = meterSubscription(n, meter)
const sub = this.meterSubscriptionFor(entry)
if (!this.addressOk(sub[0])) return
this.meterWanted.set(id, { n, meter })
this.meterWanted.set(id, entry)
this.enqueue([sub])
this.ensureMeterTimer()
}
resubscribeMeters() {
if (!this.meterWanted.size) return
this.enqueue([...this.meterWanted.values()].map(({ n, meter }) => meterSubscription(n, meter)))
this.enqueue([...this.meterWanted.values()].map((entry) => this.meterSubscriptionFor(entry)))
}
scheduleMeterResubscribe() {