diff --git a/client/components/Channel.tsx b/client/components/Channel.tsx index 2ae53d6f..93e995f3 100644 --- a/client/components/Channel.tsx +++ b/client/components/Channel.tsx @@ -134,7 +134,7 @@ class Channel extends React.Component< } handleVuMeter() { - if (window.mixerProtocol.protocol === 'CasparCG') { + if (window.mixerProtocol.protocol === 'CasparCG' || window.mixerProtocol.protocol === 'VMIX') { return ( {!window.location.search.includes('vu=0') && diff --git a/server/constants/mixerProtocols/vMix.ts b/server/constants/mixerProtocols/vMix.ts index 2d67741a..9eca9c37 100644 --- a/server/constants/mixerProtocols/vMix.ts +++ b/server/constants/mixerProtocols/vMix.ts @@ -13,102 +13,105 @@ export const VMix: IMixerProtocol = { FADE_DISPATCH_RESOLUTION: 5, leadingZeros: true, pingCommand: [ - { - mixerMessage: '/xremote', - }, - { - mixerMessage: '/meters', - value: '/meters/1', - type: 's', - }, + // { + // mixerMessage: '/xremote', + // }, + // { + // mixerMessage: '/meters', + // value: '/meters/1', + // type: 's', + // }, ], pingTime: 9500, initializeCommands: [ { - mixerMessage: '/ch/{channel}/mix/fader', - }, - { - mixerMessage: '/ch/{channel}/config/name', - }, - { - mixerMessage: '/ch/{channel}/mix/{argument}/level', - type: 'aux', - }, - { - mixerMessage: '/ch/{channel}/preamp/trim', - }, - { - mixerMessage: '/ch/{channel}/dyn/thr', - }, - { - mixerMessage: '/ch/{channel}/dyn/ratio', - }, - { - mixerMessage: '/ch/{channel}/delay/time', - }, - { - mixerMessage: '/ch/{channel}/eq/1/g', - }, - { - mixerMessage: '/ch/{channel}/eq/2/g', - }, - { - mixerMessage: '/ch/{channel}/eq/3/g', - }, - { - mixerMessage: '/ch/{channel}/eq/4/g', - }, - { - mixerMessage: '/ch/{channel}/dyn/thr', - }, - { - mixerMessage: '/ch/{channel}/dyn/ratio', - }, - { - mixerMessage: '/ch/{channel}/dyn/attack', - }, - { - mixerMessage: '/ch/{channel}/dyn/hold', - }, - { - mixerMessage: '/ch/{channel}/dyn/knee', - }, - { - mixerMessage: '/ch/{channel}/dyn/mgain', - }, - { - mixerMessage: '/ch/{channel}/dyn/ratio', - }, - { - mixerMessage: '/ch/{channel}/delay/time', - }, - { - mixerMessage: '/ch/{channel}/eq/1/g', - }, - { - mixerMessage: '/ch/{channel}/eq/1/f', - }, - { - mixerMessage: '/ch/{channel}/eq/2/f', - }, - { - mixerMessage: '/ch/{channel}/eq/3/f', - }, - { - mixerMessage: '/ch/{channel}/eq/4/f', - }, - { - mixerMessage: '/ch/{channel}/eq/1/q', - }, - { - mixerMessage: '/ch/{channel}/eq/2/q', - }, - { - mixerMessage: '/ch/{channel}/eq/3/q', - }, - { - mixerMessage: '/ch/{channel}/eq/4/q', - }, + mixerMessage: 'SetVolume', + }, + { + mixerMessage: 'AudioAutoOff', + }, + // { + // mixerMessage: '/ch/{channel}/config/name', + // }, + // { + // mixerMessage: '/ch/{channel}/mix/{argument}/level', + // type: 'aux', + // }, + // { + // mixerMessage: '/ch/{channel}/preamp/trim', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/thr', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/ratio', + // }, + // { + // mixerMessage: '/ch/{channel}/delay/time', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/1/g', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/2/g', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/3/g', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/4/g', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/thr', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/ratio', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/attack', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/hold', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/knee', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/mgain', + // }, + // { + // mixerMessage: '/ch/{channel}/dyn/ratio', + // }, + // { + // mixerMessage: '/ch/{channel}/delay/time', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/1/g', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/1/f', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/2/f', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/3/f', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/4/f', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/1/q', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/2/q', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/3/q', + // }, + // { + // mixerMessage: '/ch/{channel}/eq/4/q', + // }, ], channelTypes: [ { @@ -117,203 +120,26 @@ export const VMix: IMixerProtocol = { fromMixer: { CHANNEL_OUT_GAIN: [ { - mixerMessage: '/ch/{channel}/mix/fader', + mixerMessage: 'SetVolume', }, ], CHANNEL_VU: [ { mixerMessage: '/meters/1', }, - ], - [fxParamsList.GainTrim]: [ - { - mixerMessage: '/ch/{channel}/preamp/trim', - minLabel: -18, - maxLabel: 18, - label: 'Gain Trim', - valueLabel: ' dB', - }, - ], - [fxParamsList.CompThrs]: [ - { - mixerMessage: '/ch/{channel}/dyn/thr', - minLabel: -60, - maxLabel: 0, - label: 'Threshold', - valueLabel: ' dB', - }, - ], - [fxParamsList.CompRatio]: [ - { - mixerMessage: '/ch/{channel}/dyn/ratio', - minLabel: 1, - maxLabel: 10, - label: 'Ratio', - valueLabel: ' :1', - }, - ], - [fxParamsList.CompAttack]: [ - { - mixerMessage: '/ch/{channel}/dyn/attack', - minLabel: 0, - maxLabel: 120, - label: 'Attack', - valueLabel: ' ms', - }, - ], - [fxParamsList.CompHold]: [ { - mixerMessage: '/ch/{channel}/dyn/hold', - minLabel: 0, - maxLabel: 2000, - label: 'Hold', - valueLabel: ' ms', + mixerMessage: '/meters/2', }, ], - [fxParamsList.CompKnee]: [ + CHANNEL_INPUT_GAIN: [ { - mixerMessage: '/ch/{channel}/dyn/knee', - minLabel: 0, - maxLabel: 5, - label: 'Knee', - valueLabel: ' ', - }, - ], - [fxParamsList.CompMakeUp]: [ - { - mixerMessage: '/ch/{channel}/dyn/mgain', + mixerMessage: 'SetGain', minLabel: 0, maxLabel: 24, - label: 'MakeUp', - valueLabel: ' dB', - }, - ], - [fxParamsList.CompRelease]: [ - { - mixerMessage: '/ch/{channel}/dyn/release', - minLabel: 5, - maxLabel: 4000, - label: 'Release', - valueLabel: ' ms', - }, - ], - [fxParamsList.DelayTime]: [ - { - mixerMessage: '/ch/{channel}/delay/time', - minLabel: 0, - maxLabel: 500, - label: 'Time', - valueLabel: ' ms', - }, - ], - [fxParamsList.EqGain01]: [ - { - mixerMessage: '/ch/{channel}/eq/1/g', - minLabel: -15, - maxLabel: 15, - label: 'Low', - valueLabel: ' dB', - }, - ], - [fxParamsList.EqGain02]: [ - { - mixerMessage: '/ch/{channel}/eq/2/g', - minLabel: -15, - maxLabel: 15, - label: 'LoMid', - valueLabel: ' dB', - }, - ], - [fxParamsList.EqGain03]: [ - { - mixerMessage: '/ch/{channel}/eq/3/g', - minLabel: -15, - maxLabel: 15, - label: 'HiMid', - valueLabel: ' dB', - }, - ], - [fxParamsList.EqGain04]: [ - { - mixerMessage: '/ch/{channel}/eq/4/g', - minLabel: -15, - maxLabel: 15, - label: 'High', + label: 'Gain Trim', valueLabel: ' dB', }, ], - [fxParamsList.EqFreq01]: [ - { - mixerMessage: '/ch/{channel}/eq/1/f', - minLabel: 20, - maxLabel: 20000, - label: 'Low Freq', - valueLabel: ' Freq', - }, - ], - [fxParamsList.EqFreq02]: [ - { - mixerMessage: '/ch/{channel}/eq/2/f', - minLabel: 20, - maxLabel: 20000, - label: 'LoMid freq', - valueLabel: ' Freq', - }, - ], - [fxParamsList.EqFreq03]: [ - { - mixerMessage: '/ch/{channel}/eq/3/f', - minLabel: 20, - maxLabel: 20000, - label: 'HiMid freq', - valueLabel: ' Freq', - }, - ], - [fxParamsList.EqFreq04]: [ - { - mixerMessage: '/ch/{channel}/eq/4/f', - minLabel: 20, - maxLabel: 20000, - label: 'High freq', - valueLabel: ' Freq', - }, - ], - [fxParamsList.EqQ01]: [ - { - mixerMessage: '/ch/{channel}/eq/1/q', - minLabel: 10, - maxLabel: 0.3, - label: 'Low Q', - valueLabel: ' Q', - }, - ], - [fxParamsList.EqQ02]: [ - { - mixerMessage: '/ch/{channel}/eq/2/q', - minLabel: 10, - maxLabel: 0.3, - label: 'LoMid Q', - valueLabel: ' Q', - }, - ], - [fxParamsList.EqQ03]: [ - { - mixerMessage: '/ch/{channel}/eq/3/q', - minLabel: 10, - maxLabel: 0.3, - label: 'HiMid Q', - valueLabel: ' Q', - }, - ], - [fxParamsList.EqQ04]: [ - { - mixerMessage: '/ch/{channel}/eq/4/q', - minLabel: 10, - maxLabel: 0.3, - label: 'High Q', - valueLabel: ' Q', - }, - ], AUX_LEVEL: [ { mixerMessage: '/ch/{channel}/mix/{argument}/level', @@ -328,116 +154,63 @@ export const VMix: IMixerProtocol = { toMixer: { CHANNEL_OUT_GAIN: [ { - mixerMessage: '/ch/{channel}/mix/fader', - }, - ], - [fxParamsList.GainTrim]: [ - { - mixerMessage: '/ch/{channel}/preamp/trim', - minLabel: -18, - maxLabel: 18, - label: 'Gain Trim', - valueLabel: ' dB', - }, - ], - [fxParamsList.CompThrs]: [ - { - mixerMessage: '/ch/{channel}/dyn/thr', - }, - ], - [fxParamsList.CompRatio]: [ - { - mixerMessage: '/ch/{channel}/dyn/ratio', - }, - ], - [fxParamsList.CompAttack]: [ - { - mixerMessage: '/ch/{channel}/dyn/attack', - }, - ], - [fxParamsList.CompHold]: [ - { - mixerMessage: '/ch/{channel}/dyn/hold', - }, - ], - [fxParamsList.CompKnee]: [ - { - mixerMessage: '/ch/{channel}/dyn/knee', - }, - ], - [fxParamsList.CompMakeUp]: [ - { - mixerMessage: '/ch/{channel}/dyn/mgain', + mixerMessage: 'SetVolume', }, ], - [fxParamsList.CompRelease]: [ + CHANNEL_INPUT_SELECTOR: [ { - mixerMessage: '/ch/{channel}/dyn/release', + mixerMessage: 'AudioChannelMatrixApplyPreset', + label: 'LR', + value: 'Default', }, - ], - [fxParamsList.DelayTime]: [ { - mixerMessage: '/ch/{channel}/delay/time', + mixerMessage: 'AudioChannelMatrixApplyPreset', + label: 'LL', + value: 'LL', }, - ], - [fxParamsList.EqGain01]: [ - { - mixerMessage: '/ch/{channel}/eq/1/g', - }, - ], - [fxParamsList.EqGain02]: [ - { - mixerMessage: '/ch/{channel}/eq/2/g', - }, - ], - [fxParamsList.EqGain03]: [ - { - mixerMessage: '/ch/{channel}/eq/3/g', - }, - ], - [fxParamsList.EqGain04]: [ { - mixerMessage: '/ch/{channel}/eq/4/g', + mixerMessage: 'AudioChannelMatrixApplyPreset', + label: 'RR', + value: 'RR', }, ], - [fxParamsList.EqFreq01]: [ + CHANNEL_INPUT_GAIN: [ { - mixerMessage: '/ch/{channel}/eq/1/f', - }, - ], - [fxParamsList.EqFreq02]: [ - { - mixerMessage: '/ch/{channel}/eq/2/f', - }, - ], - [fxParamsList.EqFreq03]: [ - { - mixerMessage: '/ch/{channel}/eq/3/f', - }, - ], - [fxParamsList.EqFreq04]: [ - { - mixerMessage: '/ch/{channel}/eq/4/f', + mixerMessage: 'SetGain', + minLabel: 0, + maxLabel: 24, + label: 'Gain Trim', + valueLabel: ' dB', + min: 0, + max: 24, }, ], - [fxParamsList.EqQ01]: [ + CHANNEL_MUTE_ON: [ { - mixerMessage: '/ch/{channel}/eq/1/q', + mixerMessage: 'AudioOff', + value: 0, + type: 'f', }, ], - [fxParamsList.EqQ02]: [ + CHANNEL_MUTE_OFF: [ { - mixerMessage: '/ch/{channel}/eq/2/q', + mixerMessage: 'AudioOn', + value: 1, + type: 'f', }, ], - [fxParamsList.EqQ03]: [ + PFL_OFF: [ { - mixerMessage: '/ch/{channel}/eq/3/q', + mixerMessage: 'SoloOff', + value: 1, + type: 'f', }, ], - [fxParamsList.EqQ04]: [ + PFL_ON: [ { - mixerMessage: '/ch/{channel}/eq/4/q', + mixerMessage: 'SoloOn', + value: 1, + type: 'f', }, ], AUX_LEVEL: [ @@ -445,20 +218,6 @@ export const VMix: IMixerProtocol = { mixerMessage: '/ch/{channel}/mix/{argument}/level', }, ], - CHANNEL_MUTE_ON: [ - { - mixerMessage: '/ch/{channel}/mix/on', - value: 0, - type: 'f', - }, - ], - CHANNEL_MUTE_OFF: [ - { - mixerMessage: '/ch/{channel}/mix/on', - value: 1, - type: 'f', - }, - ], }, }, ], diff --git a/server/utils/mixerConnections/VMixMixerConnection.ts b/server/utils/mixerConnections/VMixMixerConnection.ts index 28c68e25..0fb4f0b6 100644 --- a/server/utils/mixerConnections/VMixMixerConnection.ts +++ b/server/utils/mixerConnections/VMixMixerConnection.ts @@ -10,6 +10,7 @@ import { IMixerProtocol, } from '../../constants/MixerProtocolInterface' import { + SET_OUTPUT_LEVEL, storeSetAuxLevel, storeSetOutputLevel, } from '../../reducers/channelActions' @@ -19,16 +20,22 @@ import { storeFaderFx, storeTogglePgm, storeSetMute, + storeInputGain, + storeSetPfl, + storeSetPgm, + storeSetVo, } from '../../reducers/faderActions' import { storeSetMixerOnline } from '../../reducers/settingsActions' import { logger } from '../logger' import { sendVuLevel, VuType } from '../vuServer' +import { dbToFloat } from './LawoRubyConnection' export class VMixMixerConnection { mixerProtocol: IMixerProtocol mixerIndex: number cmdChannelIndex: number vmixConnection: any + vmixVuConnection: ConnectionTCP mixerOnlineTimer: any constructor(mixerProtocol: IMixerProtocol, mixerIndex: number) { @@ -58,6 +65,14 @@ export class VMixMixerConnection { ), } ) + this.vmixVuConnection = new ConnectionTCP( + state.settings[0].mixers[this.mixerIndex].deviceIp, + { + port: parseInt( + state.settings[0].mixers[this.mixerIndex].devicePort + '' + ), + } + ) this.setupMixerConnection() } @@ -77,226 +92,11 @@ export class VMixMixerConnection { }) .on('data', (data: any) => { const message = data.toString() - console.log(XmlApi.DataParser.parse(message)) + // console.log(XmlApi.DataParser.parse(message)) clearTimeout(this.mixerOnlineTimer) if (!state.settings[0].mixers[this.mixerIndex].mixerOnline) { this.mixerOnline(true) } - logger.verbose('Received Vmix message: ' + message, {}) - console.log('Received Vmix message: ', message) - - if ( - this.checkVMixCommand( - message.address, - this.mixerProtocol.channelTypes[0].fromMixer - .CHANNEL_VU?.[0].mixerMessage - ) - ) { - let ch = message.address.split('/')[this.cmdChannelIndex] - sendVuLevel( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader, - VuType.Channel, - 0, - message.args[0] - ) - } else if ( - this.checkVMixCommand( - message.address, - this.mixerProtocol.channelTypes[0].fromMixer - .CHANNEL_VU_REDUCTION?.[0].mixerMessage - ) - ) { - let ch = message.address.split('/')[this.cmdChannelIndex] - sendVuLevel( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader, - VuType.Reduction, - 0, - message.args[0] - ) - } else if ( - this.checkVMixCommand( - message.address, - this.mixerProtocol.channelTypes[0].fromMixer - .CHANNEL_OUT_GAIN?.[0].mixerMessage - ) - ) { - let ch = message.address.split('/')[this.cmdChannelIndex] - let assignedFaderIndex = - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader - - if ( - assignedFaderIndex >= 0 && - !state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].fadeActive - ) { - if ( - message.args[0] > this.mixerProtocol.fader.min || - message.args[0] > - state.settings[0].autoResetLevel / 100 - ) { - store.dispatch( - storeFaderLevel( - assignedFaderIndex, - message.args[0] - ) - ) - state.channels[0].chMixerConnection[ - this.mixerIndex - ].channel.forEach((item, index) => { - if (item.assignedFader === assignedFaderIndex) { - store.dispatch( - storeSetOutputLevel( - this.mixerIndex, - index, - message.args[0] - ) - ) - } - }) - if ( - !state.faders[0].fader[assignedFaderIndex].pgmOn - ) { - if ( - message.args[0] > - this.mixerProtocol.fader.min || - 0 - ) { - store.dispatch( - storeTogglePgm(assignedFaderIndex) - ) - } - } - } else if ( - state.faders[0].fader[assignedFaderIndex].pgmOn || - state.faders[0].fader[assignedFaderIndex].voOn - ) { - store.dispatch( - storeFaderLevel( - assignedFaderIndex, - message.args[0] - ) - ) - state.channels[0].chMixerConnection[ - this.mixerIndex - ].channel.forEach((item, index) => { - if (item.assignedFader === assignedFaderIndex) { - store.dispatch( - storeSetOutputLevel( - this.mixerIndex, - index, - message.args[0] - ) - ) - } - }) - } - global.mainThreadHandler.updatePartialStore( - assignedFaderIndex - ) - - if (remoteConnections) { - remoteConnections.updateRemoteFaderState( - assignedFaderIndex, - message.args[0] - ) - } - } - } else if ( - this.checkVMixCommand( - message.address, - this.mixerProtocol.channelTypes?.[0].fromMixer - .AUX_LEVEL?.[0].mixerMessage - ) - ) { - let commandArray: string[] = this.mixerProtocol.channelTypes[0].fromMixer.AUX_LEVEL[0].mixerMessage.split( - '/' - ) - let messageArray: string[] = message.address.split('/') - let ch = 0 - let auxIndex = 0 - - commandArray.forEach( - (commandPart: string, index: number) => { - if (commandPart === '{channel}') { - ch = parseFloat(messageArray[index]) - } else if (commandPart === '{argument}') { - auxIndex = parseFloat(messageArray[index]) - 1 - } - } - ) - if ( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].auxLevel[auxIndex] > -1 - ) { - logger.verbose( - 'Aux Message Channel : ' + - ch + - ' Aux Index :' + - auxIndex + - ' Level : ' + - message.args[0] - ) - store.dispatch( - storeSetAuxLevel( - this.mixerIndex, - ch - 1, - auxIndex, - message.args[0] - ) - ) - global.mainThreadHandler.updateFullClientStore() - if (remoteConnections) { - remoteConnections.updateRemoteAuxPanels() - } - } - } else if ( - this.checkVMixCommand( - message.address, - this.mixerProtocol.channelTypes[0].fromMixer - .CHANNEL_NAME?.[0].mixerMessage - ) - ) { - let ch = message.address.split('/')[this.cmdChannelIndex] - store.dispatch( - storeFaderLabel( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader, - message.args[0] - ) - ) - global.mainThreadHandler.updatePartialStore( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader - ) - } else if ( - this.checkVMixCommand( - message.address, - this.mixerProtocol.channelTypes[0].fromMixer - .CHANNEL_MUTE_ON?.[0].mixerMessage - ) - ) { - let ch = message.address.split('/')[this.cmdChannelIndex] - store.dispatch( - storeSetMute( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader, - message.args[0] === 0 - ) - ) - global.mainThreadHandler.updatePartialStore( - state.channels[0].chMixerConnection[this.mixerIndex] - .channel[ch - 1].assignedFader - ) - } else { - this.checkFxCommands(message) - logger.verbose( - 'Unknown OSC message: ' + message.address, - {} - ) - } }) .on('error', (error: any) => { global.mainThreadHandler.updateFullClientStore() @@ -319,6 +119,139 @@ export class VMixMixerConnection { this.pingMixerCommand() }, this.mixerProtocol.pingTime) } + + // use separate connection for updates to prevent blocking any commands + this.vmixVuConnection.on('xml', (xml: string) => { + const doc = XmlApi.DataParser.parse(xml) + const inputs = XmlApi.Inputs.extractInputsFromXML(doc) + + const mappedInputs = inputs.map((input) => { + const d: Record = { + name: input.childNodes[0].nodeValue, + } + + const attrs = [ + 'volume', + 'muted', + 'meterF1', + 'meterF2', + 'number', + 'gainDb', + 'solo', + ] + Object.values(input.attributes) + .filter((attr: Attr) => attrs.includes(attr.name)) + .forEach((attr: Attr) => { + d[attr.name] = attr.value + }) + + d.volume = Math.pow(parseFloat(d.volume || '0') / 100, 0.25) + d.meterF1 = (9.555 * Math.log(d.meterF1)) / Math.log(3) + d.meterF2 = (9.555 * Math.log(d.meterF2)) / Math.log(3) + d.muted = d.muted === 'True' + d.solo = d.solo === 'True' + d.gainDb = parseFloat(d.gainDb || '0') / 24 + + return d + }) + + mappedInputs.forEach((input) => { + if ('number' in input) { + sendVuLevel( + input.number - 1, + VuType.Channel, + 0, + dbToFloat(input.meterF1 + 12) + ) // add +15 to convert from dBFS + sendVuLevel( + input.number - 1, + VuType.Channel, + 1, + dbToFloat(input.meterF2 + 12) + ) // add +15 to convert from dBFS + } + + const { + outputLevel, + fadeActive, + assignedFader, + } = state.channels[0].chMixerConnection[ + this.mixerIndex + ].channel[input.number - 1] + const { + inputGain, + muteOn, + pflOn, + pgmOn, + voOn, + } = state.faders[0].fader[assignedFader] + let sendUpdate = false + const dispatch = (update: any) => { + store.dispatch(update) + sendUpdate = true + } + + if ('muted' in input) { + if (input.muted === false) { + if ( + !fadeActive && + outputLevel > 0 && + Math.abs(outputLevel - input.volume) > 0.01 + ) { + dispatch( + storeFaderLevel(assignedFader, input.volume) + ) + store.dispatch({ + type: SET_OUTPUT_LEVEL, + channel: assignedFader, + mixerIndex: this.mixerIndex, + level: voOn + ? input.volume / + (state.settings[0].voLevel / 100) + : input.volume, + }) + } + if (muteOn) { + dispatch(storeSetMute(assignedFader, false)) + } + if (!pgmOn && !voOn) { + dispatch(storeSetPgm(assignedFader, true)) + store.dispatch({ + type: SET_OUTPUT_LEVEL, + channel: assignedFader, + mixerIndex: this.mixerIndex, + level: input.volume, + }) + } + } else if (!muteOn) { + if (pgmOn) { + dispatch(storeSetPgm(assignedFader, false)) + } + if (voOn) { + dispatch(storeSetVo(assignedFader, false)) + } + } + + if (inputGain !== input.gainDb) { + dispatch(storeInputGain(assignedFader, input.gainDb)) + } + if (pflOn !== input.solo) { + dispatch(storeSetPfl(assignedFader, input.solo)) + } + } + + if (sendUpdate) { + global.mainThreadHandler.updatePartialStore( + input.number - 1 + ) + } + }) + }) + this.vmixVuConnection.on('connect', () => { + setInterval(() => { + this.vmixVuConnection.send('XML') + }, 80) + }) } initialCommands() { @@ -398,6 +331,9 @@ export class VMixMixerConnection { let fxKey = keyName as keyof typeof fxParamsList if ( + this.mixerProtocol.channelTypes[0].fromMixer[ + fxParamsList[fxKey] + ] && this.checkVMixCommand( message.address, this.mixerProtocol.channelTypes[0].fromMixer[ @@ -456,33 +392,12 @@ export class VMixMixerConnection { value: string | number, type: string ) { - if (typeof value === 'number') { - this.vmixConnection.send({ - Function: `SetVolume&Input=1&Value=${value * 100}`, - }) - } else { - this.vmixConnection.send({ - Function: `SetVolume&Input=1&Value=${parseFloat(value) * 100}`, - }) - } - this.vmixConnection.send({ Function: `SetBalance&Input=1&Value=-1` }) - - /*let channelString = this.mixerProtocol.leadingZeros - ? ('0' + channel).slice(-2) - : channel.toString() - let message = vMixMessage.replace('{channel}', channelString) - if (message != 'none') { - logger.verbose('Sending OSC command :' + message, {}) - this.vmixConnection.send({ - address: message, - args: [ - { - type: type, - value: value, - }, - ], - }) - }*/ + logger.verbose(`send${vMixMessage} Input=1&Value=${value}`) + this.vmixConnection.send({ + Function: vMixMessage, + Input: channel, // todo - should we map these? + Value: value, + }) } sendOutRequest(oscMessage: string, channel: number) { @@ -523,27 +438,34 @@ export class VMixMixerConnection { state.channels[0].chMixerConnection[this.mixerIndex].channel[ channelIndex ].channelTypeIndex + const level = Math.round( + state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ].outputLevel * 100 + ) this.sendOutMessage( this.mixerProtocol.channelTypes[channelType].toMixer .CHANNEL_OUT_GAIN[0].mixerMessage, channelTypeIndex + 1, - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].outputLevel, + level, 'f' ) } updatePflState(channelIndex: number) { - let channelType = - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].channelType - let channelTypeIndex = - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].channelTypeIndex + let { + channelType, + channelTypeIndex, + outputLevel, + } = state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ] + if (state.faders[0].fader[channelIndex].pflOn === true) { + if (outputLevel === 0) { + // this.sendOutMessage('AudioOff', channelTypeIndex + 1, 1, '') + // this.sendOutMessage('SetVolume', channelTypeIndex + 1, 75, '') + } this.sendOutMessage( this.mixerProtocol.channelTypes[channelType].toMixer.PFL_ON[0] .mixerMessage, @@ -554,6 +476,10 @@ export class VMixMixerConnection { .type ) } else { + if (outputLevel === 0) { + // this.sendOutMessage('SetVolume', channelTypeIndex + 1, 0, '') + // this.sendOutMessage('AudioOn', channelTypeIndex + 1, 1, '') + } this.sendOutMessage( this.mixerProtocol.channelTypes[channelType].toMixer.PFL_OFF[0] .mixerMessage, @@ -567,15 +493,15 @@ export class VMixMixerConnection { } updateMuteState(channelIndex: number, muteOn: boolean) { - let channelType = - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].channelType - let channelTypeIndex = - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].channelTypeIndex - if (muteOn === true) { + const { + channelType, + channelTypeIndex, + outputLevel, + } = state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ] + + if (muteOn === true && outputLevel > 0) { let mute = this.mixerProtocol.channelTypes[channelType].toMixer .CHANNEL_MUTE_ON[0] this.sendOutMessage( @@ -584,7 +510,7 @@ export class VMixMixerConnection { mute.value, mute.type ) - } else { + } else if (muteOn === false && outputLevel > 0) { let mute = this.mixerProtocol.channelTypes[channelType].toMixer .CHANNEL_MUTE_OFF[0] this.sendOutMessage( @@ -605,10 +531,39 @@ export class VMixMixerConnection { } updateInputGain(channelIndex: number, level: number) { - return true + let channelType = + state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ].channelType + let channelTypeIndex = + state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ].channelTypeIndex + let mixerMessage = this.mixerProtocol.channelTypes[channelType].toMixer + .CHANNEL_INPUT_GAIN[0] + if (mixerMessage.min !== undefined && mixerMessage.max !== undefined) { + level = + mixerMessage.min + (mixerMessage.max - mixerMessage.min) * level + } + this.sendOutMessage( + mixerMessage.mixerMessage, + channelTypeIndex + 1, + Math.round(level), + 'f' + ) } updateInputSelector(channelIndex: number, inputSelected: number) { - return true + const { + channelType, + channelTypeIndex, + } = state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ] + let { mixerMessage, value } = this.mixerProtocol.channelTypes[ + channelType + ].toMixer.CHANNEL_INPUT_SELECTOR[inputSelected - 1] + + this.sendOutMessage(mixerMessage, channelTypeIndex + 1, value, '') } updateFx(fxParam: fxParamsList, channelIndex: number, level: number) { @@ -623,7 +578,15 @@ export class VMixMixerConnection { let fx = this.mixerProtocol.channelTypes[channelType].toMixer[ fxParam ][0] - this.sendOutMessage(fx.mixerMessage, channelTypeIndex + 1, level, 'f') + if (fx.min !== undefined && fx.max !== undefined) { + level = fx.min + (fx.max - fx.min) * level + } + this.sendOutMessage( + fx.mixerMessage, + channelTypeIndex + 1, + Math.round(level), + 'f' + ) // todo - is it always rounded? } updateAuxLevel(channelIndex: number, auxSendIndex: number, level: number) { @@ -649,14 +612,19 @@ export class VMixMixerConnection { } updateFadeIOLevel(channelIndex: number, outputLevel: number) { - let channelType = - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].channelType - let channelTypeIndex = - state.channels[0].chMixerConnection[this.mixerIndex].channel[ - channelIndex - ].channelTypeIndex + let { + channelType, + channelTypeIndex, + } = state.channels[0].chMixerConnection[this.mixerIndex].channel[ + channelIndex + ] + let { muteOn } = state.faders[0].fader[channelIndex] + outputLevel = Math.round(100 * outputLevel) + + if (!muteOn && outputLevel > 0) { + this.sendOutMessage('AudioOn', channelTypeIndex + 1, 1, '') + } + this.sendOutMessage( this.mixerProtocol.channelTypes[channelType].toMixer .CHANNEL_OUT_GAIN[0].mixerMessage, @@ -664,6 +632,11 @@ export class VMixMixerConnection { String(outputLevel), 'f' ) + + if (outputLevel <= 1) { + this.sendOutMessage('AudioOff', channelTypeIndex + 1, 1, '') + this.sendOutMessage('SetVolume', channelTypeIndex + 1, 75, '') + } } updateChannelName(channelIndex: number) {