From eb95d941f1cc5d9f6d7d873d263728dcef172c75 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 26 Sep 2024 18:55:13 +0100 Subject: [PATCH 1/3] feat: multiviewer border color --- .../Settings/MultiViewerBorderColorCommand.ts | 49 +++++++++++++++++++ src/commands/Settings/index.ts | 3 +- src/state/settings.ts | 12 +++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 src/commands/Settings/MultiViewerBorderColorCommand.ts diff --git a/src/commands/Settings/MultiViewerBorderColorCommand.ts b/src/commands/Settings/MultiViewerBorderColorCommand.ts new file mode 100644 index 000000000..3a814d231 --- /dev/null +++ b/src/commands/Settings/MultiViewerBorderColorCommand.ts @@ -0,0 +1,49 @@ +import { SymmetricalCommand } from '../CommandBase' +import { AtemState, AtemStateUtil, InvalidIdError } from '../../state' +import type { MultiViewerBorderColorState } from '../../state/settings' + +export class MultiViewerBorderColorCommand extends SymmetricalCommand { + public static readonly rawName = 'MvBC' + + public readonly multiViewerId: number + + constructor(multiviewerId: number, color: MultiViewerBorderColorState) { + super(color) + + this.multiViewerId = multiviewerId + } + + public serialize(): Buffer { + const buffer = Buffer.alloc(12) + buffer.writeUInt8(this.multiViewerId, 0) + buffer.writeUInt16BE(this.properties.red, 2) + buffer.writeUInt16BE(this.properties.green, 4) + buffer.writeUInt16BE(this.properties.blue, 6) + buffer.writeUInt16BE(this.properties.alpha, 8) + return buffer + } + + public static deserialize(rawCommand: Buffer): MultiViewerBorderColorCommand { + const multiViewerId = rawCommand.readUInt8(0) + + const color: MultiViewerBorderColorState = { + red: rawCommand.readUInt16BE(2), + green: rawCommand.readUInt16BE(4), + blue: rawCommand.readUInt16BE(6), + alpha: rawCommand.readUInt16BE(8), + } + + return new MultiViewerBorderColorCommand(multiViewerId, color) + } + + public applyToState(state: AtemState): string { + if (!state.info.multiviewer || this.multiViewerId >= state.info.multiviewer.count) { + throw new InvalidIdError('MultiViewer', this.multiViewerId) + } + + const multiviewer = AtemStateUtil.getMultiViewer(state, this.multiViewerId) + multiviewer.borderColor = { ...this.properties } + + return `settings.multiViewers.${this.multiViewerId}.borderColor` + } +} diff --git a/src/commands/Settings/index.ts b/src/commands/Settings/index.ts index 7b4fb50c1..5b949720a 100644 --- a/src/commands/Settings/index.ts +++ b/src/commands/Settings/index.ts @@ -1,6 +1,7 @@ export * from './MediaPool' -export * from './MultiViewerSourceCommand' +export * from './MultiViewerBorderColorCommand' export * from './MultiViewerPropertiesCommand' +export * from './MultiViewerSourceCommand' export * from './MultiViewerVuOpacityCommand' export * from './MultiViewerWindowVuMeterCommand' export * from './MultiViewerWindowSafeAreaCommand' diff --git a/src/state/settings.ts b/src/state/settings.ts index fbb581fff..b6fb3593e 100644 --- a/src/state/settings.ts +++ b/src/state/settings.ts @@ -16,12 +16,24 @@ export interface MultiViewerPropertiesState { layout: MultiViewerLayout programPreviewSwapped: boolean } +export interface MultiViewerBorderColorState { + /** Red component 0-1000 */ + red: number + /** Green component 0-1000 */ + green: number + /** Blue component 0-1000 */ + blue: number + /** Alpha component 0-1000 */ + alpha: number +} export interface MultiViewer { readonly index: number readonly windows: Array properties?: MultiViewerPropertiesState vuOpacity?: number + + borderColor?: MultiViewerBorderColorState } export interface MediaPool { From 313793d3ebac03ef8403ab8942fac89b2b9c1da9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 26 Sep 2024 21:58:40 +0100 Subject: [PATCH 2/3] feat: multiviewer box overlays --- .../Settings/MultiViewerSourceCommand.ts | 2 +- ...ltiViewerWindowOverlayPropertiesCommand.ts | 79 +++++++++++++++++++ .../MultiViewerWindowSafeAreaTypeCommand.ts | 51 ++++++++++++ src/commands/Settings/index.ts | 4 +- src/enums/index.ts | 5 ++ src/state/settings.ts | 10 ++- 6 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 src/commands/Settings/MultiViewerWindowOverlayPropertiesCommand.ts create mode 100644 src/commands/Settings/MultiViewerWindowSafeAreaTypeCommand.ts diff --git a/src/commands/Settings/MultiViewerSourceCommand.ts b/src/commands/Settings/MultiViewerSourceCommand.ts index 678d87963..5da139b8f 100644 --- a/src/commands/Settings/MultiViewerSourceCommand.ts +++ b/src/commands/Settings/MultiViewerSourceCommand.ts @@ -56,7 +56,7 @@ export class MultiViewerSourceUpdateCommand extends DeserializedCommand { + public static MaskFlags = { + labelVisible: 1 << 0, + borderVisible: 1 << 1, + } + + public static readonly rawName = 'CMvO' + + public readonly multiViewerId: number + public readonly windowIndex: number + + constructor(multiviewerId: number, windowIndex: number) { + super() + + this.multiViewerId = multiviewerId + this.windowIndex = windowIndex + } + + public serialize(): Buffer { + const buffer = Buffer.alloc(8) + buffer.writeUInt8(this.multiViewerId, 0) + buffer.writeUInt8(this.windowIndex || 0, 1) + + let value = 0 + if (this.properties.labelVisible) value |= 0x01 + if (this.properties.borderVisible) value |= 0x02 + + buffer.writeUInt8(value, 3) + buffer.writeUInt8(this.flag, 5) + return buffer + } +} + +export class MultiViewerWindowOverlayPropertiesUpdateCommand extends DeserializedCommand { + public static readonly rawName = 'MvOv' + + public readonly multiViewerId: number + public readonly windowIndex: number + + constructor(multiviewerId: number, windowIndex: number, props: MultiViewerWindowOverlayPropertiesState) { + super(props) + + this.multiViewerId = multiviewerId + this.windowIndex = windowIndex + } + + public static deserialize(rawCommand: Buffer): MultiViewerWindowOverlayPropertiesUpdateCommand { + const multiViewerId = rawCommand.readUInt8(0) + const windowIndex = rawCommand.readUInt8(1) + + const values = rawCommand.readUInt8(3) + + const props: MultiViewerWindowOverlayPropertiesState = { + labelVisible: (values & 0x01) > 0, + borderVisible: (values & 0x02) > 0, + } + + return new MultiViewerWindowOverlayPropertiesUpdateCommand(multiViewerId, windowIndex, props) + } + + public applyToState(state: AtemState): string { + if (!state.info.multiviewer || this.multiViewerId >= state.info.multiviewer.count) { + throw new InvalidIdError('MultiViewer', this.multiViewerId) + } + + const multiviewer = AtemStateUtil.getMultiViewer(state, this.multiViewerId) + const window = multiviewer.windows[this.windowIndex] + if (!window) { + throw new InvalidIdError('MultiViewer Window', this.multiViewerId, this.windowIndex) + } + window.overlayProperties = this.properties + + return `settings.multiViewers.${this.multiViewerId}.windows.${this.windowIndex}.overlayProperties` + } +} diff --git a/src/commands/Settings/MultiViewerWindowSafeAreaTypeCommand.ts b/src/commands/Settings/MultiViewerWindowSafeAreaTypeCommand.ts new file mode 100644 index 000000000..df135af44 --- /dev/null +++ b/src/commands/Settings/MultiViewerWindowSafeAreaTypeCommand.ts @@ -0,0 +1,51 @@ +import { SymmetricalCommand } from '../CommandBase' +import { AtemState, AtemStateUtil, InvalidIdError } from '../../state' +import { SafeTitlePattern } from '../../enums' +import { combineComponents, getComponents } from '../../lib/atemUtil' + +export class MultiViewerWindowOverlaySafeAreaPatternCommand extends SymmetricalCommand<{ + safeTitlePattern: SafeTitlePattern[] +}> { + public static readonly rawName = 'StMw' + + public readonly multiViewerId: number + public readonly windowIndex: number + + constructor(multiviewerId: number, windowIndex: number, safeTitlePattern: SafeTitlePattern[]) { + super({ safeTitlePattern }) + + this.multiViewerId = multiviewerId + this.windowIndex = windowIndex + } + + public serialize(): Buffer { + const buffer = Buffer.alloc(4) + buffer.writeUInt8(this.multiViewerId, 0) + buffer.writeUInt8(this.windowIndex, 1) + buffer.writeUInt8(combineComponents(this.properties.safeTitlePattern), 2) + return buffer + } + + public static deserialize(rawCommand: Buffer): MultiViewerWindowOverlaySafeAreaPatternCommand { + const multiViewerId = rawCommand.readUInt8(0) + const windowIndex = rawCommand.readUInt8(1) + const safeTitlePattern = getComponents(rawCommand.readUInt8(2)) as SafeTitlePattern[] + + return new MultiViewerWindowOverlaySafeAreaPatternCommand(multiViewerId, windowIndex, safeTitlePattern) + } + + public applyToState(state: AtemState): string { + if (!state.info.multiviewer || this.multiViewerId >= state.info.multiviewer.count) { + throw new InvalidIdError('MultiViewer', this.multiViewerId) + } + + const multiviewer = AtemStateUtil.getMultiViewer(state, this.multiViewerId) + const window = multiviewer.windows[this.windowIndex] + if (!window) { + throw new InvalidIdError('MultiViewer Window', this.multiViewerId, this.windowIndex) + } + window.safeTitlePattern = this.properties.safeTitlePattern + + return `settings.multiViewers.${this.multiViewerId}.windows.${this.windowIndex}.safeTitlePattern` + } +} diff --git a/src/commands/Settings/index.ts b/src/commands/Settings/index.ts index 5b949720a..3daed1aa8 100644 --- a/src/commands/Settings/index.ts +++ b/src/commands/Settings/index.ts @@ -3,6 +3,8 @@ export * from './MultiViewerBorderColorCommand' export * from './MultiViewerPropertiesCommand' export * from './MultiViewerSourceCommand' export * from './MultiViewerVuOpacityCommand' -export * from './MultiViewerWindowVuMeterCommand' +export * from './MultiViewerWindowOverlayPropertiesCommand' export * from './MultiViewerWindowSafeAreaCommand' +export * from './MultiViewerWindowSafeAreaTypeCommand' +export * from './MultiViewerWindowVuMeterCommand' export * from './VideoMode' diff --git a/src/enums/index.ts b/src/enums/index.ts index 8904cd531..24e151830 100644 --- a/src/enums/index.ts +++ b/src/enums/index.ts @@ -419,3 +419,8 @@ export enum TimeMode { FreeRun = 0, TimeOfDay = 1, } + +export enum SafeTitlePattern { + Horizontal = 1, + Vertical = 2, +} diff --git a/src/state/settings.ts b/src/state/settings.ts index b6fb3593e..70e5191b0 100644 --- a/src/state/settings.ts +++ b/src/state/settings.ts @@ -1,15 +1,19 @@ -import { VideoMode, MultiViewerLayout, TimeMode } from '../enums' +import { VideoMode, MultiViewerLayout, TimeMode, SafeTitlePattern } from '../enums' export interface MultiViewerSourceState { source: number readonly windowIndex: number readonly supportsVuMeter: boolean readonly supportsSafeArea: boolean + // readonly supportsOverlayProperties: boolean } export interface MultiViewerWindowState extends MultiViewerSourceState { safeTitle?: boolean audioMeter?: boolean + + safeTitlePattern?: SafeTitlePattern[] + overlayProperties?: MultiViewerWindowOverlayPropertiesState } export interface MultiViewerPropertiesState { @@ -26,6 +30,10 @@ export interface MultiViewerBorderColorState { /** Alpha component 0-1000 */ alpha: number } +export interface MultiViewerWindowOverlayPropertiesState { + labelVisible: boolean + borderVisible: boolean +} export interface MultiViewer { readonly index: number From 10b86336082139cf11ffd7fd355aa21572ce54ac Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 26 Sep 2024 22:05:03 +0100 Subject: [PATCH 3/3] chore: fix tests --- src/commands/__tests__/index.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/commands/__tests__/index.spec.ts b/src/commands/__tests__/index.spec.ts index ad0a5ce78..633be5aa5 100644 --- a/src/commands/__tests__/index.spec.ts +++ b/src/commands/__tests__/index.spec.ts @@ -203,7 +203,12 @@ describe('Commands vs LibAtem', () => { !n.startsWith('TlSr') && !n.startsWith('_VMC') && !n.startsWith('FAMS') && - !n.startsWith('CFMS') + !n.startsWith('CFMS') && + // new multiviewer border + !n.startsWith('CMvO') && + !n.startsWith('MvBC') && + !n.startsWith('MvOv') && + !n.startsWith('StMw') ) knownNames.sort()