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/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 7b4fb50c1..3daed1aa8 100644 --- a/src/commands/Settings/index.ts +++ b/src/commands/Settings/index.ts @@ -1,7 +1,10 @@ export * from './MediaPool' -export * from './MultiViewerSourceCommand' +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/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() 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 fbb581fff..70e5191b0 100644 --- a/src/state/settings.ts +++ b/src/state/settings.ts @@ -1,27 +1,47 @@ -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 { 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 MultiViewerWindowOverlayPropertiesState { + labelVisible: boolean + borderVisible: boolean +} export interface MultiViewer { readonly index: number readonly windows: Array properties?: MultiViewerPropertiesState vuOpacity?: number + + borderColor?: MultiViewerBorderColorState } export interface MediaPool {