diff --git a/buildHADefs.js b/buildHADefs.js index 6003718..fc50161 100644 --- a/buildHADefs.js +++ b/buildHADefs.js @@ -2,24 +2,56 @@ import { pad0 } from './helpers.js' export function buildHADefs(self) { let haActions = {} - let haVariables = {} + let haVariables = [] + let ppRange = [1,4] + const mc = self.config.channels for (let s = 1; s <= 24; s++) { let c = pad0(s) let baseID = `/headamp/${c}/` - let baseFID = 'ha' + c + let fID = `ha_gain${c}` + let vID = `ha_gain${s}` let theID = baseID + 'gain' self.fbToStat[fID] = theID self.xStat[theID] = { - name: 'ha' + c + '_gain', + varID: vID, valid: false, fbID: fID, polled: 0, } - haVariables.push({ - name: 'Head Amp ' + c , - variableId: fID, - }) + if (self.HA_CONFIG[s][mc].has) { + let haName = self.HA_CONFIG[s][mc].name + 'Gain' + + + + + haVariables.push({ + name: haName + ' %', + variableId: vID + '_p', + }) + haVariables.push({ + name: haName + ' dB', + variableId: vID + '_d', + }) + if (self.HA_CONFIG[s].ph) { + fID = `ha_pp${c}` + vID = `ha_pp${s}` + theID = baseID + 'phantom' + self.fbToStat[fID] = theID + self.xStat[theID] = { + varID: vID, + valid: false, + fbID: fID, + polled: 0, + pp: false, + } + haVariables.push({ + name: self.HA_CONFIG[s][mc].name + ' Phantom', + variableId: vID, + }) + ppRange[2] = Math.max(ppRange[2],s) + } + } } self.variableDefs.push(...haVariables) diff --git a/buildSoloDefs.js b/buildSoloDefs.js index 703428b..8e64ca9 100644 --- a/buildSoloDefs.js +++ b/buildSoloDefs.js @@ -22,6 +22,7 @@ export function buildSoloDefs(self) { for (const cmd of defSolo) { const pfx = cmd.prefix const cMap = cmd.cmdMap + let fbDescription switch (cmd.id) { case 'solosw': for (const cm in cmd.cmdMap) { @@ -148,6 +149,7 @@ export function buildSoloDefs(self) { varID: soloID, valid: false, polled: 0, + fbSubs: new Set(), } soloFbToStat[actID] = c if (ch.isFader) { @@ -215,56 +217,152 @@ export function buildSoloDefs(self) { variableId: soloID + '_rp', }) } else { - soloActions[actID] = { - name: 'Solo ' + ch.description, - options: [], - callback: async (action, context) => { - let strip = `${c}` - let arg = { type: 'i', value: setToggle(self.xStat[strip].isOn, action.options.set) } - await self.sendOSC(strip, arg) - }, - } - soloActions[actID].options.push({ - type: 'dropdown', - label: 'Value', - id: 'set', - default: '2', - choices: [ - { id: '1', label: 'On' }, - { id: '0', label: 'Off' }, - { id: '2', label: 'Toggle' }, - ], - }) - soloActions[actID] - stat[c].isOn = false - let fbDescription = 'Solo ' + ch.description + ' status' - soloFeedbacks[actID] = { - type: 'boolean', - name: 'Indicate ' + fbDescription, - description: 'Indicate ' + fbDescription + ' on button', - options: [ - { + switch (ch.actID) { + case 'source': + soloActions[actID] = { + name: 'Monitor Source', + options: [ + { + type: 'dropdown', + label: 'Source', + id: 'source', + default: 0, + choices: self.MONITOR_SOURCES, + }, + ], + callback: async (action, context) => { + let arg = { type: 'i', value: action.options.source } + await self.sendOSC(c, arg) + }, + } + stat[c].varID = 'm_source' + soloVariables.push({ + name: 'Monitor Source', + variableId: 'm_source', + }) + fbDescription = 'Monitor Source' + soloFeedbacks[actID] = { + type: 'boolean', + name: fbDescription, + description: 'Indicate ' + fbDescription + ' is on button', + options: [ + { + type: 'dropdown', + label: 'Source', + id: 'source', + default: 0, + choices: self.MONITOR_SOURCES, + }, + ], + defaultStyle: { + color: combineRgb(255, 255, 255), + bgcolor: combineRgb.apply(this, ch.bg), + }, + // subscribe: async (feedback, context) => { + // const fbID = feedback.feedbackId + // if (fbID) { + // self.xStat[self.fbToStat[fbID]].fbSubs.add(feedback.id) + // } + // }, + // unsubscribe: async (feedback, context) => { + // const fbID = feedback.feedbackId + // if (fbID) { + // self.xStat[self.fbToStat[fbID]].fbSubs.delete(feedback.id) + // } + // }, + callback: function (feedback, context) { + var fbWhich = feedback.feedbackId + var stat = self.xStat[self.fbToStat[fbWhich]] + var source = feedback.options.source + + return stat.m_source == source + }, + } + break + case 'chmode': + case 'busmode': + soloActions[actID] = { + name: ch.description, + options: [ + { + type: 'dropdown', + label: 'Value', + id: 'set', + default: '1', + choices: [ + { id: '1', label: 'AFL' }, + { id: '0', label: 'PFL' }, + ], + }, + ], + callback: async (action, context) => { + let arg = { type: 'i', value: parseInt(action.options.set) } + await self.sendOSC(c, arg) + }, + } + stat[c].varID = 'm_' + ch.actID + soloVariables.push({ + name: ch.description, + variableId: stat[c].varID, + }) + break + default: + soloActions[actID] = { + name: 'Solo ' + ch.description, + options: [], + callback: async (action, context) => { + let strip = `${c}` + let arg = { type: 'i', value: setToggle(self.xStat[strip].isOn, action.options.set) } + await self.sendOSC(c, arg) + }, + } + soloActions[actID].options.push({ type: 'dropdown', - label: 'State', - id: 'state', - default: '1', + label: 'Value', + id: 'set', + default: '2', choices: [ { id: '1', label: 'On' }, { id: '0', label: 'Off' }, + { id: '2', label: 'Toggle' }, ], - }, - ], - defaultStyle: { - color: combineRgb(255, 255, 255), - bgcolor: combineRgb.apply(this, ch.bg), - }, - callback: function (feedback, context) { - var fbWhich = feedback.feedbackId - var stat = self.xStat[self.fbToStat[fbWhich]] - var state = feedback.options.state != '0' + }) + stat[c].isOn = false + fbDescription = 'Solo ' + ch.description + ' status' + soloFeedbacks[actID] = { + type: 'boolean', + name: 'Indicate ' + fbDescription, + description: 'Indicate ' + fbDescription + ' on button', + options: [ + { + type: 'dropdown', + label: 'State', + id: 'state', + default: '1', + choices: [ + { id: '1', label: 'On' }, + { id: '0', label: 'Off' }, + ], + }, + ], + defaultStyle: { + color: combineRgb(255, 255, 255), + bgcolor: combineRgb.apply(this, ch.bg), + }, + callback: function (feedback, context) { + var fbWhich = feedback.feedbackId + var stat = self.xStat[self.fbToStat[fbWhich]] + var state = feedback.options.state != '0' - return stat.isOn == state - }, + return stat.isOn == state + }, + } + stat[c].varID = 's_' + ch.actID + soloVariables.push({ + name: ch.description, + variableId: stat[c].varID, + }) + break } } } diff --git a/companion/HELP.md b/companion/HELP.md index f95cb2d..d2f9be5 100644 --- a/companion/HELP.md +++ b/companion/HELP.md @@ -39,6 +39,10 @@ Contributions for development and maintenance of this open source module are alw | Channel, USB, FX Send, Fx Return, Bus and Main Solo | Solos the selected Channel, USB, FX Send, Fx Return, Bus and Main | | Clear Solo | Clears all active solos | | Solo Level Set | Sets the level of the Solo (monitor) output | +| Set Monitor Source | Sets the Source of the Monitor output | +| Channel Solo Mode | Set solo mode for Channels (AFL/PFL) | +| Channel Solo Mode | Set solo mode for Channels (AFL/PFL) | +| PFL Dim | Enable/Disable PFL Dim/Attenuation | | Solo Level Adjust | Adjust the Solo level up or down by steps **see notes* | | Solo Dim | Dims the Solo output level to the value configured in the console. | | Solo Mute | Mutes the Solo output | @@ -100,6 +104,11 @@ Contributions for development and maintenance of this open source module are alw | **$(INSTANCENAME:f_fxsend#_d)** | FX Bus Master # Fader dB | | **$(INSTANCENAME:f_fxsend#_p)** | FX Bus Master # Fader Percent | | **$(INSTANCENAME:f_fxsend#_rp)** | FX Bus Master # Fader Relative Loudness Percent | +| **$(INSTANCENAME:m_source)** | Current Monitor Output Source | +| **$(INSTANCENAME:m_chmode)** | Channel Solo Mode (AFL/PFL) | +| **$(INSTANCENAME:m_busmode)** | Bus Solo Mode (AFL/PFL) | +| **$(INSTANCENAME:m_dimpfl)** | Solo PFL Attenuation Enabled (true/false) | +| **$(INSTANCENAME:m_dim)** | Solo Dim Enabled (true/false) | | **$(INSTANCENAME:f_solo_d)** | Solo (monitor) output level dB | | **$(INSTANCENAME:f_solo_p)** | Solo (monitor) output level Percent | | **$(INSTANCENAME:f_fxsend#_rp)** | FX Bus Master # Fader Relative Loudness Percent | @@ -142,11 +151,13 @@ Contributions for development and maintenance of this open source module are alw | **Indicate Main LR Solo** * | Changes the button when Main LR Solo on | | **Indicate USB/Aux Solo** * | Changes the button when USB/Aux Solo on | | **Indicate processing status** * | Changes the button according to the selected channel/process status | +| **Indicate Monitor Source** * | Changes the button when Monitor Source is set to selected option | | **Color when Solo Mute** * | Sets the button color when the Solo output is muted | | **Color when Solo Mono** * | Sets the button color when the Solo output is mono | +| **Color when Solo Dim PFL** * | Sets the button color when the Solo PFL is dimmed | | **Color when Solo Dim** * | Sets the button color when the Solo output is dimmed | | **Color when Any Solo Active** | Sets the button color when 'Clr Solo' is active | -| **Meter Bar** | Adds a graphic meter bar for the selected channel/bus to the button | +| **Meter Bar** | Adds a graphic meter bar for the selected channel/bus to the button | ## Notes diff --git a/config.js b/config.js index 49e05c9..fbd4ed1 100644 --- a/config.js +++ b/config.js @@ -1,9 +1,9 @@ import { Regex } from '@companion-module/base' // Return config fields for web config - export function getConfigFields() { - let cf = [] - cf.push({ +export function getConfigFields() { + let cf = [ + { type: 'textinput', id: 'host', label: 'Target IP', @@ -11,32 +11,43 @@ import { Regex } from '@companion-module/base' width: 6, default: '0.0.0.0', regex: Regex.IP, - }) - cf.push({ + }, + { type: 'checkbox', id: 'scan', label: 'Scan network for XAir mixers?', default: true, width: 12, - }) + }, + { + type: 'dropdown', + id: 'model', + label: 'Select Model', + tooltip: 'This model is assumed when mixer is offline', + width: 6, + default: 'X18', + choices: this.MIXER_CHOICES, + }, + ] - let ch = [] - if (Object.keys(this.unitsFound || {}).length == 0) { - ch = [{ id: 'none', label: 'No XAir units located' }] - } else { - let unit = this.unitsFound - for (let u in unit) { - ch.push({ id: unit[u].m_name, label: `${unit[u].m_name} (${unit[u].m_ip})` }) - } + let ch = [] + if (Object.keys(this.unitsFound || {}).length == 0) { + ch = [{ id: 'none', label: 'No XAir units located' }] + } else { + let unit = this.unitsFound + for (let u in unit) { + ch.push({ id: unit[u].m_name, label: `${unit[u].m_name} (${unit[u].m_ip})` }) } - cf.push({ - type: 'dropdown', - id: 'mixer', - label: 'Select Mixer by Name', - tooltip: 'Name and IP of mixers on the network', - width: 12, - default: ch[0].id, - choices: ch, - }) - return cf } + + cf.push({ + type: 'dropdown', + id: 'mixer', + label: 'Select Mixer by Name', + tooltip: 'Name and IP of mixers on the network', + width: 12, + default: ch[0].id, + choices: ch, + }) + return cf +} diff --git a/constants.js b/constants.js index 58f5246..3fca2bd 100644 --- a/constants.js +++ b/constants.js @@ -89,6 +89,24 @@ export function buildConstants(self) { { label: 'White Inverted', id: '15', bg: 0, fg: combineRgb(224, 224, 224) }, ] + self.MONITOR_SOURCES = [ + { id: 0, label: 'Off' }, + { id: 1, label: 'LR' }, + { id: 2, label: 'LR (PFL)' }, + { id: 3, label: 'LR (AFL)' }, + { id: 4, label: 'AUX' }, + { id: 5, label: 'USB 17/18' }, + { id: 6, label: 'Bus 1' }, + { id: 7, label: 'Bus 2' }, + { id: 8, label: 'Bus 3' }, + { id: 9, label: 'Bus 4' }, + { id: 10, label: 'Bus 5' }, + { id: 11, label: 'Bus 6' }, + { id: 12, label: 'Bus 1/2' }, + { id: 13, label: 'Bus 3/4' }, + { id: 14, label: 'Bus 5/6' }, + ] + self.TAPE_FUNCTIONS = [ { label: 'STOP', id: '0' }, { label: 'PLAY PAUSE', id: '1' }, @@ -113,6 +131,14 @@ export function buildConstants(self) { X18: { desc: 'XAir 18 Desk', channels: 18 }, } + self.MIXER_CHOICES = [] + Object.entries(self.MIXER_MODEL).forEach(([key, val]) => { + self.MIXER_CHOICES.push({ + id: key, + label: val.desc, + }) + }) + self.HA_CONFIG = [ { 12: { name: '', has: false }, diff --git a/defSolo.js b/defSolo.js index d0523c3..c19648f 100644 --- a/defSolo.js +++ b/defSolo.js @@ -65,12 +65,34 @@ export const defSolo = [ isFader: true, fadeType: 161, }, + { + actID: 'source', + description: 'Solo Bus Source', + isFader: false, + bg: [100, 75, 50], + }, { actID: 'sourcetrim', description: 'Source Trim', isFader: true, fadeType: 73, }, + { + actID: 'chmode', + description: 'Channel Solo', + isFader: false, + }, + { + actID: 'busmode', + description: 'Bus Solo', + isFader: false, + }, + { + actID: 'dimpfl', + description: 'PFL Attenuation', + isFader: false, + bg: [0, 150, 200], + }, { actID: 'dimatt', description: 'Dim Gain/Att', diff --git a/index.js b/index.js index 099184e..4bb1e96 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,7 @@ import { buildSoloDefs } from './buildSoloDefs.js' import { buildStaticActions } from './actions.js' import { buildSnapshotDefs } from './buildSnapshotDefs.js' import { buildMeterDefs } from './buildMeterDefs.js' +import { buildHADefs } from './buildHADefs.js' import { getConfigFields } from './config.js' import { ICON_SOLO } from './icons.js' import { pad0 } from './helpers.js' @@ -51,7 +52,8 @@ class BAirInstance extends InstanceBase { this.myMixer = { name: '', model: '', - modelNum: 0, + channels: 0, + ip: '', fwVersion: '', } @@ -79,6 +81,7 @@ class BAirInstance extends InstanceBase { buildStaticActions(this) buildSnapshotDefs(this) buildMeterDefs(this) + buildHADefs(this) //buildHeadampDefs(this) this.setActionDefinitions(this.actionDefs) @@ -106,6 +109,8 @@ class BAirInstance extends InstanceBase { for (let m in this.unitsFound) { if (this.unitsFound[m].m_ip == config.host) { config.mixer = m + config.model = this.unitsFound[m].m_model + config.channels = this.unitsFound[m].m_channels } } this.saveConfig(config) @@ -234,13 +239,15 @@ class BAirInstance extends InstanceBase { m_name: args[1].value, m_model: args[2].value, m_fwver: args[3].value, - m_modelNum: parseInt(args[2].value.match(/\d+/)[0]), + m_channels: parseInt(args[2].value.match(/\d+/)[0]), m_last: Date.now(), } this.unitsFound[newUnit.m_name] = newUnit if (!this.config.mixer || this.config.mixer == '') { if (newUnit.m_ip == this.config.host) { this.config.mixer = newUnit.m_name + this.config.model = newUnit.m_model + this.config.channels = newUnit.m_channels this.saveConfig(this.config) } } @@ -574,9 +581,9 @@ class BAirInstance extends InstanceBase { } break - // this.xStat[node].isOn = !!v - // this.checkFeedbacks(this.xStat[node].fbID) - // break + // this.xStat[node].isOn = !!v + // this.checkFeedbacks(this.xStat[node].fbID) + // break case 'fader': case 'level': v = Math.floor(v * 10000) / 10000 @@ -588,6 +595,38 @@ class BAirInstance extends InstanceBase { }) this.xStat[node].idx = this.fLevels[this.xStat[node].fSteps].findIndex((i) => i >= v) break + case 'gain': // headamp + let ha = parseInt(node.split('/')[2]) + this.xStat[node].gain = v + this.setVariableValues({ + [this.xStat[node].varID + '_p']: Math.round(v * 100), + [this.xStat[node].varID + '_d']: this.faderToDB( + v, + this.HA_CONFIG[ha][this.myMixer.channels].trim, + false + ), + }) + break + case 'phantom': + this.xStat[node].pp = !!v + this.setVariableValues({ + [this.xStat[node].varID]: !!v, + }) + break + case 'source': + this.xStat[node].m_source = v + this.setVariableValues({ + [this.xStat[node].varID]: this.MONITOR_SOURCES[v].label, + }) + this.checkFeedbacks(this.xStat[node].fbID) + break + case 'chmode': + case 'busmode': + this.xStat[node].value = !!v ? 'AFL' : 'PFL' + this.setVariableValues({ + [this.xStat[node].varID]: this.xStat[node].value, + }) + break case 'name': // no name, use behringer default v = v == '' ? this.xStat[node].defaultName : v @@ -608,12 +647,15 @@ class BAirInstance extends InstanceBase { this.xStat[node].color = v this.checkFeedbacks(this.xStat[node].fbID) break + case 'dimpfl': case 'mono': case 'dim': case 'mute': // '/config/solo/' - this.xStat[node].isOn = v + this.xStat[node].isOn = !!v this.checkFeedbacks(this.xStat[node].fbID) + this.setVariableValues({ [this.xStat[node].varID]: v ? true : false }) break + default: if (node.match(/\/solo/)) { this.xStat[node].isOn = v @@ -628,13 +670,13 @@ class BAirInstance extends InstanceBase { } else if ('xinfo' == top) { this.myMixer.name = args[1].value this.myMixer.model = args[2].value - this.myMixer.modelNum = parseInt(args[2].value.match(/\d+/)[0]) + this.myMixer.channels = parseInt(args[2].value.match(/\d+/)[0]) this.myMixer.fw = args[3].value this.myMixer.ip = args[0].value this.setVariableValues({ m_name: this.myMixer.name, m_model: this.myMixer.model, - m_modelNum: this.myMixer.modelNum, + m_channels: this.myMixer.channels, m_fw: this.myMixer.fw, m_ip: this.myMixer.ip, }) @@ -782,6 +824,10 @@ class BAirInstance extends InstanceBase { name: 'XAir Mixer IP Address', variableId: 'm_ip', }, + { + name: 'XAir Mixer Channels', + variableId: 'm_channels', + }, { name: 'Current Snapshot Name', variableId: 's_name', @@ -827,13 +873,17 @@ class BAirInstance extends InstanceBase { }, callback: async (feedback, context) => { - const snap = parseInt(await context.parseVariablesInString(feedback.options.theSnap)) - if (snap < 1 || snap > 64) { - const err = [feedback.controlId, feedback.feedbackId, 'Invalid Snapshot #'].join(' → ') - this.updateStatus(InstanceStatus.BadConfig, err) - this.paramError = true - } else { - return snap == this.currentSnapshot + try { + const snap = parseInt(await context.parseVariablesInString(feedback.options.theSnap)) + + if (snap < 1 || snap > 64) { + const err = [feedback.controlId, feedback.feedbackId, 'Invalid Snapshot #'].join(' → ') + this.updateStatus(InstanceStatus.BadConfig, err) + this.paramError = true + } else { + return snap == this.currentSnapshot + } + } finally { } }, }, diff --git a/package.json b/package.json index 81b9d28..e2e181b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "behringer-xair", - "version": "2.3.4", + "version": "2.4.0", "type": "module", "main": "index.js", "scripts": {