UNPKG

timeline-state-resolver

Version:
395 lines • 14.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SisyfosCommandType = exports.SisyfosApi = void 0; const osc = require("osc"); const eventemitter3_1 = require("eventemitter3"); /** How often to check connection status */ const CONNECTIVITY_INTERVAL = 3000; // ms const CONNECTIVITY_TIMEOUT = 1000; // ms class SisyfosApi extends eventemitter3_1.EventEmitter { constructor() { super(...arguments); this._labelToChannel = new Map(); this._pingCounter = Math.round(Math.random() * 10000); this._connectivityTimeout = null; this._connected = false; this._mixerOnline = true; } /** * Connnects to the OSC server. * @param host ip to connect to * @param port port the osc server is hosted on */ async connect(host, port) { const client = (this._oscClient = new osc.UDPPort({ localAddress: '0.0.0.0', localPort: 0, remoteAddress: host, remotePort: port, metadata: true, })); this._oscClient.on('error', (error) => this.emit('error', error)); this._oscClient.on('message', (received) => this.receiver(received)); return new Promise((resolve) => { client.once('ready', () => { // Monitor connectivity: this._monitorConnectivity(); // Request initial, full state: client.send({ address: '/state/full', args: [] }); }); client.open(); if (this.isInitialized()) { resolve(); } else { // Wait for the state to be received from sisyfos this.once('initialized', () => { resolve(); }); } }); } dispose() { this.updateIsConnected(false); if (this._connectivityCheckInterval) { clearInterval(this._connectivityCheckInterval); } if (this._oscClient) this._oscClient.close(); } send(command) { if (!this._oscClient) { throw new Error(`OSC client not initialised`); } if (command.type === SisyfosCommandType.TAKE) { this._oscClient.send({ address: '/take', args: [] }); } else if (command.type === SisyfosCommandType.CLEAR_PST_ROW) { this._oscClient.send({ address: '/clearpst', args: [] }); } else if (command.type === SisyfosCommandType.RESYNC_CHANNEL) { this._oscClient.send({ address: `/ch/${command.channel + 1}/state`, args: [ { type: 'i', value: command.value, }, ], }); } else if (command.type === SisyfosCommandType.LABEL) { this._oscClient.send({ address: `/ch/${command.channel + 1}/label`, args: [ { type: 's', value: command.value, }, ], }); } else if (command.type === SisyfosCommandType.TOGGLE_PGM) { const args = command.values.map((value) => { return { type: 'i', value, }; }); this._oscClient.send({ address: `/ch/${command.channel + 1}/pgm`, args, }); } else if (command.type === SisyfosCommandType.TOGGLE_PST) { this._oscClient.send({ address: `/ch/${command.channel + 1}/pst`, args: [ { type: 'i', value: command.value, }, ], }); } else if (command.type === SisyfosCommandType.SET_FADER) { const args = command.values.map((value) => { return { type: 'f', value, }; }); this._oscClient.send({ address: `/ch/${command.channel + 1}/faderlevel`, args, }); } else if (command.type === SisyfosCommandType.VISIBLE) { this._oscClient.send({ address: `/ch/${command.channel + 1}/visible`, args: [ { type: 'i', value: command.value === true ? 1 : 0, }, ], }); } else if (command.type === SisyfosCommandType.SET_MUTE) { this._oscClient.send({ address: `/ch/${command.channel + 1}/mute`, args: [ { type: 'i', value: command.value === true ? 1 : 0, }, ], }); } else if (command.type === SisyfosCommandType.SET_INPUT_GAIN) { this._oscClient.send({ address: `/ch/${command.channel + 1}/inputgain`, args: [ { type: 'f', value: command.value, }, ], }); } else if (command.type === SisyfosCommandType.SET_INPUT_SELECTOR) { this._oscClient.send({ address: `/ch/${command.channel + 1}/inputselector`, args: [ { type: 'i', value: command.value, }, ], }); } else if (command.type === SisyfosCommandType.SET_CHANNEL) { // Resync all properties for a channel const channelState = { ...command.values, }; this.setSisyfosChannel(command.channel + 1, channelState); } else if (command.type === SisyfosCommandType.LOAD_MIXER_PRESET) { this.sendLoadMixerPresetCommand(command.presetName); } } disconnect() { if (this._oscClient) this._oscClient.close(); } isInitialized() { return !!this._state; } reInitialize() { this._state = undefined; if (!this._oscClient) { throw new Error(`OSC client not initialised`); } this._oscClient.send({ address: '/state/full', args: [] }); } reSyncOneChannel(channel) { if (!this._oscClient) { throw new Error(`Can't resync channel, OSC client not initialised`); } // This will trigger Sisyfos to emit its state of that channel, to be picked up in this.receiver() this._oscClient.send({ address: `/ch/${channel}/state`, args: [] }); } setSisyfosChannel(channel, apiState) { if (!this._oscClient) { throw new Error(`Can't set channel, OSC client not initialised`); } const oscApiState = { pgmOn: apiState.pgmOn === 1, voOn: apiState.pgmOn === 2, pstOn: apiState.pstOn === 1, label: apiState.label ?? '', faderLevel: apiState.faderLevel ?? 0.75, muteOn: apiState.muteOn ?? false, inputGain: apiState.inputGain ?? 0.75, inputSelector: apiState.inputSelector ?? 1, fadeTime: apiState.fadeTime, showChannel: apiState.visible, }; this._oscClient.send({ address: `/setchannel/${channel}`, args: { type: 's', value: JSON.stringify(oscApiState) } }); } getChannelByLabel(label) { return this._labelToChannel.get(label); } get connected() { return this._connected; } get state() { return this._state; } get mixerOnline() { return this._mixerOnline; } setMixerOnline(state) { this._mixerOnline = state; } _monitorConnectivity() { const pingSisyfos = () => { if (this._oscClient) this._oscClient.send({ address: `/ping/${this._pingCounter}`, args: [] }); const waitingForPingCounter = this._pingCounter; // Expect a reply within a certain time: this._clearPingTimer(); this._connectivityTimeout = setTimeout(() => { if (waitingForPingCounter === this._pingCounter) { // this._pingCounter hasn't changed, ie no response has been received this.updateIsConnected(false); } }, CONNECTIVITY_TIMEOUT); }; // Ping Sisyfos and expect a reply back: pingSisyfos(); this._connectivityCheckInterval = setInterval(() => { pingSisyfos(); }, CONNECTIVITY_INTERVAL); } _clearPingTimer() { if (this._connectivityTimeout) { clearTimeout(this._connectivityTimeout); this._connectivityTimeout = null; } } receiver(message) { const address = message.address.substr(1).split('/'); if (address[0] === 'state' && address[1] === 'full') { this._state = this.parseSisyfosState(message); this._labelToChannel = new Map(Object.entries(this._state.channels).map((v) => [v[1].label, Number(v[0])])); this.emit('initialized'); } else if (address[0] === 'ch' && this._state) { // This receives updates for a single channel // But is not used in TSR as of now // If once neeeded a new event should be implemented: // like: this.emit('channel-state-changed') const ch = Number(address[1]) - 1; this._state.channels[ch] = { ...this._state.channels[ch], ...this.parseChannelCommand(message, address.slice(2)), }; } else if (address[0] === 'pong') { // a reply to "/ping" const pingValue = parseInt(message.args[0].value, 10); if (pingValue && this._pingCounter === pingValue) { this._clearPingTimer(); this.updateIsConnected(true); this._pingCounter++; this.emit('mixerOnline', true); } else if (message.args[0].value === 'offline') { this._clearPingTimer(); this.updateIsConnected(true); this._pingCounter++; this.emit('mixerOnline', false); } } } updateIsConnected(connected) { if (this._connected !== connected) { this._connected = connected; if (connected) { this.emit('connected'); } else { this.emit('disconnected'); } } } parseChannelCommand(message, address) { if (address[0] === 'pgm') { return { pgmOn: message.args[0].value }; } else if (address[0] === 'pst') { return { pstOn: message.args[0].value }; } else if (address[0] === 'faderlevel') { return { faderLevel: message.args[0].value }; } else if (address[0] === 'state') { const stateFromChannel = this.parseSisyfosState(message).channels[0]; return { pgmOn: stateFromChannel.pgmOn, pstOn: stateFromChannel.pstOn, faderLevel: stateFromChannel.faderLevel, visible: stateFromChannel.visible, label: stateFromChannel.label, fadeTime: stateFromChannel.fadeTime, muteOn: stateFromChannel.muteOn, inputGain: stateFromChannel.inputGain, inputSelector: stateFromChannel.inputSelector, }; } return {}; } parseSisyfosState(message) { const extState = JSON.parse(message.args[0].value); const deviceState = { channels: {}, resync: false }; Object.keys(extState.channel).forEach((index) => { const ch = extState.channel[index]; let pgmOn = 0; if (ch.pgmOn === true) { pgmOn = 1; } else if (ch.voOn === true) { pgmOn = 2; } const channel = { faderLevel: ch.faderLevel || 0.75, pgmOn: pgmOn, pstOn: ch.pstOn === true ? 1 : 0, label: ch.label || '', visible: ch.showChannel ? true : false, fadeTime: ch.fadeTime || undefined, muteOn: ch.muteOn || false, inputGain: ch.inputGain || 0.75, inputSelector: ch.inputSelector || 1, timelineObjIds: [], }; deviceState.channels[index] = channel; }); return deviceState; } sendLoadMixerPresetCommand(presetName) { if (!this._oscClient) { throw new Error(`Can't load mixer preset, OSC client not initialised`); } this._oscClient.send({ address: `/loadmixerpreset`, args: [ { type: 's', value: presetName, }, ], }); } } exports.SisyfosApi = SisyfosApi; var SisyfosCommandType; (function (SisyfosCommandType) { SisyfosCommandType["TOGGLE_PGM"] = "togglePgm"; SisyfosCommandType["TOGGLE_PST"] = "togglePst"; SisyfosCommandType["SET_FADER"] = "setFader"; SisyfosCommandType["SET_INPUT_GAIN"] = "setInputGain"; SisyfosCommandType["SET_INPUT_SELECTOR"] = "setInputSelector"; SisyfosCommandType["SET_MUTE"] = "setMute"; SisyfosCommandType["CLEAR_PST_ROW"] = "clearPstRow"; SisyfosCommandType["LABEL"] = "label"; SisyfosCommandType["TAKE"] = "take"; SisyfosCommandType["VISIBLE"] = "visible"; SisyfosCommandType["RESYNC"] = "resync"; SisyfosCommandType["RESYNC_CHANNEL"] = "resyncChannel"; SisyfosCommandType["SET_CHANNEL"] = "setChannel"; SisyfosCommandType["LOAD_MIXER_PRESET"] = "loadMixerPreset"; })(SisyfosCommandType = exports.SisyfosCommandType || (exports.SisyfosCommandType = {})); //# sourceMappingURL=connection.js.map