Skip to content
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

feat: multiviewer border and additional box properties #169

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/commands/Settings/MultiViewerBorderColorCommand.ts
Original file line number Diff line number Diff line change
@@ -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<MultiViewerBorderColorState> {
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`
}
}
2 changes: 1 addition & 1 deletion src/commands/Settings/MultiViewerSourceCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class MultiViewerSourceUpdateCommand extends DeserializedCommand<MultiVie
const multiviewer = AtemStateUtil.getMultiViewer(state, this.multiViewerId)
const currentWindow = multiviewer.windows[this.properties.windowIndex]

// The Constellation HD range has a bug where it sends this command for every window on every frame
// The Constellation HD range had a bug where it sends this command for every window on every frame
// This hides that from library consumers by doing a deep diff, when we usually do not.
if (currentWindow && !isRunningInTests()) {
let isChanged = false
Expand Down
79 changes: 79 additions & 0 deletions src/commands/Settings/MultiViewerWindowOverlayPropertiesCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { DeserializedCommand, WritableCommand } from '../CommandBase'
import { MultiViewerWindowOverlayPropertiesState } from '../../state/settings'
import { AtemState, AtemStateUtil, InvalidIdError } from '../../state'

export class MultiViewerWindowOverlayPropertiesCommand extends WritableCommand<MultiViewerWindowOverlayPropertiesState> {
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<MultiViewerWindowOverlayPropertiesState> {
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`
}
}
51 changes: 51 additions & 0 deletions src/commands/Settings/MultiViewerWindowSafeAreaTypeCommand.ts
Original file line number Diff line number Diff line change
@@ -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`
}
}
7 changes: 5 additions & 2 deletions src/commands/Settings/index.ts
Original file line number Diff line number Diff line change
@@ -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'
7 changes: 6 additions & 1 deletion src/commands/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
5 changes: 5 additions & 0 deletions src/enums/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,8 @@ export enum TimeMode {
FreeRun = 0,
TimeOfDay = 1,
}

export enum SafeTitlePattern {
Horizontal = 1,
Vertical = 2,
}
22 changes: 21 additions & 1 deletion src/state/settings.ts
Original file line number Diff line number Diff line change
@@ -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<MultiViewerWindowState | undefined>
properties?: MultiViewerPropertiesState
vuOpacity?: number

borderColor?: MultiViewerBorderColorState
}

export interface MediaPool {
Expand Down