File: ducker.py

package info (click to toggle)
pyatem 0.13.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,776 kB
  • sloc: python: 15,193; xml: 435; ansic: 256; sh: 26; makefile: 20
file content (100 lines) | stat: -rw-r--r-- 3,879 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import argparse
import time

from pyatem.command import SendFairlightLevelsCommand, FairlightStripPropertiesCommand
from pyatem.field import FairlightMeterLevelsField
from pyatem.protocol import AtemProtocol

switcher: AtemProtocol = None

release_timer = time.time()


def on_disconnected():
    print("Hardware has disconnected")
    exit(0)


def run(device, main, sidechain, threshold, mode, release=0.3, ratio=2, duck_level=-30):
    global switcher

    # Parse the channel index, for split channels the format can be 1.1 for the right channel instead of just 1
    if '.' in main:
        main_index, main_sub = map(int, main.split('.'))
        main_strip = main
    else:
        main_index = int(main)
        main_sub = -1
        main_strip = f'{main}.0'

    if '.' in sidechain:
        side_index, side_sub = map(int, sidechain.split('.'))
    else:
        side_index = int(sidechain)
        side_sub = 0

    def on_connected():
        global switcher

        print("Connection successful")
        model = switcher.mixerstate['product-name']
        print(f"Detected hardware: {model.name}")
        fw = switcher.mixerstate['firmware-version']
        print(f"Firmware: {fw}")
        print()

        # Trigger receiving audio channels
        cmd = SendFairlightLevelsCommand(True)
        switcher.send_commands([cmd])

    def on_levels(level: FairlightMeterLevelsField):
        global release_timer
        if level.index == side_index and level.subchannel == side_sub:
            # Grab the output peak level
            val = sum(level.output[0:2]) / 2.0

            if val > threshold:
                gr = val - threshold
                if mode == 'compress':
                    new_vol = -int(gr * 100)
                else:
                    new_vol = duck_level

                if new_vol < switcher.mixerstate['fairlight-strip-properties'][main_strip].volume:
                    cmd = FairlightStripPropertiesCommand(main_index, main_sub, volume=-int(gr * 100))
                    switcher.send_commands([cmd])
                release_timer = time.time()
            elif switcher.mixerstate['fairlight-strip-properties'][main_strip].volume < 0 and (
                    time.time() - release_timer) > release:
                cur = switcher.mixerstate['fairlight-strip-properties'][main_strip].volume
                new_vol = int((cur * 0.9) * 1)
                cmd = FairlightStripPropertiesCommand(main_index, main_sub, volume=new_vol)
                switcher.send_commands([cmd])

    print(f"Connecting to {device}...")
    switcher = AtemProtocol(device)
    switcher.on('connected', on_connected)
    switcher.on('disconnected', on_disconnected)
    switcher.on('change:fairlight-meter-levels:*', on_levels)
    switcher.connect()
    while True:
        switcher.loop()


def main():
    parser = argparse.ArgumentParser(description="Duck channel volume on incoming audio of other channels")
    parser.add_argument('device', help="Device ip address or 'usb'")
    parser.add_argument('main', help='Channel number for the channel that needs to be reduced in level')
    parser.add_argument('sidechain', help='Channel number for the detection channel')
    parser.add_argument('--threshold', type=float, default=-30.0, help='Trigger level on the detection channel')
    parser.add_argument('--mode', choices=['compress', 'duck'], help='Method for selecting the reduction level',
                        default='duck')
    parser.add_argument('--level', type=float, default=-30, help='Target level for duck mode')
    parser.add_argument('--release', type=float, default=300, help='Release time in milliseconds')
    args = parser.parse_args()
    run(args.device, args.main, args.sidechain, args.threshold, mode=args.mode, duck_level=args.level,
        release=args.release / 1000)


if __name__ == '__main__':
    main()