Add a feedback for every controllable state, matrix send presets

A matrix-send knob looked dead on the booth Companion although the console
followed it (verified through Companion's rotate API: -90 -> -60 -> -59):
the module had matrix send actions but no matrix send feedbacks.

- Every action with a readable state now has a matching feedback: matrix
  send on/level text/level bar, matrix and control group solo and fader
  bars, graphic EQ in and band gain text/bar, aux send pan text, and value
  text for trim, gain and filter frequencies. A test checks the parity.
- New action: matrix output solo.
- Matrix sends use the console's -90 dB floor: adjusting down from -inf no
  longer writes -150 there.
- Presets: "Matrix sends to <output>" (on/off and knob per matrix input)
  and graphic EQ in/out. 2970 presets, 1.8 MB.

npm test: 368 checks pass on Node 25 and on Companion's Node 22.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 15:44:43 -04:00
co-authored by Claude Opus 5.5
parent 57645be1e9
commit 90dee14444
9 changed files with 351 additions and 57 deletions
+28 -10
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 { 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, 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'
@@ -730,10 +730,27 @@ export default class DigicoS31Instance extends InstanceBase {
if (s.level === undefined || s.on === undefined) this.ensure(`send:${ch}:${buss}`, [addr.sendLevel(ch, buss), addr.sendOn(ch, buss)])
}
ensureSendPan(ch, buss) {
if (this.state.getSend(ch, buss).pan === undefined) this.ensure(`pan:${ch}:${buss}`, [addr.sendPan(ch, buss)])
}
ensureGroup(ch, buss) {
if (this.state.getGroup(ch, buss) === undefined) this.ensure(`group:${ch}:${buss}`, [addr.groupAssign(ch, buss)])
}
ensureMatrixSend(inN, outN) {
const m = this.state.getMatrixSend(inN, outN)
if (m.level === undefined || m.on === undefined) this.ensure(`msend:${inN}:${outN}`, [addr.matrixSendLevel(inN, outN), addr.matrixSendOn(inN, outN)])
}
ensureGeqOn(n) {
if (this.state.getGeq(n).on === undefined) this.ensure(`geq:${n}:on`, [addr.geqIn(n)])
}
ensureGeqBand(n, band) {
if (this.state.getGeq(n).gains?.get(Number(band)) === undefined) this.ensure(`geq:${n}:gain:${band}`, [addr.geqGain(n, band)])
}
ensureParam(ent, n, param) {
const address = addr.param(ent, n, this.sideOf(ent, n), param)
if (address && this.state.getParam(ent, n, param) === undefined) this.ensure(`param:${ent}:${n}:${param}`, [address])
@@ -758,21 +775,22 @@ export default class DigicoS31Instance extends InstanceBase {
return this.write(address, [on ? 1 : 0], on ? 1 : 0)
}
writeLevel(address, dB) {
/** Write a dB level, clamped to [lo, +10]; `lo` is -150 except for matrix sends (-90). */
writeLevel(address, dB, lo = LEVEL.min) {
if (!this.addressOk(address)) return false
const v = clamp(Number(dB), LEVEL.min, LEVEL.max)
const v = clamp(Number(dB), lo, LEVEL.max)
if (!Number.isFinite(v)) return false
return this.write(address, [v], v)
}
async adjustLevel(address, key, get, delta) {
async adjustLevel(address, key, get, delta, lo = LEVEL.min) {
if (!this.addressOk(address)) return false
const cur = await this.current(key, get, address)
if (cur === undefined) {
this.log('warn', `${address}: current level unknown (no reply from the console), not adjusted`)
return false
}
return this.writeLevel(address, adjustedLevel(cur, delta))
return this.writeLevel(address, adjustedLevel(cur, delta, lo), lo)
}
writePan(address, pan) {
@@ -857,11 +875,11 @@ export default class DigicoS31Instance extends InstanceBase {
}
setMatrixSendLevel(inN, outN, dB) {
return this.writeLevel(addr.matrixSendLevel(inN, outN), dB)
return this.writeLevel(addr.matrixSendLevel(inN, outN), dB, LEVEL.matrixSendMin)
}
adjustMatrixSendLevel(inN, outN, delta) {
return this.adjustLevel(addr.matrixSendLevel(inN, outN), K.msend(inN, outN, 'level'), () => this.state.getMatrixSend(inN, outN).level, delta)
return this.adjustLevel(addr.matrixSendLevel(inN, outN), K.msend(inN, outN, 'level'), () => this.state.getMatrixSend(inN, outN).level, delta, LEVEL.matrixSendMin)
}
setGeqOn(n, mode) {
@@ -871,7 +889,7 @@ export default class DigicoS31Instance extends InstanceBase {
setGeqBand(n, band, gain) {
const address = addr.geqGain(n, band)
if (!this.addressOk(address)) return false
const v = clamp(Number(gain), -12, 12)
const v = clamp(Number(gain), GEQ_RANGE.min, GEQ_RANGE.max)
if (!Number.isFinite(v)) return false
return this.write(address, [v], v)
}
@@ -1153,8 +1171,8 @@ export default class DigicoS31Instance extends InstanceBase {
this.definedVariables = new Set(Object.keys(vars))
this.presetStats = stats
const changedPresets = this.pushIfChanged('presets', { structure, presets }, () => this.setPresetDefinitions(structure, presets))
if (changedPresets && stats.droppedSendKnobs) {
this.log('warn', `Too many presets (limit reached): the per-input send level knobs were left out (${stats.count} presets, ${stats.bytes} bytes)`)
if (changedPresets && stats.dropped.length) {
this.log('warn', `Too many presets (limit reached): left out ${stats.dropped.join(' and ')} (${stats.count} presets, ${stats.bytes} bytes)`)
}
this.queueVariables(getAllVariableValues(this))
this.publishRelay()