Skip to content

Commit

Permalink
feat: atem color generator support SOFIE-2968 (#322)
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian authored Feb 28, 2024
1 parent 4d16d1b commit b7ceb69
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 28 deletions.
8 changes: 7 additions & 1 deletion packages/timeline-state-resolver-types/src/generated/atem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ export interface MappingAtemAudioRouting {
mappingType: MappingAtemType.AudioRouting
}

export interface MappingAtemColorGenerator {
index: number
mappingType: MappingAtemType.ColorGenerator
}

export enum MappingAtemType {
MixEffect = 'mixEffect',
DownStreamKeyer = 'downStreamKeyer',
Expand All @@ -77,9 +82,10 @@ export enum MappingAtemType {
AudioChannel = 'audioChannel',
MacroPlayer = 'macroPlayer',
AudioRouting = 'audioRouting',
ColorGenerator = 'colorGenerator',
}

export type SomeMappingAtem = MappingAtemMixEffect | MappingAtemDownStreamKeyer | MappingAtemSuperSourceBox | MappingAtemAuxilliary | MappingAtemMediaPlayer | MappingAtemSuperSourceProperties | MappingAtemAudioChannel | MappingAtemMacroPlayer | MappingAtemAudioRouting
export type SomeMappingAtem = MappingAtemMixEffect | MappingAtemDownStreamKeyer | MappingAtemSuperSourceBox | MappingAtemAuxilliary | MappingAtemMediaPlayer | MappingAtemSuperSourceProperties | MappingAtemAudioChannel | MappingAtemMacroPlayer | MappingAtemAudioRouting | MappingAtemColorGenerator

export enum AtemActions {
Resync = 'resync'
Expand Down
15 changes: 15 additions & 0 deletions packages/timeline-state-resolver-types/src/integrations/atem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export enum TimelineContentTypeAtem { // Atem-state
AUDIOCHANNEL = 'audioChan',
MACROPLAYER = 'macroPlayer',
AUDIOROUTING = 'audioRouting',
COLORGENERATOR = 'colorGenerator',
}

export enum AtemTransitionStyle { // Note: copied from atem-state
Expand Down Expand Up @@ -112,6 +113,7 @@ export type TimelineContentAtemAny =
| TimelineContentAtemAudioChannel
| TimelineContentAtemMediaPlayer
| TimelineContentAtemAudioRouting
| TimelineContentAtemColorGenerator

export interface TimelineContentAtemBase {
deviceType: DeviceType.ATEM
Expand Down Expand Up @@ -414,3 +416,16 @@ export interface TimelineContentAtemAudioRouting extends TimelineContentAtemBase
sourceId: number
}
}

export interface TimelineContentAtemColorGenerator extends TimelineContentAtemBase {
type: TimelineContentTypeAtem.COLORGENERATOR

colorGenerator: {
/** 0-3599 */
hue: number
/** 0-1000 */
saturation: number
/** 0-1000 */
luma: number
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@
},
"required": ["index"],
"additionalProperties": false
},
"colorGenerator": {
"type": "object",
"properties": {
"index": {
"type": "integer",
"ui:title": "Index",
"ui:summaryTitle": "Color Generator",
"default": 0,
"min": 0,
"ui:zeroBased": true
}
},
"required": ["index"],
"additionalProperties": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ exports[`Diff States Simple diff against empty state 1`] = `
"monitorOutput": undefined,
},
},
"colorGenerators": undefined,
"colorGenerators": [],
"macros": {
"player": {
"player": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
AtemTransitionStyle,
DeviceType,
MappingAtemAuxilliary,
MappingAtemColorGenerator,
MappingAtemMixEffect,
MappingAtemType,
Mappings,
Expand Down Expand Up @@ -216,4 +217,82 @@ describe('Diff States', () => {
expectIncludesAtemCommandName(allCommands, AtemConnection.Commands.AutoTransitionCommand.name)
}
})

test('Diff color generator without mapping', async () => {
const device = await createDevice()

const state1 = AtemConnection.AtemStateUtil.Create()
state1.colorGenerators = {
[0]: {
hue: 1,
saturation: 2,
luma: 3,
},
}

const diffOptions = createDiffOptions({})
expect(diffOptions.colorGenerators).toStrictEqual([])

expect(diffStatesSpy).toHaveBeenCalledTimes(0)

const commands = device.diffStates(undefined, state1, {})

expect(diffStatesSpy).toHaveBeenCalledTimes(1)
expect(diffStatesSpy).toHaveBeenNthCalledWith(
1,
expect.anything(),
AtemConnection.AtemStateUtil.Create(),
state1,
diffOptions
)

expect(commands).toHaveLength(0)
})

test('Diff color generator with mapping', async () => {
const device = await createDevice()

const state1 = AtemConnection.AtemStateUtil.Create()
state1.colorGenerators = {
[0]: {
hue: 1,
saturation: 2,
luma: 3,
},
}

const mappings: Mappings<MappingAtemColorGenerator> = {
myAux: {
device: DeviceType.ATEM,
deviceId: '',
options: {
mappingType: MappingAtemType.ColorGenerator,
index: 0,
},
},
}

const diffOptions = createDiffOptions(mappings)
expect(diffOptions.colorGenerators).toStrictEqual([0])

expect(diffStatesSpy).toHaveBeenCalledTimes(0)

const commands = device.diffStates(undefined, state1, mappings)

expect(diffStatesSpy).toHaveBeenCalledTimes(1)
expect(diffStatesSpy).toHaveBeenNthCalledWith(
1,
expect.anything(),
AtemConnection.AtemStateUtil.Create(),
state1,
diffOptions
)

const allCommands = extractAllCommands(commands)
expect(allCommands).toHaveLength(1)

const expectedCommand = new AtemConnection.Commands.ColorGeneratorCommand(0)
expectedCommand.updateProps({ hue: 1, saturation: 2, luma: 3 })
compareAtemCommands(allCommands[0], expectedCommand)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
MappingAtemAudioChannel,
MappingAtemAudioRouting,
MappingAtemAuxilliary,
MappingAtemColorGenerator,
MappingAtemDownStreamKeyer,
MappingAtemMacroPlayer,
MappingAtemMediaPlayer,
Expand All @@ -20,6 +21,7 @@ import {
TimelineContentAtemAUX,
TimelineContentAtemAudioChannel,
TimelineContentAtemAudioRouting,
TimelineContentAtemColorGenerator,
TimelineContentAtemDSK,
TimelineContentAtemMacroPlayer,
TimelineContentAtemMediaPlayer,
Expand Down Expand Up @@ -642,4 +644,52 @@ describe('AtemStateBuilder', () => {
expect(deviceState1).toEqual(expectedState)
})
})

describe('Color Generator', () => {
const myLayerMapping0: Mapping<MappingAtemColorGenerator> = {
device: DeviceType.ATEM,
deviceId: 'myAtem',
options: {
mappingType: MappingAtemType.ColorGenerator,
index: 0,
},
}
const myLayerMapping: Mappings = {
myLayer0: myLayerMapping0,
}

test('Basic', async () => {
const mockState1: Timeline.StateInTime<TimelineContentAtemColorGenerator> = {
myLayer0: makeTimelineObjectResolved<TimelineContentAtemColorGenerator>({
id: 'obj0',
enable: {
start: -1000, // 1 seconds ago
duration: 2000,
},
layer: 'myLayer0',
content: {
deviceType: DeviceType.ATEM,
type: TimelineContentTypeAtem.COLORGENERATOR,
colorGenerator: {
hue: 123,
luma: 456,
saturation: 789,
},
},
}),
}

const expectedState = AtemConnection.AtemStateUtil.Create()
expectedState.colorGenerators = {
[0]: {
hue: 123,
luma: 456,
saturation: 789,
},
}

const deviceState1 = AtemStateBuilder.fromTimeline(mockState1, myLayerMapping)
expect(deviceState1).toEqual(expectedState)
})
})
})
40 changes: 16 additions & 24 deletions packages/timeline-state-resolver/src/integrations/atem/diffState.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
import { Diff } from 'atem-state'
import { DeepComplete } from 'atem-state/dist/util'
import {
Mapping,
SomeMappingAtem,
MappingAtemAuxilliary,
MappingAtemType,
MappingAtemAudioChannel,
Mappings,
} from 'timeline-state-resolver-types'
import { Mapping, SomeMappingAtem, MappingAtemType, Mappings } from 'timeline-state-resolver-types'

/**
Returns an option object to be passed into AtemState.diffStates().
Based on the mappings, these options enables/disables certain areas-of-interest in the diff atem state.
*/
export function createDiffOptions(mappings: Mappings): DeepComplete<Diff.SectionsToDiff> {
// Find the auxes that have mappings
const auxMappings = Object.values<Mapping<unknown>>(mappings)
.filter(
(mapping): mapping is Mapping<MappingAtemAuxilliary> =>
(mapping as Mapping<SomeMappingAtem>).options.mappingType === MappingAtemType.Auxilliary
)
.map((mapping) => mapping.options.index)
export function createDiffOptions(mappings: Mappings<SomeMappingAtem>): DeepComplete<Diff.SectionsToDiff> {
const auxMappings: number[] = []
const audioOutputs: number[] = []
const colorGenerators: number[] = []

for (const mapping of Object.values<Mapping<SomeMappingAtem>>(mappings)) {
if (mapping.options.mappingType === MappingAtemType.Auxilliary) {
auxMappings.push(mapping.options.index)
} else if (mapping.options.mappingType === MappingAtemType.AudioChannel) {
audioOutputs.push(mapping.options.index)
} else if (mapping.options.mappingType === MappingAtemType.ColorGenerator) {
colorGenerators.push(mapping.options.index)
}
}

// Find the audioOutputs that have mappings
const audioOutputs = Object.values<Mapping<unknown>>(mappings)
.filter(
(mapping): mapping is Mapping<MappingAtemAudioChannel> =>
(mapping as Mapping<SomeMappingAtem>).options.mappingType === MappingAtemType.Auxilliary
)
.map((mapping) => mapping.options.index)
const audioOutputsObj: DeepComplete<Record<number | 'default', Diff.DiffFairlightAudioRoutingOutput | undefined>> = {
default: undefined,
}
Expand All @@ -42,7 +34,7 @@ export function createDiffOptions(mappings: Mappings): DeepComplete<Diff.Section
// Manually construct the tree of what to diff, to match the previous version of atem-state.
// Future: this should be computed from the mappings
return {
colorGenerators: undefined,
colorGenerators: colorGenerators,
settings: {
multiviewer: undefined,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ActionExecutionResult,
ActionExecutionResultCode,
AtemActions,
SomeMappingAtem,
} from 'timeline-state-resolver-types'
import { AtemState, State as DeviceState } from 'atem-state'
import {
Expand Down Expand Up @@ -160,7 +161,7 @@ export class AtemDevice extends Device<AtemOptions, AtemDeviceState, AtemCommand
// Make sure there is something to diff against
oldAtemState = oldAtemState ?? this._atem.state ?? AtemStateUtil.Create()

const diffOptions = createDiffOptions(mappings)
const diffOptions = createDiffOptions(mappings as Mappings<SomeMappingAtem>)
const commands = AtemState.diffStates(this._protocolVersion, oldAtemState, newAtemState, diffOptions)

if (commands.length > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import {
MappingAtemDownStreamKeyer,
MappingAtemAudioRouting,
TimelineContentAtemAudioRouting,
MappingAtemColorGenerator,
TimelineContentAtemColorGenerator,
} from 'timeline-state-resolver-types'
import _ = require('underscore')
import { State as DeviceState, Defaults as StateDefault } from 'atem-state'
import { Defaults, State as DeviceState, Defaults as StateDefault } from 'atem-state'
import { assertNever, cloneDeep, deepMerge, literal } from '../../lib'
import { PartialDeep } from 'type-fest'

Expand Down Expand Up @@ -98,6 +100,11 @@ export class AtemStateBuilder {
builder._applyMacroPlayer(mapping.options, content)
}
break
case MappingAtemType.ColorGenerator:
if (content.type === TimelineContentTypeAtem.COLORGENERATOR) {
builder._applyColorGenerator(mapping.options, content)
}
break
default:
assertNever(mapping.options)
break
Expand Down Expand Up @@ -294,4 +301,13 @@ export class AtemStateBuilder {
content.macroPlayer
)
}

private _applyColorGenerator(mapping: MappingAtemColorGenerator, content: TimelineContentAtemColorGenerator): void {
if (!this.#deviceState.colorGenerators) this.#deviceState.colorGenerators = {}
this.#deviceState.colorGenerators[mapping.index] = {
...Defaults.Color.ColorGenerator,
...this.#deviceState.colorGenerators[mapping.index],
...content.colorGenerator,
}
}
}

0 comments on commit b7ceb69

Please sign in to comment.