UNPKG

timeline-state-resolver

Version:
590 lines • 27.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SisyfosMessageDevice = void 0; const _ = require("underscore"); const device_1 = require("./../../devices/device"); const timeline_state_resolver_types_1 = require("timeline-state-resolver-types"); const doOnTime_1 = require("../../devices/doOnTime"); const connection_1 = require("./connection"); const debug_1 = require("debug"); const lib_1 = require("../../lib"); const debug = (0, debug_1.default)('timeline-state-resolver:sisyfos'); /** * This is a generic wrapper for any osc-enabled device. */ class SisyfosMessageDevice extends device_1.DeviceWithState { constructor(deviceId, deviceOptions, getCurrentTime) { super(deviceId, deviceOptions, getCurrentTime); this._commandReceiver = this._defaultCommandReceiver.bind(this); this._resyncing = false; if (deviceOptions.options) { if (deviceOptions.commandReceiver) this._commandReceiver = deviceOptions.commandReceiver; } this._sisyfos = new connection_1.SisyfosApi(); this._sisyfos.on('error', (e) => this.emit('error', 'Sisyfos', e)); this._sisyfos.on('connected', () => { this._connectionChanged(); }); this._sisyfos.on('disconnected', () => { this._connectionChanged(); }); this._sisyfos.on('mixerOnline', (onlineStatus) => { this._sisyfos.setMixerOnline(onlineStatus); this._connectionChanged(); }); this._doOnTime = new doOnTime_1.DoOnTime(() => { return this.getCurrentTime(); }, doOnTime_1.SendMode.BURST, this._deviceOptions); this.handleDoOnTime(this._doOnTime, 'Sisyfos'); } async init(initOptions) { this._sisyfos.once('initialized', () => { this.setState(this.getDeviceState(false), this.getCurrentTime()); this.emit('resyncStates'); }); this._sisyfos .connect(initOptions.host, initOptions.port) .catch((e) => this.emit('error', 'Failed to initialise Sisyfos connection', e)); return true; } /** Called by the Conductor a bit before a .handleState is called */ prepareForHandleState(newStateTime) { // clear any queued commands later than this time: this._doOnTime.clearQueueNowAndAfter(newStateTime); this.cleanUpStates(0, newStateTime); } /** * Handles a new state such that the device will be in that state at a specific point * in time. * @param newState */ handleState(newState, newMappings) { super.onHandleState(newState, newMappings); if (!this._sisyfos.state) { this.emit('warning', 'Sisyfos State not initialized yet'); return; } // Transform timeline states into device states const convertTrace = (0, lib_1.startTrace)(`device:convertState`, { deviceId: this.deviceId }); const previousStateTime = Math.max(this.getCurrentTime(), newState.time); const oldSisyfosState = (this.getStateBefore(previousStateTime) || { state: { channels: {}, resync: false } }).state; this.emit('timeTrace', (0, lib_1.endTrace)(convertTrace)); const diffTrace = (0, lib_1.startTrace)(`device:diffState`, { deviceId: this.deviceId }); const newSisyfosState = this.convertStateToSisyfosState(newState, newMappings); this.emit('timeTrace', (0, lib_1.endTrace)(diffTrace)); this._handleStateInner(oldSisyfosState, newSisyfosState, previousStateTime, newState.time); } _handleStateInner(oldSisyfosState, newSisyfosState, previousStateTime, newTime) { // Generate commands necessary to transition to the new state const commandsToAchieveState = this._diffStates(oldSisyfosState, newSisyfosState); // clear any queued commands later than this time: this._doOnTime.clearQueueNowAndAfter(previousStateTime); // add the new commands to the queue: this._addToQueue(commandsToAchieveState, newTime); // store the new state, for later use: this.setState(newSisyfosState, newTime); } /** * Clear any scheduled commands after this time * @param clearAfterTime */ clearFuture(clearAfterTime) { this._doOnTime.clearQueueAfter(clearAfterTime); } async terminate() { this._doOnTime.dispose(); this._sisyfos.dispose(); this._sisyfos.removeAllListeners(); } getStatus() { let statusCode = device_1.StatusCode.GOOD; const messages = []; if (!this._sisyfos.connected) { statusCode = device_1.StatusCode.BAD; messages.push('Not connected'); } if (!this._sisyfos.state && !this._resyncing) { statusCode = device_1.StatusCode.BAD; messages.push(`Sisyfos device connection not initialized (restart required)`); } if (!this._sisyfos.mixerOnline) { statusCode = device_1.StatusCode.BAD; messages.push(`Sisyfos has no connection to Audiomixer`); } return { statusCode: statusCode, messages: messages, active: this.isActive, }; } async makeReady(okToDestroyStuff) { if (okToDestroyStuff) return this._makeReadyInner(okToDestroyStuff); } async _makeReadyInner(resync) { if (resync) { this._resyncing = true; // If state is still not reinitialised afer 5 seconds, we may have a problem. setTimeout(() => (this._resyncing = false), 5000); } this._doOnTime.clearQueueNowAndAfter(this.getCurrentTime()); this._sisyfos.reInitialize(); this._sisyfos.once('initialized', () => { if (resync) { this._resyncing = false; const targetState = this.getState(this.getCurrentTime()); if (targetState) { this._handleStateInner(this.getDeviceState(false), targetState.state, targetState.time, this.getCurrentTime()); } } else { this.setState(this.getDeviceState(false), this.getCurrentTime()); this.emit('resyncStates'); } }); return Promise.resolve(); } async executeAction(actionId0, payload0) { const actionId = actionId0; switch (actionId) { case timeline_state_resolver_types_1.SisyfosActions.Reinit: return this._makeReadyInner() .then(() => ({ result: timeline_state_resolver_types_1.ActionExecutionResultCode.Ok, })) .catch(() => ({ result: timeline_state_resolver_types_1.ActionExecutionResultCode.Error, })); case timeline_state_resolver_types_1.SisyfosActions.SetSisyfosChannelState: { const payload = payload0; if (typeof payload?.channel !== 'number') { return { result: timeline_state_resolver_types_1.ActionExecutionResultCode.Error, }; } this._sisyfos.setSisyfosChannel(payload.channel + 1, { ...this.getDeviceState().channels[payload.channel] }); return { result: timeline_state_resolver_types_1.ActionExecutionResultCode.Ok, }; } case timeline_state_resolver_types_1.SisyfosActions.LoadMixerPreset: { const payload = payload0; if (!payload?.name) { return { result: timeline_state_resolver_types_1.ActionExecutionResultCode.Error, response: (0, lib_1.t)('Missing name') }; } return this._handleLoadMixerPreset(payload.name); } default: return (0, lib_1.actionNotFoundMessage)(actionId); } } get canConnect() { return true; } get connected() { return this._sisyfos.connected; } getDeviceState(isDefaultState = true, mappings) { let deviceStateFromAPI = this._sisyfos.state; const deviceState = { channels: {}, resync: false, }; if (!deviceStateFromAPI) deviceStateFromAPI = deviceState; const channels = mappings ? Object.values(mappings || {}) .filter((m) => m.options.mappingType === timeline_state_resolver_types_1.MappingSisyfosType.Channel) .map((m) => m.options.channel) : Object.keys(deviceStateFromAPI.channels); for (const ch of channels) { const channelFromAPI = deviceStateFromAPI.channels[ch]; let channel = { ...channelFromAPI, timelineObjIds: [], }; if (isDefaultState) { // reset values for default state channel = { ...channel, ...this.getDefaultStateChannel(), }; } deviceState.channels[ch] = channel; } return deviceState; } getDefaultStateChannel() { return { faderLevel: 0.75, pgmOn: 0, pstOn: 0, label: '', visible: true, muteOn: false, inputGain: 0.75, inputSelector: 1, timelineObjIds: [], }; } /** * Transform the timeline state into a device state, which is in this case also * a timeline state. * @param state */ convertStateToSisyfosState(state, mappings) { const deviceState = this.getDeviceState(true, mappings); // Set labels to layer names for (const mapping of Object.values(mappings)) { const sisyfosMapping = mapping; if (sisyfosMapping.options.mappingType !== timeline_state_resolver_types_1.MappingSisyfosType.Channel) continue; if (!sisyfosMapping.options.setLabelToLayerName) continue; if (!sisyfosMapping.layerName) continue; let channel = deviceState.channels[sisyfosMapping.options.channel]; if (!channel) { channel = this.getDefaultStateChannel(); } channel.label = sisyfosMapping.layerName; deviceState.channels[sisyfosMapping.options.channel] = channel; } // Preparation: put all channels that comes from the state in an array: const newChannels = []; _.each(state.layers, (tlObject, layerName) => { const layer = tlObject; let foundMapping = mappings[layerName]; const content = tlObject.content; // Allow resync without valid channel mapping if ('resync' in content && content.resync !== undefined) { deviceState.resync = deviceState.resync || content.resync; } // Allow global retrigger without valid channel mapping if ('triggerValue' in content && content.triggerValue !== undefined && content.type === timeline_state_resolver_types_1.TimelineContentTypeSisyfos.TRIGGERVALUE) { deviceState.triggerValue = content.triggerValue; } // if the tlObj is specifies to load to PST the original Layer is used to resolve the mapping if (!foundMapping && layer.isLookahead && layer.lookaheadForLayer) { foundMapping = mappings[layer.lookaheadForLayer]; } if (foundMapping && foundMapping.deviceId === this.deviceId) { // @ts-ignore backwards-compatibility: if (!foundMapping.mappingType) foundMapping.mappingType = timeline_state_resolver_types_1.MappingSisyfosType.CHANNEL; // @ts-ignore backwards-compatibility: if (content.type === 'sisyfos') content.type = timeline_state_resolver_types_1.TimelineContentTypeSisyfos.CHANNEL; debug(`Mapping ${foundMapping.layerName}: ${foundMapping.options.mappingType}, ${foundMapping.options.channel || foundMapping.options.label}`); if (foundMapping.options.mappingType === timeline_state_resolver_types_1.MappingSisyfosType.Channel && content.type === timeline_state_resolver_types_1.TimelineContentTypeSisyfos.CHANNEL) { newChannels.push({ ...content, channel: foundMapping.options.channel, overridePriority: content.overridePriority || 0, isLookahead: layer.isLookahead || false, timelineObjId: layer.id, triggerValue: content.triggerValue, }); deviceState.resync = deviceState.resync || content.resync || false; } else if (foundMapping.options.mappingType === timeline_state_resolver_types_1.MappingSisyfosType.ChannelByLabel && content.type === timeline_state_resolver_types_1.TimelineContentTypeSisyfos.CHANNEL) { const ch = this._sisyfos.getChannelByLabel(foundMapping.options.label); debug(`Channel by label ${foundMapping.options.label}(${ch}): ${content.isPgm}`); if (ch === undefined) return; newChannels.push({ ...content, channel: ch, overridePriority: content.overridePriority || 0, isLookahead: layer.isLookahead || false, timelineObjId: layer.id, triggerValue: content.triggerValue, }); deviceState.resync = deviceState.resync || content.resync || false; } else if (foundMapping.options.mappingType === timeline_state_resolver_types_1.MappingSisyfosType.Channels && content.type === timeline_state_resolver_types_1.TimelineContentTypeSisyfos.CHANNELS) { _.each(content.channels, (channel) => { const referencedMapping = mappings[channel.mappedLayer]; if (referencedMapping && referencedMapping.options.mappingType === timeline_state_resolver_types_1.MappingSisyfosType.Channel) { newChannels.push({ ...channel, channel: referencedMapping.options.channel, overridePriority: content.overridePriority || 0, isLookahead: layer.isLookahead || false, timelineObjId: layer.id, triggerValue: content.triggerValue, }); } else if (referencedMapping && referencedMapping.options.mappingType === timeline_state_resolver_types_1.MappingSisyfosType.ChannelByLabel) { const ch = this._sisyfos.getChannelByLabel(referencedMapping.options.label); debug(`Channel by label ${referencedMapping.options.label}(${ch}): ${channel.isPgm}`); if (ch === undefined) return; newChannels.push({ ...channel, channel: ch, overridePriority: content.overridePriority || 0, isLookahead: layer.isLookahead || false, timelineObjId: layer.id, triggerValue: content.triggerValue, }); } }); deviceState.resync = deviceState.resync || content.resync || false; } } }); // Sort by overridePriority, so that those with highest overridePriority will be applied last _.each(_.sortBy(newChannels, (channel) => channel.overridePriority), (newChannel) => { if (!deviceState.channels[newChannel.channel]) { deviceState.channels[newChannel.channel] = this.getDefaultStateChannel(); } const channel = deviceState.channels[newChannel.channel]; if (newChannel.isPgm !== undefined) { if (newChannel.isLookahead) { channel.pstOn = newChannel.isPgm || 0; } else { channel.pgmOn = newChannel.isPgm || 0; } } if (newChannel.faderLevel !== undefined) channel.faderLevel = newChannel.faderLevel; if (newChannel.label !== undefined && newChannel.label !== '') channel.label = newChannel.label; if (newChannel.visible !== undefined) channel.visible = newChannel.visible; if (newChannel.fadeTime !== undefined) channel.fadeTime = newChannel.fadeTime; if (newChannel.muteOn !== undefined) channel.muteOn = newChannel.muteOn; if (newChannel.inputGain !== undefined) channel.inputGain = newChannel.inputGain; if (newChannel.inputSelector !== undefined) channel.inputSelector = newChannel.inputSelector; if (newChannel.triggerValue !== undefined) channel.triggerValue = newChannel.triggerValue; channel.timelineObjIds.push(newChannel.timelineObjId); }); return deviceState; } get deviceType() { return timeline_state_resolver_types_1.DeviceType.SISYFOS; } get deviceName() { return 'Sisyfos ' + this.deviceId; } get queue() { return this._doOnTime.getQueue(); } /** * add the new commands to the queue: * @param commandsToAchieveState * @param time */ _addToQueue(commandsToAchieveState, time) { _.each(commandsToAchieveState, (cmd) => { this._doOnTime.queue(time, undefined, async (cmd) => { return this._commandReceiver(time, cmd.command, cmd.context, cmd.timelineObjId); }, cmd); }); } /** * Compares the new timeline-state with the old one, and generates commands to account for the difference */ _diffStates(oldOscSendState, newOscSendState) { const commands = []; if (newOscSendState.resync && !oldOscSendState.resync) { commands.push({ context: `Resyncing with Sisyfos`, command: { type: connection_1.SisyfosCommandType.RESYNC, }, timelineObjId: '', }); } _.each(newOscSendState.channels, (newChannel, index) => { const oldChannel = oldOscSendState.channels[index]; if (newOscSendState.triggerValue && newOscSendState.triggerValue !== oldOscSendState.triggerValue) { // || (!oldChannel && Number(index) >= 0)) { // push commands for everything debug('reset channel ' + index); commands.push({ context: `Channel ${index} reset`, command: { type: connection_1.SisyfosCommandType.SET_CHANNEL, channel: Number(index), values: newChannel, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); return; } if (newChannel.triggerValue && oldChannel?.triggerValue !== newChannel.triggerValue) { // note - should we only do this if we have an oldchannel? debug('reset channel ' + index); commands.push({ context: `Channel ${index} reset`, command: { type: connection_1.SisyfosCommandType.SET_CHANNEL, channel: Number(index), values: newChannel, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); return; } if (oldChannel && oldChannel.pgmOn !== newChannel.pgmOn) { debug(`Channel ${index} pgm goes from "${oldChannel.pgmOn}" to "${newChannel.pgmOn}"`); const values = [newChannel.pgmOn]; if (newChannel.fadeTime) { values.push(newChannel.fadeTime); } commands.push({ context: `Channel ${index} pgm goes from "${oldChannel.pgmOn}" to "${newChannel.pgmOn}"`, command: { type: connection_1.SisyfosCommandType.TOGGLE_PGM, channel: Number(index), values, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } if (oldChannel && oldChannel.pstOn !== newChannel.pstOn) { debug(`Channel ${index} pst goes from "${oldChannel.pstOn}" to "${newChannel.pstOn}"`); commands.push({ context: `Channel ${index} pst goes from "${oldChannel.pstOn}" to "${newChannel.pstOn}"`, command: { type: connection_1.SisyfosCommandType.TOGGLE_PST, channel: Number(index), value: newChannel.pstOn, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } if (oldChannel && oldChannel.faderLevel !== newChannel.faderLevel) { debug(`change faderLevel ${index}: "${newChannel.faderLevel}"`); const values = [newChannel.faderLevel]; if (newChannel.fadeTime) { values.push(newChannel.fadeTime); } commands.push({ context: 'faderLevel change', command: { type: connection_1.SisyfosCommandType.SET_FADER, channel: Number(index), values, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } newChannel.label = newChannel.label || (oldChannel ? oldChannel.label : ''); if (oldChannel && newChannel.label !== '' && oldChannel.label !== newChannel.label) { debug(`set label on fader ${index}: "${newChannel.label}"`); commands.push({ context: 'set label on fader', command: { type: connection_1.SisyfosCommandType.LABEL, channel: Number(index), value: newChannel.label, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } if (oldChannel && oldChannel.visible !== newChannel.visible) { debug(`Channel ${index} Visibility goes from "${oldChannel.visible}" to "${newChannel.visible}"`); commands.push({ context: `Channel ${index} Visibility goes from "${oldChannel.visible}" to "${newChannel.visible}"`, command: { type: connection_1.SisyfosCommandType.VISIBLE, channel: Number(index), value: newChannel.visible, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } if (oldChannel && oldChannel.muteOn !== newChannel.muteOn) { debug(`Channel ${index} mute goes from "${oldChannel.muteOn}" to "${newChannel.muteOn}"`); commands.push({ context: `Channel ${index} mute goes from "${oldChannel.muteOn}" to "${newChannel.muteOn}"`, command: { type: connection_1.SisyfosCommandType.SET_MUTE, channel: Number(index), value: newChannel.muteOn, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } if (oldChannel && oldChannel.inputGain !== newChannel.inputGain) { debug(`Channel ${index} inputGain goes from "${oldChannel.inputGain}" to "${newChannel.inputGain}"`); commands.push({ context: `Channel ${index} inputGain goes from "${oldChannel.inputGain}" to "${newChannel.inputGain}"`, command: { type: connection_1.SisyfosCommandType.SET_INPUT_GAIN, channel: Number(index), value: newChannel.inputGain, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } if (oldChannel && oldChannel.inputSelector !== newChannel.inputSelector) { debug(`Channel ${index} inputSelector goes from "${oldChannel.inputSelector}" to "${newChannel.inputSelector}"`); commands.push({ context: `Channel ${index} inputSelector goes from "${oldChannel.inputSelector}" to "${newChannel.inputSelector}"`, command: { type: connection_1.SisyfosCommandType.SET_INPUT_SELECTOR, channel: Number(index), value: newChannel.inputSelector, }, timelineObjId: newChannel.timelineObjIds[0] || '', }); } }); return commands; } async _defaultCommandReceiver(_time, cmd, context, timelineObjId) { const cwc = { context, command: cmd, timelineObjId, }; this.emitDebug(cwc); if (cmd.type === connection_1.SisyfosCommandType.RESYNC) { return this._makeReadyInner(true); } else { try { this._sisyfos.send(cmd); return Promise.resolve(); } catch (e) { return Promise.reject(e); } } } _connectionChanged() { this.emit('connectionChanged', this.getStatus()); } _handleLoadMixerPreset(presetName) { if (!this._sisyfos.connected || !this._sisyfos.mixerOnline) return { result: timeline_state_resolver_types_1.ActionExecutionResultCode.Error, }; this._sisyfos.send({ type: connection_1.SisyfosCommandType.LOAD_MIXER_PRESET, presetName, }); return { result: timeline_state_resolver_types_1.ActionExecutionResultCode.Ok, }; } } exports.SisyfosMessageDevice = SisyfosMessageDevice; //# sourceMappingURL=index.js.map