Timed fades, mute-style matrix sends, meters on matrix send knobs
- Every set-level action (input, bus, matrix and control group faders, aux and matrix sends) has a fade time and a curve: fader travel (natural), fader travel with ease in/out, or linear in dB. Fades step at 20 Hz, land exactly on the target, never overshoot, and are cancelled by any new move on the same fader; new "Stop all fades" action, fades_running variable and fade_running feedback. Verified on the S31 with a switched-off matrix send: 20 writes in 1 s, ending exactly on target, then restored. - Matrix sends now look like mutes in the presets: red when the send is off, black when it is on (new matrix_send_off feedback, true only when the send is known to be off). - Matrix send knobs carry a meter column on the right. The S31 has no meter for matrix inputs, so it shows the destination output's meter. Meter gauges use a faint track so an empty meter reads as empty. - Test fixes: the meter-resubscribe check no longer depends on a silent channel changing value, plus back-to-back /Meters/clear coverage. npm test: 504 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:
+55
-14
@@ -1,4 +1,4 @@
|
||||
import { LEVEL, LIMITS, PARAMS } from '../protocol.js'
|
||||
import { FADE_CURVES, LEVEL, LIMITS, PARAMS } from '../protocol.js'
|
||||
|
||||
export const STATE_CHOICES = [
|
||||
{ id: 'on', label: 'On' },
|
||||
@@ -41,6 +41,30 @@ export const opt = {
|
||||
step: 0.5,
|
||||
showMinAsNegativeInfinity: true,
|
||||
}),
|
||||
/** Fade time of a "set level" action; the curve choice shows only while it is above 0 (so `duration` cannot be an expression). */
|
||||
duration: (id = 'duration') => ({
|
||||
type: 'number',
|
||||
id,
|
||||
label: 'Fade time (ms, 0 = instant)',
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 60000,
|
||||
step: 100,
|
||||
asInteger: true,
|
||||
disableAutoExpression: true,
|
||||
}),
|
||||
curve: (id = 'curve') => ({
|
||||
type: 'dropdown',
|
||||
id,
|
||||
label: 'Fade curve',
|
||||
default: 'fader',
|
||||
choices: [
|
||||
{ id: 'fader', label: 'Fader travel (natural)' },
|
||||
{ id: 'ease', label: 'Fader travel, ease in/out' },
|
||||
{ id: 'db', label: 'Linear in dB' },
|
||||
],
|
||||
isVisibleExpression: '$(options:duration) > 0',
|
||||
}),
|
||||
delta: (id = 'delta', label = 'Change (dB, for encoders)') => ({ type: 'number', id, label, default: 1, min: -30, max: 30, step: 0.5 }),
|
||||
pan: (id = 'pan', label = 'Pan (0 = left, 0.5 = centre, 1 = right)') => ({ type: 'number', id, label, default: 0.5, min: 0, max: 1, step: 0.01 }),
|
||||
pdelta: (id = 'pdelta', label = 'Pan change (for encoders)') => ({ type: 'number', id, label, default: 0.05, min: -1, max: 1, step: 0.01 }),
|
||||
@@ -50,6 +74,12 @@ export const opt = {
|
||||
/** Said in every level adjust action: a relative step cannot start from -inf, so the first step up jumps to -60 dB. */
|
||||
const ADJUST_NOTE = 'From −∞ the first step up jumps to −60 dB.'
|
||||
|
||||
/** Said in every level set action: the optional fade. */
|
||||
const FADE_NOTE = 'With a fade time above 0 the level moves from where it is now to the target in steps of 50 ms; another set, adjust or fade on the same level ends it.'
|
||||
|
||||
/** [fade time in ms (0 = instant), curve] from a set-level action's options; missing options (buttons made before fades existed) mean instant. */
|
||||
const fadeArgs = (options) => [Number(options?.duration) > 0 ? Math.min(60000, Math.round(Number(options.duration))) : 0, FADE_CURVES.includes(options?.curve) ? options.curve : 'fader']
|
||||
|
||||
export const numbers = (v) => (Array.isArray(v) ? v : v === undefined || v === '' ? [] : [v]).map(Number).filter(Number.isFinite)
|
||||
|
||||
/** Which entity kinds get actions / feedbacks for a parameter: 'input', 'buss', 'matrix' and/or 'minput' (matrix input). */
|
||||
@@ -132,8 +162,9 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
input_fader_set: {
|
||||
name: 'Input: set fader',
|
||||
options: [opt.channel(inst), opt.level()],
|
||||
callback: async ({ options }) => inst.setFader('input', n(options.channel), n(options.level)),
|
||||
description: FADE_NOTE,
|
||||
options: [opt.channel(inst), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setFader('input', n(options.channel), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
input_fader_adjust: {
|
||||
name: 'Input: adjust fader (relative)',
|
||||
@@ -164,8 +195,9 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
send_level_set: {
|
||||
name: 'Aux send: set level',
|
||||
options: [opt.channel(inst), opt.buss(inst, 'aux'), opt.level()],
|
||||
callback: async ({ options }) => inst.setSendLevel(n(options.channel), n(options.buss), n(options.level)),
|
||||
description: FADE_NOTE,
|
||||
options: [opt.channel(inst), opt.buss(inst, 'aux'), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setSendLevel(n(options.channel), n(options.buss), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
send_level_adjust: {
|
||||
name: 'Aux send: adjust level (relative)',
|
||||
@@ -201,8 +233,9 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
buss_fader_set: {
|
||||
name: 'Buss: set fader',
|
||||
options: [opt.buss(inst), opt.level()],
|
||||
callback: async ({ options }) => inst.setFader('buss', n(options.buss), n(options.level)),
|
||||
description: FADE_NOTE,
|
||||
options: [opt.buss(inst), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setFader('buss', n(options.buss), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
buss_fader_adjust: {
|
||||
name: 'Buss: adjust fader (relative)',
|
||||
@@ -223,8 +256,9 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
matrix_fader_set: {
|
||||
name: 'Matrix output: set fader',
|
||||
options: [opt.matrix(inst), opt.level()],
|
||||
callback: async ({ options }) => inst.setFader('matrix', n(options.matrix), n(options.level)),
|
||||
description: FADE_NOTE,
|
||||
options: [opt.matrix(inst), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setFader('matrix', n(options.matrix), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
matrix_fader_adjust: {
|
||||
name: 'Matrix output: adjust fader (relative)',
|
||||
@@ -239,9 +273,9 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
matrix_send_level_set: {
|
||||
name: 'Matrix send: set level',
|
||||
description: 'A matrix send goes down to −90 dB (−∞).',
|
||||
options: [opt.matrixIn(inst), opt.matrix(inst, 'matrixOut'), opt.level('level', 'Level (dB)', LEVEL.matrixSendMin)],
|
||||
callback: async ({ options }) => inst.setMatrixSendLevel(n(options.matrixIn), n(options.matrixOut), n(options.level)),
|
||||
description: `A matrix send goes down to −90 dB (−∞). ${FADE_NOTE}`,
|
||||
options: [opt.matrixIn(inst), opt.matrix(inst, 'matrixOut'), opt.level('level', 'Level (dB)', LEVEL.matrixSendMin), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setMatrixSendLevel(n(options.matrixIn), n(options.matrixOut), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
matrix_send_level_adjust: {
|
||||
name: 'Matrix send: adjust level (relative)',
|
||||
@@ -262,8 +296,9 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
cg_fader_set: {
|
||||
name: 'Control group: set fader',
|
||||
options: [opt.cg(inst), opt.level()],
|
||||
callback: async ({ options }) => inst.setFader('cg', n(options.cg), n(options.level)),
|
||||
description: FADE_NOTE,
|
||||
options: [opt.cg(inst), opt.level(), opt.duration(), opt.curve()],
|
||||
callback: async ({ options }) => inst.setFader('cg', n(options.cg), n(options.level), ...fadeArgs(options)),
|
||||
},
|
||||
cg_fader_adjust: {
|
||||
name: 'Control group: adjust fader (relative)',
|
||||
@@ -305,6 +340,12 @@ export function getActionDefinitions(inst) {
|
||||
},
|
||||
snapshot_next: { name: 'Snapshot: next', options: [], callback: async () => inst.fireNextSnapshot() },
|
||||
snapshot_prev: { name: 'Snapshot: previous', options: [], callback: async () => inst.firePrevSnapshot() },
|
||||
fade_stop: {
|
||||
name: 'Stop all fades',
|
||||
description: 'Ends every running level fade where it is.',
|
||||
options: [],
|
||||
callback: async () => inst.stopFades(),
|
||||
},
|
||||
reload: {
|
||||
name: 'Console: reload names and levels',
|
||||
options: [],
|
||||
|
||||
Reference in New Issue
Block a user