-
Notifications
You must be signed in to change notification settings - Fork 5
feat: allow hotcuts while using transition property #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
621b29c
feat: allow hotcuts while using transition property
mint-dewit f42514a
chore: guard against missing props
mint-dewit 7676b61
chore: allow setting preview when program is unchanged
mint-dewit 6b12f64
chore: diff transition for USK & DSK
mint-dewit 454d19e
chore: fix tests
mint-dewit 938cb38
chore: skip keyer transitions if ME doesn't diff
mint-dewit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,42 +1,48 @@ | ||
| /* eslint-disable @typescript-eslint/no-non-null-assertion */ | ||
| import * as ME from '../mixEffect' | ||
| import * as Enums from '../../enums' | ||
| import { MixEffect, ExtendedMixEffect } from '../../state' | ||
| import { MixEffect } from '../../state' | ||
| import * as Defaults from '../../defaults' | ||
| import { Commands, Enums as AtemEnums, AtemStateUtil, VideoState } from 'atem-connection' | ||
| import { Commands, Enums as AtemEnums, AtemStateUtil } from 'atem-connection' | ||
| import { jsonClone } from '../../util' | ||
| import { DiffAllObject, DiffMixEffect } from '../../diff' | ||
|
|
||
| const STATE1 = AtemStateUtil.Create() | ||
| const ME1: MixEffect = AtemStateUtil.getMixEffect(STATE1, 0) as unknown as MixEffect | ||
| const STATE2 = AtemStateUtil.Create() | ||
| const ME2: MixEffect = AtemStateUtil.getMixEffect(STATE2, 0) as unknown as MixEffect | ||
|
|
||
| const fullDiffObject = DiffAllObject().video?.mixEffects as DiffMixEffect | ||
|
|
||
| function getState() { | ||
| const state1 = AtemStateUtil.Create() | ||
| const mixEffect1: MixEffect = AtemStateUtil.getMixEffect(state1, 0) as unknown as MixEffect | ||
| const state2 = AtemStateUtil.Create() | ||
| const mixEffect2: MixEffect = AtemStateUtil.getMixEffect(state2, 0) as unknown as MixEffect | ||
|
|
||
| return [mixEffect1, mixEffect2] | ||
| } | ||
|
|
||
| test('Unit: mix effect: same state gives no commands', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| // same state gives no commands: | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) | ||
| expect(commands).toHaveLength(0) | ||
| }) | ||
|
|
||
| test('Unit: mix effect: same input gives no commands', function () { | ||
| ;(ME1 as ExtendedMixEffect).input = 1 | ||
| ;(ME1 as ExtendedMixEffect).transition = Enums.TransitionStyle.CUT | ||
| ;(ME2 as ExtendedMixEffect).input = 1 | ||
| ;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.CUT | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME1.programInput = 1 | ||
| ME1.transition = Enums.TransitionStyle.CUT | ||
| ME2.programInput = 1 | ||
| ME2.transition = Enums.TransitionStyle.CUT | ||
|
|
||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) | ||
| expect(commands).toHaveLength(0) | ||
|
|
||
| delete (ME1 as Partial<ExtendedMixEffect>).input | ||
| delete (ME1 as Partial<ExtendedMixEffect>).transition | ||
| delete (ME2 as Partial<ExtendedMixEffect>).input | ||
| delete (ME2 as Partial<ExtendedMixEffect>).transition | ||
| }) | ||
|
|
||
| test('Unit: mix effect: program input', function () { | ||
| ;(ME2 as VideoState.MixEffect).programInput = 1 | ||
| test('Unit: mix effect: hot cut program', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.programInput = 1 | ||
| ME2.transition = Enums.TransitionStyle.CUT | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.ProgramInputCommand> | ||
| expect(commands).toHaveLength(1) | ||
|
|
||
|
|
@@ -45,11 +51,12 @@ test('Unit: mix effect: program input', function () { | |
| expect(commands[0].properties).toEqual({ | ||
| source: 1, | ||
| }) | ||
| ;(ME2 as VideoState.MixEffect).programInput = 0 | ||
| }) | ||
|
|
||
| test('Unit: mix effect: preview input', function () { | ||
| ;(ME2 as VideoState.MixEffect).previewInput = 1 | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.previewInput = 1 | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand> | ||
| expect(commands).toHaveLength(1) | ||
|
|
||
|
|
@@ -58,31 +65,55 @@ test('Unit: mix effect: preview input', function () { | |
| expect(commands[0].properties).toEqual({ | ||
| source: 1, | ||
| }) | ||
| ;(ME2 as VideoState.MixEffect).previewInput = 0 | ||
| }) | ||
|
|
||
| test('Unit: mix effect: cut command', function () { | ||
| ;(ME2 as ExtendedMixEffect).input = 1 | ||
| ;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.CUT | ||
| test('Unit: mix effect: program + preview', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.previewInput = 2 | ||
| ME2.programInput = 1 | ||
| ME2.transition = Enums.TransitionStyle.CUT | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand> | ||
| expect(commands).toHaveLength(2) | ||
|
|
||
| expect(commands[0].constructor.name).toEqual('PreviewInputCommand') | ||
| expect(commands[0].constructor.name).toEqual('ProgramInputCommand') | ||
| expect(commands[0].mixEffect).toEqual(0) | ||
| expect(commands[0].properties).toEqual({ | ||
| source: 1, | ||
| }) | ||
|
|
||
| expect(commands[1].constructor.name).toEqual('CutCommand') | ||
| expect(commands[1].constructor.name).toEqual('PreviewInputCommand') | ||
| expect(commands[1].mixEffect).toEqual(0) | ||
| expect(commands[1].properties).toEqual({ | ||
| source: 2, | ||
| }) | ||
| }) | ||
|
|
||
| delete (ME2 as Partial<ExtendedMixEffect>).input | ||
| delete (ME2 as Partial<ExtendedMixEffect>).transition | ||
| test('Unit: mix effect: deprecated "input" field', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.previewInput = 2 | ||
| ME2.input = 1 // this is deprecated and should follow the same logic as using programInput | ||
| ME2.transition = Enums.TransitionStyle.CUT | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand> | ||
| expect(commands).toHaveLength(2) | ||
|
||
|
|
||
| expect(commands[0].constructor.name).toEqual('ProgramInputCommand') | ||
| expect(commands[0].mixEffect).toEqual(0) | ||
| expect(commands[0].properties).toEqual({ | ||
| source: 1, | ||
| }) | ||
| expect(commands[1].constructor.name).toEqual('PreviewInputCommand') | ||
| expect(commands[1].mixEffect).toEqual(0) | ||
| expect(commands[1].properties).toEqual({ | ||
| source: 2, | ||
| }) | ||
| }) | ||
|
|
||
| test('Unit: mix effect: dummy command', function () { | ||
| ;(ME2 as ExtendedMixEffect).input = 1 | ||
| ;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.DUMMY | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.programInput = 1 | ||
| ME2.transition = Enums.TransitionStyle.DUMMY | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewInputCommand> | ||
| expect(commands).toHaveLength(1) | ||
|
|
||
|
|
@@ -93,14 +124,13 @@ test('Unit: mix effect: dummy command', function () { | |
| }) | ||
|
|
||
| // Dummy implies that something else will perform the cut. (eg a macro) | ||
|
|
||
| delete (ME2 as Partial<ExtendedMixEffect>).input | ||
| delete (ME2 as Partial<ExtendedMixEffect>).transition | ||
| }) | ||
|
|
||
| test('Unit: mix effect: auto command', function () { | ||
| ;(ME2 as ExtendedMixEffect).input = 1 | ||
| ;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.MIX | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.programInput = 1 | ||
| ME2.transition = Enums.TransitionStyle.MIX | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array< | ||
| Commands.PreviewInputCommand | Commands.TransitionPositionCommand | ||
| > | ||
|
|
@@ -120,12 +150,13 @@ test('Unit: mix effect: auto command', function () { | |
|
|
||
| expect(commands[2].constructor.name).toEqual('AutoTransitionCommand') | ||
| expect(commands[2].mixEffect).toEqual(0) | ||
| ;(ME2 as ExtendedMixEffect).input = 0 | ||
| }) | ||
|
|
||
| test('Unit: mix effect: auto command, new transition', function () { | ||
| ;(ME2 as ExtendedMixEffect).input = 1 | ||
| ;(ME2 as ExtendedMixEffect).transition = Enums.TransitionStyle.WIPE | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.programInput = 1 | ||
| ME2.transition = Enums.TransitionStyle.WIPE | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array< | ||
| Commands.PreviewInputCommand | Commands.TransitionPositionCommand | ||
| > | ||
|
|
@@ -151,21 +182,22 @@ test('Unit: mix effect: auto command, new transition', function () { | |
|
|
||
| expect(commands[3].constructor.name).toEqual('AutoTransitionCommand') | ||
| expect(commands[3].mixEffect).toEqual(0) | ||
| ;(ME2 as ExtendedMixEffect).input = 0 | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition preview', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionPreview = true | ||
| const commands = ME.resolveMixEffectsState([ME1], [ME2], fullDiffObject) as Array<Commands.PreviewTransitionCommand> | ||
| expect(commands).toHaveLength(1) | ||
|
|
||
| expect(commands[0].constructor.name).toEqual('PreviewTransitionCommand') | ||
| expect(commands[0].mixEffect).toEqual(0) | ||
|
|
||
| ME2.transitionPreview = false | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition position', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionPosition = { | ||
| ...ME2.transitionPosition, | ||
| inTransition: true, | ||
|
|
@@ -179,15 +211,11 @@ test('Unit: mix effect: transition position', function () { | |
| expect(commands[0].properties).toEqual({ | ||
| handlePosition: 500, | ||
| }) | ||
|
|
||
| ME2.transitionPosition = { | ||
| ...ME2.transitionPosition, | ||
| inTransition: false, | ||
| handlePosition: 0, | ||
| } | ||
| }) | ||
|
|
||
| test('Unit: mix effect: from transition, to no transition', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME1.transitionPosition = { | ||
| ...ME1.transitionPosition, | ||
| inTransition: true, | ||
|
|
@@ -201,15 +229,11 @@ test('Unit: mix effect: from transition, to no transition', function () { | |
| expect(commands[0].properties).toEqual({ | ||
| handlePosition: 10000, | ||
| }) | ||
|
|
||
| ME1.transitionPosition = { | ||
| ...ME1.transitionPosition, | ||
| inTransition: false, | ||
| handlePosition: 0, | ||
| } | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition properties', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionProperties.nextSelection = [ | ||
| AtemEnums.TransitionSelection.Background, | ||
| AtemEnums.TransitionSelection.Key1, | ||
|
|
@@ -228,15 +252,11 @@ test('Unit: mix effect: transition properties', function () { | |
| nextSelection: [AtemEnums.TransitionSelection.Background, AtemEnums.TransitionSelection.Key1], | ||
| nextStyle: 1, | ||
| }) | ||
|
|
||
| ME2.transitionProperties = { | ||
| ...ME2.transitionProperties, | ||
| selection: [AtemEnums.TransitionSelection.Background], | ||
| style: 0, | ||
| } | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition settings: dip', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionSettings.dip = { | ||
| input: 1, | ||
| rate: 50, | ||
|
|
@@ -255,11 +275,11 @@ test('Unit: mix effect: transition settings: dip', function () { | |
| input: 1, | ||
| rate: 50, | ||
| }) | ||
|
|
||
| delete ME2.transitionSettings.dip | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition settings: DVE', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionSettings.DVE = { | ||
| rate: 50, | ||
| logoRate: 50, | ||
|
|
@@ -301,11 +321,11 @@ test('Unit: mix effect: transition settings: DVE', function () { | |
| reverse: true, | ||
| flipFlop: true, | ||
| }) | ||
|
|
||
| delete ME2.transitionSettings.DVE | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition settings: mix', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionSettings.mix = jsonClone(Defaults.Video.MixTransitionSettings) | ||
| ME2.transitionSettings.mix.rate = 50 | ||
| const commands = ME.resolveTransitionSettingsState( | ||
|
|
@@ -321,11 +341,11 @@ test('Unit: mix effect: transition settings: mix', function () { | |
| expect(commands[0].properties).toEqual({ | ||
| rate: 50, | ||
| }) | ||
|
|
||
| delete ME2.transitionSettings.mix | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition settings: stinger', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionSettings.stinger = { | ||
| source: 1, | ||
| preMultipliedKey: true, | ||
|
|
@@ -362,11 +382,11 @@ test('Unit: mix effect: transition settings: stinger', function () { | |
| triggerPoint: 25, | ||
| mixRate: 25, | ||
| }) | ||
|
|
||
| delete ME2.transitionSettings.stinger | ||
| }) | ||
|
|
||
| test('Unit: mix effect: transition settings: wipe', function () { | ||
| const [ME1, ME2] = getState() | ||
|
|
||
| ME2.transitionSettings.wipe = { | ||
| rate: 50, | ||
| pattern: AtemEnums.Pattern.HorizontalBarnDoor, | ||
|
|
@@ -401,6 +421,4 @@ test('Unit: mix effect: transition settings: wipe', function () { | |
| reverseDirection: true, | ||
| flipFlop: true, | ||
| }) | ||
|
|
||
| delete ME2.transitionSettings.wipe | ||
| }) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type assertion in this test casts the result to
Array<Commands.PreviewInputCommand>, but the expectation checks for aProgramInputCommandat index 0. Using a union type here (or avoiding the cast) will keep the test type-safe and prevent future refactors from being masked by an overly narrow cast.