From fae1dd28f97e48adbd7aa23e7d8696603d190956 Mon Sep 17 00:00:00 2001 From: John A Knight Jr Date: Thu, 18 Jul 2024 16:35:28 -0700 Subject: [PATCH] Fix scope fb/actions and a cuelist bug --- actions.js | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++ choices.js | 36 +++++++++------- feedbacks.js | 87 ++++++++++++++++++++++++++++---------- package.json | 2 +- presets.js | 78 ++++++++++++++++++++++++++++++++++ qlabfb.js | 9 ++-- 6 files changed, 285 insertions(+), 42 deletions(-) diff --git a/actions.js b/actions.js index 405bc5b..6515afa 100644 --- a/actions.js +++ b/actions.js @@ -1,5 +1,6 @@ import * as Colors from './colors.js' import * as Choices from './choices.js' +import { Regex } from '@companion-module/base' const cueListActions = ['go', 'next', 'panic', 'previous', 'reset', 'stop', 'togglePause'] @@ -244,6 +245,120 @@ export function compileActionDefinitions(self) { await sendCommand(action, pfx + cmd) }, }, + new_loadAt: { + name: 'Load Cue(s) At', + description: 'Load to Time with optional cue selection', + options: [ + { + type: 'dropdown', + label: 'Mode', + id: 'mode', + default: 'S', + choices: Choices.TIME_MODE, + }, + { + type: 'dropdown', + label: 'Scope', + id: 'scope', + default: 'D', + choices: Choices.SCOPE, + }, + { + type: 'textinput', + label: 'Cue Number', + id: 'q_num', + default: self.nextCue.q_num, + useVariables: true, + isVisible: (options, data) => { + return options.scope === 'N' + }, + }, + { + type: 'textinput', + label: 'Cue ID', + id: 'q_id', + default: self.nextCue.q_id, + useVariables: true, + isVisible: (options, data) => { + return options.scope === 'I' + }, + }, + { + type: 'textinput', + label: 'Hour', + id: 'hh', + length: 2, + default: 0, + useVariables: true, + regex: Regex.NUMBER, + isVisible: (options, data) => { + return options.mode === 'S' + }, + }, + { + type: 'textinput', + label: 'Minute', + id: 'mm', + length: 2, + default: 0, + useVariables: true, + regex: Regex.NUMBER, + isVisible: (options, data) => { + return options.mode === 'S' + }, + }, + { + type: 'textinput', + label: 'Seconds', + id: 'ss', + default: 0, + useVariables: true, + regex: Regex.FLOAT, + }, + ], + callback: async (action, context) => { + const opt = action.options + const scope = opt.scope + let cmd = '/loadAt' + let pfx = '' + let sfx = opt.mode == 'D' ? '/-' : opt.mode == 'I' ? '/+' : '' + + switch (scope) { + case 'S': + pfx = '/cue/selected' + break + case 'N': + let qnum = await context.parseVariablesInString(opt.q_num.trim()) + pfx = `/cue/${qnum}` + break + case 'I': + let qid = await context.parseVariablesInString(opt.q_id.trim()) + pfx = `/cue_id/${qid}` + } + + let s = parseFloat(await context.parseVariablesInString(opt.ss)) + let args = [] + + if (opt.mode === 'S') { + let h = parseInt(await context.parseVariablesInString(opt.hh)) + let m = parseInt(await context.parseVariablesInString(opt.mm)) + if (Math.abs(h) + Math.abs(m) > 0) { + args.push({ type: 'i', value: h }) + args.push({ type: 'i', value: m }) + } + } + args.push({ type: 'f', value: s }) + + await sendCommand(action, pfx + cmd + sfx, args) + }, + }, + add_slice: { + name: 'Add Slice', + options: [], + callback: async (action, context) => { + await sendCommand(action, '/cue/selected/addSliceMarker') + } + }, previous: { name: 'Previous Cue', options: [], diff --git a/choices.js b/choices.js index 5b012cb..e3c77ea 100644 --- a/choices.js +++ b/choices.js @@ -14,6 +14,11 @@ export const GROUP_MODE = [ 'Playlist', // 6 ] +export const TIME_MODE = [ + { id: 'S', label: 'Set Time' }, + { id: 'I', label: 'Increase by' }, + { id: 'D', label: 'Decrease by' }, +] export const SCOPE = [ { id: 'D', label: 'Playhead' }, { id: 'S', label: 'Selected' }, @@ -23,6 +28,7 @@ export const SCOPE = [ export const FB_SCOPE = [ { id: 'D', label: 'Playhead/Next' }, + { id: 'R', label: 'Running Cue'}, { id: 'N', label: 'Cue Number' }, { id: 'I', label: 'Cue ID' }, ] @@ -39,19 +45,19 @@ export const ON_OFF = [ ] export const OVERRIDE = [ - { id: 'midiInputEnabled', label: 'Midi Input [4, 5]' }, - { id: 'midiOutputEnabled', label: 'Midi Output [4, 5]' }, - { id: 'mscInputEnabled', label: 'MSC Input [4, 5]' }, - { id: 'mscOutputEnabled', label: 'MSC Output [4, 5]' }, - { id: 'sysexInputEnabled', label: 'SysEx Input [4, 5]' }, - { id: 'sysexOutputEnabled', label: 'SysEx Output [4, 5]' }, - { id: 'oscOutputEnabled', label: 'OSC Output [4]' }, - { id: 'timecodeInputEnabled', label: 'Timecode Input [4, 5]' }, - { id: 'timecodeOutputEnabled', label: 'Timecode Output [4, 5]' }, - { id: 'artNetEnabled', label: 'Art-Net Enabled [4]' }, - { id: 'dmxOutputEnabled', label: 'DMX Output [5]' }, - { id: 'networkExternalInputEnabled', label: 'External Network Input [5]' }, - { id: 'networkExternalOutputEnabled', label: 'External Network Output [5]' }, - { id: 'networkLocalInputEnabled', label: 'Local Network Input [5]' }, - { id: 'networkLocalOutputEnabled', label: 'Local Network Output [5]' }, + { id: 'midiInputEnabled', label: 'Midi Input [4, 5]', preset: '' }, + { id: 'midiOutputEnabled', label: 'Midi Output [4, 5]', preset: '45' }, + { id: 'mscInputEnabled', label: 'MSC Input [4, 5]', preset: '' }, + { id: 'mscOutputEnabled', label: 'MSC Output [4, 5]', preset: '45' }, + { id: 'sysexInputEnabled', label: 'SysEx Input [4, 5]', preset: '' }, + { id: 'sysexOutputEnabled', label: 'SysEx Output [4, 5]', preset: '45' }, + { id: 'oscOutputEnabled', label: 'OSC Output [4]', preset: '4' }, + { id: 'timecodeInputEnabled', label: 'Timecode Input [4, 5]', preset: '' }, + { id: 'timecodeOutputEnabled', label: 'Timecode Output [4, 5]', preset: '45' }, + { id: 'artNetEnabled', label: 'Art-Net Enabled [4]', preset: '' }, + { id: 'dmxOutputEnabled', label: 'DMX Output [5]', preset: '5' }, + { id: 'networkExternalInputEnabled', label: 'External Network Input [5]', preset: '' }, + { id: 'networkExternalOutputEnabled', label: 'External Network Output [5]', preset: '5' }, + { id: 'networkLocalInputEnabled', label: 'Local Network Input [5]', preset: '' }, + { id: 'networkLocalOutputEnabled', label: 'Local Network Output [5]', preset: '5' }, ] diff --git a/feedbacks.js b/feedbacks.js index 01effe9..af98be0 100644 --- a/feedbacks.js +++ b/feedbacks.js @@ -2,6 +2,23 @@ import { combineRgb } from '@companion-module/base' import * as Choices from './choices.js' export function compileFeedbackDefinitions(self) { + function getScope(opt) { + let cue + switch (opt.scope) { + case 'D': + cue = self.nextCue + break + case 'R': + cue = self.runningCue.uniqueID + break; + case 'N': + cue = self.cueByNum[opt.q_num?.replace(/[^\w\.]/gi, '_')] + break + case 'I': + cue = opt.q_id + } + return cue + } return { playhead_bg: { type: 'advanced', @@ -96,17 +113,8 @@ export function compileFeedbackDefinitions(self) { }, callback: async (feedback, context) => { const opt = feedback.options - let cue - switch (opt.scope) { - case 'D': - cue = self.nextCue - break - case 'N': - cue = self.cueByNum[opt.q_num?.replace(/[^\w\.]/gi, '_')] - break - case 'I': - cue = opt.q_id - } + let cue = getScope(opt) + return self.wsCues[cue]?.isArmed }, }, @@ -149,20 +157,55 @@ export function compileFeedbackDefinitions(self) { }, callback: async (feedback, context) => { const opt = feedback.options - let cue - switch (opt.scope) { - case 'D': - cue = self.nextCue - break - case 'N': - cue = self.cueByNum[opt.q_num?.replace(/[^\w\.]/gi, '_')] - break - case 'I': - cue = opt.q_id - } + let cue = getScope(opt) return self.wsCues[cue]?.isflagged }, }, + q_paused: { + type: 'boolean', + name: 'Cue is Paused', + description: 'Indicate on button when the specified cue is Paused', + options: [ + { + type: 'dropdown', + label: 'Scope', + id: 'scope', + default: 'R', + choices: Choices.FB_SCOPE, + }, + { + type: 'textinput', + label: 'Cue Number', + id: 'q_num', + default: self.nextCue.q_num, + useVariables: true, + isVisible: (options, data) => { + return options.scope === 'N' + }, + }, + { + type: 'textinput', + label: 'Cue ID', + id: 'q_id', + default: self.nextCue.q_id, + useVariables: true, + isVisible: (options, data) => { + return options.scope === 'I' + }, + }, + ], + defaultStyle: { + bgcolor: combineRgb(102, 0, 102), + color: combineRgb(255, 255, 255), + }, + callback: async (feedback, context) => { + const opt = feedback.options + let cue = getScope(opt) + + return self.wsCues[cue]?.isPaused + }, + }, + q_run: { type: 'boolean', name: 'Indicate Cue is running', diff --git a/package.json b/package.json index ce0a865..4204f9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "figure53-qlab-advance", - "version": "2.7.2", + "version": "2.7.3", "main": "qlabfb.js", "type": "module", "scripts": { diff --git a/presets.js b/presets.js index 3a77529..cc0df25 100644 --- a/presets.js +++ b/presets.js @@ -1,6 +1,7 @@ import { combineRgb } from '@companion-module/base' import Icons from './icons.js' import * as Colors from './colors.js' +import * as Choices from './choices.js' // determine text color for a background color function textColor(pbin) { @@ -431,6 +432,83 @@ export function compilePresetDefinitions(self) { feedbacks: [], } + // Workspace Overrides + + let dSteps = [] + let eSteps = [] + const ver = self.qVer + for (let o of Choices.OVERRIDE) { + if (o.preset.includes(`${ver}`)) { + dSteps.push({ + actionId: 'overrides', + options: { + which: o.id, + onOff: '0', + }, + }) + eSteps.push({ + actionId: 'overrides', + options: { + which: o.id, + onOff: '1', + }, + }) + } + } + + presets['ws-disable'] = { + type: 'button', + category: 'Workspace', + name: 'Disable a Workspace', + style: { + bgcolor: combineRgb(192, 0, 0), + text: 'Disable Workspace', + tooltip: 'Turn output overrides ON (for a Backup System).\nPrevents transmitting control commands twice.', + size: 13, + color: combineRgb(250, 250, 250), + }, + steps: [ + { + down: [ + ...dSteps, + { + actionId: 'overrideWindow', + options: { + onOff: '0', + }, + }, + ], + up: [], + }, + ], + } + presets['ws-enable'] = { + type: 'button', + category: 'Workspace', + name: 'Enable a Workspace', + style: { + bgcolor: combineRgb(0, 192, 0), + text: 'Enable Workspace', + tooltip: 'Turn output overrides OFF (for a Primary System).\nAllows transmitting control commands.', + size: 13, + color: combineRgb(250, 250, 250), + }, + steps: [ + { + down: [ + ...eSteps, + { + actionId: 'overrideWindow', + options: { + onOff: '0', + }, + }, + ], + up: [], + }, + ], + } + // Editing presets presets['edit-prewait-1s-'] = { type: 'button', category: 'Edit', diff --git a/qlabfb.js b/qlabfb.js index e3103f4..92bfb7c 100644 --- a/qlabfb.js +++ b/qlabfb.js @@ -56,7 +56,7 @@ class QLabInstance extends InstanceBase { '"isBroken","continueMode","percentActionElapsed","cartPosition","infiniteLoop","holdLastFrame"]', }, ] - fb2check = ['q_run', 'qid_run', 'any_run', 'q_armed', 'q_bg', 'qid_bg', 'q_flagged'] + fb2check = ['q_run', 'qid_run', 'any_run', 'q_armed', 'q_bg', 'qid_bg', 'q_flagged', 'q_paused'] constructor(internal) { super(internal) @@ -489,7 +489,7 @@ class QLabInstance extends InstanceBase { if (0 == this.pollCount % (this.config.useTenths ? 10 : 4)) { this.sendOSC('/overrideWindow', [], true) - this.sendOSC((this.cl ? '/cue/' + this.cl : '') + `/playhead${phID}`, []) + this.sendOSC((this.cl ? '/cue_id/' + this.cl : '') + `/playhead${phID}`, []) if (5 == this.qVer) { this.sendOSC('/alwaysAudition', [], true) @@ -672,7 +672,7 @@ class QLabInstance extends InstanceBase { this.qSocket.on('message', (message, timetag, info) => { const node = message.address.split('/') - this.log('debug', 'received ' + JSON.stringify(message) + `from ${this.qSocket.options.address}`) + //this.log('debug', 'received ' + JSON.stringify(message) + `from ${this.qSocket.options.address}`) if ('update' == node[1]) { // debug("readUpdate"); this.readUpdate(message) @@ -920,7 +920,7 @@ class QLabInstance extends InstanceBase { switch (ms.slice(-1)[0]) { case 'playbackPosition': - const cl = ms[3] + const cl = ms[4] if (message.args.length > 0) { const oa = message.args[0].value if (this.cl) { @@ -1098,6 +1098,7 @@ class QLabInstance extends InstanceBase { this.config.useTenths ? 100 : 250 ) } else { + this.init_presets(); this.needWorkspace = this.qVer > 3 && this.useTCP } break