From 26033cdba23618bc03bf4dee89c1db7907b40dcc Mon Sep 17 00:00:00 2001 From: Balte de Wit Date: Tue, 12 Jan 2021 14:57:33 +0100 Subject: [PATCH] feat: sisyfos retrigger mechanism --- src/devices/__tests__/sisyfos.spec.ts | 200 +++++++++++++++++++++++++- src/devices/sisyfos.ts | 22 ++- src/devices/sisyfosAPI.ts | 43 +++++- src/types/src/sisyfos.ts | 14 +- 4 files changed, 272 insertions(+), 7 deletions(-) diff --git a/src/devices/__tests__/sisyfos.spec.ts b/src/devices/__tests__/sisyfos.spec.ts index bd5f085120..e3be9eed95 100644 --- a/src/devices/__tests__/sisyfos.spec.ts +++ b/src/devices/__tests__/sisyfos.spec.ts @@ -97,10 +97,8 @@ describe('Sisyfos', () => { duration: 2000 }, layer: 'sisyfos_channel_1', - // @ts-ignore: for backwards compatibility content: { deviceType: DeviceType.SISYFOS, - // @ts-ignore: for backwards compatibility type: 'sisyfos', isPgm: 1 @@ -791,6 +789,204 @@ describe('Sisyfos', () => { commandReceiver0.mockClear() }) + test('Sisyfos: using triggerValue', async () => { + + const commandReceiver0: any = jest.fn(() => { + return Promise.resolve() + }) + let myChannelMapping0: MappingSisyfos = { + device: DeviceType.SISYFOS, + mappingType: MappingSisyfosType.CHANNELS, + deviceId: 'mySisyfos' + } + let myChannelMapping1: MappingSisyfos = { + device: DeviceType.SISYFOS, + mappingType: MappingSisyfosType.CHANNEL, + deviceId: 'mySisyfos', + channel: 1 + } + let myChannelMapping2: MappingSisyfos = { + device: DeviceType.SISYFOS, + mappingType: MappingSisyfosType.CHANNEL, + deviceId: 'mySisyfos', + channel: 2 + } + let myChannelMapping3: MappingSisyfos = { + device: DeviceType.SISYFOS, + mappingType: MappingSisyfosType.CHANNELS, + deviceId: 'mySisyfos' + } + let myChannelMapping: Mappings = { + 'sisyfos_channels_base': myChannelMapping0, + 'sisyfos_channel_1': myChannelMapping1, + 'sisyfos_channel_2': myChannelMapping2, + 'sisyfos_channels': myChannelMapping3 + } + + let myConductor = new Conductor({ + initializeAsClear: true, + getCurrentTime: mockTime.getCurrentTime + }) + myConductor.setTimelineAndMappings([], myChannelMapping) + await myConductor.init() // we cannot do an await, because setTimeout will never call without jest moving on. + await myConductor.addDevice('mySisyfos', { + type: DeviceType.SISYFOS, + options: { + commandReceiver: commandReceiver0, + host: '192.168.0.10', + port: 8900 + } + }) + await mockTime.advanceTimeToTicks(10100) + + let deviceContainer = myConductor.getDevice('mySisyfos') + let device = deviceContainer.device as ThreadedClass + + // Check that no commands has been scheduled: + expect(await device.queue).toHaveLength(0) + + myConductor.setTimelineAndMappings([ + { + id: 'baseline', + enable: { + while: 1 + }, + layer: 'sisyfos_channels_base', + content: { + deviceType: DeviceType.SISYFOS, + type: TimelineContentTypeSisyfos.CHANNELS, + + channels: [ + { + mappedLayer: 'sisyfos_channel_1', + faderLevel: 0.1, + isPgm: 0 + }, + { + mappedLayer: 'sisyfos_channel_2', + faderLevel: 0.2, + isPgm: 0 + } + ], + triggerValue: 'a', + overridePriority: -999 + } + }, + { + id: 'obj1', + enable: { + start: mockTime.now + 1000, // 1 seconds in the future + duration: 10000 + }, + layer: 'sisyfos_channel_1', + content: { + deviceType: DeviceType.SISYFOS, + type: TimelineContentTypeSisyfos.TRIGGERVALUE, + triggerValue: 'b' + } + }, + { + id: 'obj2', + enable: { + start: mockTime.now + 1000, // 1 seconds in the future + duration: 10000 + }, + layer: 'sisyfos_channel_2', + content: { + deviceType: DeviceType.SISYFOS, + type: TimelineContentTypeSisyfos.CHANNEL, + + isPgm: 1 + } + } + ]) + + // baseline: + await mockTime.advanceTimeTicks(100) // 100 + expect(commandReceiver0.mock.calls.length).toEqual(3) + expect(getMockCall(commandReceiver0, 0, 1)).toMatchObject({ + type: 'setChannel', + channel: 0, + values: { + faderLevel: 0.75 + } + }) + expect(getMockCall(commandReceiver0, 1, 1)).toMatchObject({ + type: 'setChannel', + channel: 1, + values: { + faderLevel: 0.1, + pgmOn: 0 + } + }) + expect(getMockCall(commandReceiver0, 2, 1)).toMatchObject({ + type: 'setChannel', + channel: 2, + values: { + faderLevel: 0.2, + pgmOn: 0 + } + }) + commandReceiver0.mockClear() + + // obj1 has started + await mockTime.advanceTimeTicks(1000) // 1100 + expect(commandReceiver0.mock.calls.length).toEqual(3) + expect(getMockCall(commandReceiver0, 0, 1)).toMatchObject({ + type: 'setChannel', + channel: 0, + values: { + faderLevel: 0.75 + } + }) + expect(getMockCall(commandReceiver0, 1, 1)).toMatchObject({ + type: 'setChannel', + channel: 1, + values: { + faderLevel: 0.1, + pgmOn: 0 + } + }) + expect(getMockCall(commandReceiver0, 2, 1)).toMatchObject({ + type: 'setChannel', + channel: 2, + values: { + faderLevel: 0.2, + pgmOn: 1 + } + }) + commandReceiver0.mockClear() + + // back to baseline + await mockTime.advanceTimeTicks(10000) // 11100 + expect(commandReceiver0.mock.calls.length).toEqual(3) + expect(getMockCall(commandReceiver0, 0, 1)).toMatchObject({ + type: 'setChannel', + channel: 0, + values: { + faderLevel: 0.75 + } + }) + expect(getMockCall(commandReceiver0, 1, 1)).toMatchObject({ + type: 'setChannel', + channel: 1, + values: { + faderLevel: 0.1, + pgmOn: 0 + } + }) + expect(getMockCall(commandReceiver0, 2, 1)).toMatchObject({ + type: 'setChannel', + channel: 2, + values: { + faderLevel: 0.2, + pgmOn: 0 + } + }) + + commandReceiver0.mockClear() + }) + test('Connection status', async () => { const commandReceiver0: any = jest.fn(() => { return Promise.resolve() diff --git a/src/devices/sisyfos.ts b/src/devices/sisyfos.ts index 534688d7c9..77767a3af8 100644 --- a/src/devices/sisyfos.ts +++ b/src/devices/sisyfos.ts @@ -290,6 +290,11 @@ export class SisyfosMessageDevice extends DeviceWithState implemen deviceState.resync = deviceState.resync || layer.content.resync } + // Allow retrigger without valid channel mapping + if (layer.content.triggerValue !== undefined) { + deviceState.triggerValue = layer.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] as MappingSisyfos | undefined @@ -312,6 +317,7 @@ export class SisyfosMessageDevice extends DeviceWithState implemen isLookahead: layer.isLookahead || false, tlObjId: layer.id }) + deviceState.resync = deviceState.resync || content.resync || false } else if ( foundMapping.mappingType === MappingSisyfosType.CHANNELS && content.type === TimelineContentTypeSisyfos.CHANNELS @@ -329,8 +335,8 @@ export class SisyfosMessageDevice extends DeviceWithState implemen }) } }) + deviceState.resync = deviceState.resync || content.resync || false } - deviceState.resync = deviceState.resync || content.resync || false } }) @@ -403,6 +409,20 @@ export class SisyfosMessageDevice extends DeviceWithState implemen _.each(newOscSendState.channels, (newChannel: SisyfosChannel, index) => { const oldChannel = oldOscSendState.channels[index] + if ((newOscSendState.triggerValue && newOscSendState.triggerValue !== oldOscSendState.triggerValue)) { // || (!oldChannel && Number(index) >= 0)) { + // push commands for everything + commands.push({ + context: `Channel ${index} reset`, + content: { + type: SisyfosCommandType.SET_CHANNEL, + channel: Number(index), + values: newChannel + }, + timelineObjId: newChannel.tlObjIds[0] || '' + }) + return + } + if (oldChannel && oldChannel.pgmOn !== newChannel.pgmOn) { commands.push({ context: `Channel ${index} pgm goes from "${oldChannel.pgmOn}" to "${newChannel.pgmOn}"`, diff --git a/src/devices/sisyfosAPI.ts b/src/devices/sisyfosAPI.ts index 0ec5febd66..c405b84544 100644 --- a/src/devices/sisyfosAPI.ts +++ b/src/devices/sisyfosAPI.ts @@ -95,6 +95,37 @@ export class SisyfosApi extends EventEmitter { type: 'i', value: (command as ValueCommand).value }] }) + } else if (command.type === SisyfosCommandType.SET_CHANNEL) { + if ((command as SetChannelCommand).values.label) { + this._oscClient.send({ address: `/ch/${(command as StringCommand).channel + 1}/label`, args: [{ + type: 's', + value: (command as SetChannelCommand).values.label as string + }] }) + } + if ((command as SetChannelCommand).values.pgmOn !== undefined) { + this._oscClient.send({ address: `/ch/${(command as ValueCommand).channel + 1}/pgm`, args: [{ + type: 'i', + value: (command as SetChannelCommand).values.pgmOn as number + }] }) + } + if ((command as SetChannelCommand).values.pstOn !== undefined) { + this._oscClient.send({ address: `/ch/${(command as ValueCommand).channel + 1}/pst`, args: [{ + type: 'i', + value: (command as SetChannelCommand).values.pstOn as number + }] }) + } + if ((command as SetChannelCommand).values.faderLevel !== undefined) { + this._oscClient.send({ address: `/ch/${(command as ValueCommand).channel + 1}/faderlevel`, args: [{ + type: 'f', + value: (command as SetChannelCommand).values.faderLevel as number + }] }) + } + if ((command as SetChannelCommand).values.visible !== undefined) { + this._oscClient.send({ address: `/ch/${(command as ValueCommand).channel + 1}/visible`, args: [{ + type: 'i', + value: (command as SetChannelCommand).values.visible as unknown as number + }] }) + } } } @@ -242,13 +273,20 @@ export enum SisyfosCommandType { LABEL = 'label', TAKE = 'take', VISIBLE = 'visible', - RESYNC = 'resync' + RESYNC = 'resync', + SET_CHANNEL = 'setChannel' } export interface BaseCommand { type: SisyfosCommandType } +export interface SetChannelCommand { + type: SisyfosCommandType.SET_CHANNEL + channel: number + values: Partial +} + export interface ChannelCommand { type: SisyfosCommandType.SET_FADER | SisyfosCommandType.TOGGLE_PGM | SisyfosCommandType.TOGGLE_PST | SisyfosCommandType.LABEL | SisyfosCommandType.VISIBLE channel: number @@ -273,7 +311,7 @@ export interface ResyncCommand extends BaseCommand { type: SisyfosCommandType.RESYNC } -export type SisyfosCommand = BaseCommand | ValueCommand | BoolCommand | StringCommand | ResyncCommand +export type SisyfosCommand = BaseCommand | ValueCommand | BoolCommand | StringCommand | ResyncCommand | SetChannelCommand export interface SisyfosChannel extends SisyfosAPIChannel { tlObjIds: string[] @@ -281,6 +319,7 @@ export interface SisyfosChannel extends SisyfosAPIChannel { export interface SisyfosState { channels: { [index: string]: SisyfosChannel } resync: boolean + triggerValue?: string } // ------------------------------------------------------ diff --git a/src/types/src/sisyfos.ts b/src/types/src/sisyfos.ts index 8c83dbb528..9a4f968cac 100644 --- a/src/types/src/sisyfos.ts +++ b/src/types/src/sisyfos.ts @@ -27,10 +27,11 @@ export enum TimelineContentTypeSisyfos { /** @deprecated use CHANNEL instead */ SISYFOS = 'sisyfos', CHANNEL = 'channel', - CHANNELS = 'channels' + CHANNELS = 'channels', + TRIGGERVALUE = 'triggerValue' } -export type TimelineObjSisyfosAny = TimelineObjSisyfosChannel | TimelineObjSisyfosChannels +export type TimelineObjSisyfosAny = TimelineObjSisyfosChannel | TimelineObjSisyfosChannels | TimelineObjSisyfosTriggerValue export interface TimelineObjSisyfos extends TSRTimelineObjBase { content: { @@ -46,6 +47,14 @@ export interface SisyfosChannelOptions { visible?: boolean } +export interface TimelineObjSisyfosTriggerValue extends TimelineObjSisyfos { + content: { + deviceType: DeviceType.SISYFOS + type: TimelineContentTypeSisyfos.TRIGGERVALUE + + triggerValue: string + } +} export interface TimelineObjSisyfosChannel extends TimelineObjSisyfos { content: { deviceType: DeviceType.SISYFOS @@ -66,6 +75,7 @@ export interface TimelineObjSisyfosChannels extends TimelineObjSisyfos { )[], resync?: boolean overridePriority?: number // defaults to 0 + triggerValue?: string } } // Backwards compatibility: