Skip to content

Commit 0bf0af7

Browse files
author
Balte de Wit
committed
feat: Audio Channels
1 parent b2eaada commit 0bf0af7

File tree

5 files changed

+115
-0
lines changed

5 files changed

+115
-0
lines changed

src/defaults.ts

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { VideoState, Enums } from 'atem-connection'
22
import * as USK from 'atem-connection/dist/state/video/upstreamKeyers'
33
import * as DSK from 'atem-connection/dist/state/video/downstreamKeyers'
4+
import { AudioChannel } from 'atem-connection/dist/state/audio'
45

56
export namespace Defaults {
67
export namespace Video {
@@ -260,4 +261,14 @@ export namespace Defaults {
260261
borderLightSourceAltitude: 0
261262
}
262263
}
264+
265+
export namespace Audio {
266+
export const Channel: AudioChannel = {
267+
sourceType: 0,
268+
portType: 1,
269+
mixOption: 0,
270+
gain: 0,
271+
balance: 0
272+
}
273+
}
263274
}

src/resolvers/__tests__/audio.spec.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { State as StateObject } from '../../'
2+
import * as audio from '../audio'
3+
import { AudioChannel } from 'atem-connection/dist/state/audio'
4+
import { AudioMixerInputCommand } from 'atem-connection/dist/commands'
5+
6+
function literal<T> (o: T) { return o }
7+
8+
const STATE1 = new StateObject()
9+
STATE1.audio.channels = [
10+
literal<AudioChannel>({
11+
sourceType: 0,
12+
portType: 1,
13+
mixOption: 0,
14+
gain: 0,
15+
balance: 0
16+
}),
17+
literal<AudioChannel>({
18+
sourceType: 0,
19+
portType: 1,
20+
mixOption: 0,
21+
gain: 0,
22+
balance: 0
23+
})
24+
]
25+
const STATE2 = new StateObject()
26+
STATE2.audio.channels = JSON.parse(JSON.stringify(STATE1.audio.channels))
27+
28+
test('Unit: audio: same state gives no commands', function () {
29+
// same state gives no commands:
30+
const commands = audio.resolveAudioState(STATE1 as unknown as StateObject, STATE1 as unknown as StateObject)
31+
expect(commands.length).toEqual(0)
32+
})
33+
34+
test('Unit: audio: channel command', function () {
35+
STATE2.audio.channels[0].gain = -192
36+
37+
const commands = audio.resolveAudioState(STATE1 as unknown as StateObject, STATE2 as unknown as StateObject) as Array<AudioMixerInputCommand>
38+
expect(commands.length).toEqual(1)
39+
expect(commands[0].rawName).toEqual('AMIP')
40+
expect(commands[0].index).toEqual(0)
41+
expect(commands[0].properties).toMatchObject({ gain: -192 })
42+
expect(commands[0].flag).toEqual(2) // 010
43+
44+
STATE2.audio.channels[0].gain = 0
45+
})
46+
47+
test('Unit: audio: new channel', function () {
48+
const c = STATE1.audio.channels[1]
49+
delete STATE1.audio.channels[1]
50+
51+
const commands = audio.resolveAudioState(STATE1 as unknown as StateObject, STATE2 as unknown as StateObject) as Array<AudioMixerInputCommand>
52+
expect(commands.length).toEqual(1)
53+
expect(commands[0].rawName).toEqual('AMIP')
54+
expect(commands[0].index).toEqual(1)
55+
expect(commands[0].properties).toMatchObject({ ...STATE2.audio.channels[1] })
56+
expect(commands[0].flag).toEqual(7) // 111
57+
58+
STATE1.audio.channels[1] = c
59+
})

src/resolvers/audio.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Commands as AtemCommands, Commands } from 'atem-connection'
2+
import { State as StateObject } from '../'
3+
import { AudioChannel } from 'atem-connection/dist/state/audio'
4+
import { compareProps } from '../util'
5+
6+
export function resolveAudioState (oldState: StateObject, newState: StateObject): Array<Commands.AbstractCommand> {
7+
let commands: Array<AtemCommands.AbstractCommand> = []
8+
if (!newState.audio || !newState.audio.channels) return commands
9+
10+
for (const index in newState.audio.channels) {
11+
const oldChannel = oldState.audio.channels[index]
12+
const newChannel = newState.audio.channels[index]
13+
let props: Partial<AudioChannel> = {}
14+
15+
if (!newChannel) continue
16+
17+
if (!oldChannel) {
18+
props = newChannel
19+
} else {
20+
props = compareProps(oldChannel, newChannel, ['gain', 'mixOption', 'balance'])
21+
}
22+
23+
if (Object.keys(props).length > 0) {
24+
const command = new Commands.AudioMixerInputCommand()
25+
command.index = Number(index)
26+
command.updateProps(props)
27+
commands.push(command)
28+
}
29+
}
30+
31+
return commands
32+
}

src/resolvers/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { State as StateObject } from '../'
55
import { resolveMixEffectsState } from './mixEffect'
66
import { resolveDownstreamKeyerState } from './downstreamKeyer'
77
import { resolveSupersourceBoxState, resolveSuperSourcePropertiesState } from './supersource'
8+
import { resolveAudioState } from './audio'
89

910
export function videoState (oldState: StateObject, newState: StateObject): Array<AtemCommands.AbstractCommand> {
1011
let commands: Array<AtemCommands.AbstractCommand> = []
@@ -13,6 +14,7 @@ export function videoState (oldState: StateObject, newState: StateObject): Array
1314
commands = commands.concat(resolveDownstreamKeyerState(oldState, newState))
1415
commands = commands.concat(resolveSupersourceBoxState(oldState, newState))
1516
commands = commands.concat(resolveSuperSourcePropertiesState(oldState, newState))
17+
commands = commands.concat(resolveAudioState(oldState, newState))
1618

1719
// resolve auxilliaries:
1820
for (const index in newState.video.auxilliaries) {

src/util.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function compareProps<T> (oldProps: T, newProps: T, props: Array<keyof T>) {
2+
const changedProps: Partial<T> = {}
3+
4+
for (let key of props) {
5+
if ((newProps)[key] !== (oldProps)[key]) {
6+
(changedProps)[key] = (newProps)[key]
7+
}
8+
}
9+
10+
return changedProps
11+
}

0 commit comments

Comments
 (0)