make changes to sockets.

replace black+isort with ruff

upd examples
This commit is contained in:
Onyx and Iris 2025-01-17 02:51:17 +00:00
parent dad5ee9e9d
commit 16df0d559e
27 changed files with 786 additions and 695 deletions

View File

@ -1,8 +1,7 @@
[![PyPI version](https://badge.fury.io/py/vban-cmd.svg)](https://badge.fury.io/py/vban-cmd) [![PyPI version](https://badge.fury.io/py/vban-cmd.svg)](https://badge.fury.io/py/vban-cmd)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/vban-cmd-python/blob/dev/LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/vban-cmd-python/blob/dev/LICENSE)
[![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
![Tests Status](./tests/basic.svg?dummy=8484744) ![Tests Status](./tests/basic.svg?dummy=8484744)
![Tests Status](./tests/banana.svg?dummy=8484744) ![Tests Status](./tests/banana.svg?dummy=8484744)
![Tests Status](./tests/potato.svg?dummy=8484744) ![Tests Status](./tests/potato.svg?dummy=8484744)

View File

@ -6,27 +6,27 @@ class ManyThings:
self.vban = vban self.vban = vban
def things(self): def things(self):
self.vban.strip[0].label = "podmic" self.vban.strip[0].label = 'podmic'
self.vban.strip[0].mute = True self.vban.strip[0].mute = True
print( print(
f"strip 0 ({self.vban.strip[0].label}) mute has been set to {self.vban.strip[0].mute}" f'strip 0 ({self.vban.strip[0].label}) mute has been set to {self.vban.strip[0].mute}'
) )
def other_things(self): def other_things(self):
self.vban.bus[3].gain = -6.3 self.vban.bus[3].gain = -6.3
self.vban.bus[4].eq = True self.vban.bus[4].eq = True
info = ( info = (
f"bus 3 gain has been set to {self.vban.bus[3].gain}", f'bus 3 gain has been set to {self.vban.bus[3].gain}',
f"bus 4 eq has been set to {self.vban.bus[4].eq}", f'bus 4 eq has been set to {self.vban.bus[4].eq}',
) )
print("\n".join(info)) print('\n'.join(info))
def main(): def main():
kind_id = "banana" kind_id = 'banana'
with vban_cmd.api( with vban_cmd.api(
kind_id, ip="gamepc.local", port=6980, streamname="Command1" kind_id, ip='gamepc.local', port=6980, streamname='Command1'
) as vban: ) as vban:
do = ManyThings(vban) do = ManyThings(vban)
do.things() do.things()
@ -35,12 +35,12 @@ def main():
# set many parameters at once # set many parameters at once
vban.apply( vban.apply(
{ {
"strip-2": {"A1": True, "B1": True, "gain": -6.0}, 'strip-2': {'A1': True, 'B1': True, 'gain': -6.0},
"bus-2": {"mute": True}, 'bus-2': {'mute': True},
"vban-in-0": {"on": True}, 'vban-in-0': {'on': True},
} }
) )
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@ -1,10 +1,10 @@
import logging import logging
import tkinter as tk
from tkinter import ttk
import vban_cmd import vban_cmd
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
import tkinter as tk
from tkinter import ttk
class App(tk.Tk): class App(tk.Tk):
@ -13,7 +13,7 @@ class App(tk.Tk):
def __init__(self, vban): def __init__(self, vban):
super().__init__() super().__init__()
self.vban = vban self.vban = vban
self.title(f"{vban} - version {vban.version}") self.title(f'{vban} - version {vban.version}')
self.vban.observer.add(self.on_ldirty) self.vban.observer.add(self.on_ldirty)
# create widget variables # create widget variables
@ -24,10 +24,10 @@ class App(tk.Tk):
# initialize style table # initialize style table
self.style = ttk.Style() self.style = ttk.Style()
self.style.theme_use("clam") self.style.theme_use('clam')
self.style.configure( self.style.configure(
"Mute.TButton", 'Mute.TButton',
foreground="#cd5c5c" if vban.strip[self.INDEX].mute else "#5a5a5a", foreground='#cd5c5c' if vban.strip[self.INDEX].mute else '#5a5a5a',
) )
# create labelframe and grid it onto the mainframe # create labelframe and grid it onto the mainframe
@ -39,7 +39,7 @@ class App(tk.Tk):
self.labelframe, self.labelframe,
from_=12, from_=12,
to_=-60, to_=-60,
orient="vertical", orient='vertical',
variable=self.slider_var, variable=self.slider_var,
command=lambda arg: self.on_slider_move(arg), command=lambda arg: self.on_slider_move(arg),
) )
@ -47,15 +47,15 @@ class App(tk.Tk):
column=0, column=0,
row=0, row=0,
) )
slider.bind("<Double-Button-1>", self.on_button_double_click) slider.bind('<Double-Button-1>', self.on_button_double_click)
# create level meter and grid it onto the labelframe # create level meter and grid it onto the labelframe
level_meter = ttk.Progressbar( level_meter = ttk.Progressbar(
self.labelframe, self.labelframe,
orient="vertical", orient='vertical',
variable=self.meter_var, variable=self.meter_var,
maximum=72, maximum=72,
mode="determinate", mode='determinate',
) )
level_meter.grid(column=1, row=0) level_meter.grid(column=1, row=0)
@ -66,8 +66,8 @@ class App(tk.Tk):
# create button and grid it onto the labelframe # create button and grid it onto the labelframe
button = ttk.Button( button = ttk.Button(
self.labelframe, self.labelframe,
text="Mute", text='Mute',
style="Mute.TButton", style='Mute.TButton',
command=lambda: self.on_button_press(), command=lambda: self.on_button_press(),
) )
button.grid(column=0, row=2, columnspan=2, padx=1, pady=2) button.grid(column=0, row=2, columnspan=2, padx=1, pady=2)
@ -83,7 +83,7 @@ class App(tk.Tk):
self.button_var.set(not self.button_var.get()) self.button_var.set(not self.button_var.get())
self.vban.strip[self.INDEX].mute = self.button_var.get() self.vban.strip[self.INDEX].mute = self.button_var.get()
self.style.configure( self.style.configure(
"Mute.TButton", foreground="#cd5c5c" if self.button_var.get() else "#5a5a5a" 'Mute.TButton', foreground='#cd5c5c' if self.button_var.get() else '#5a5a5a'
) )
def on_button_double_click(self, e): def on_button_double_click(self, e):
@ -100,10 +100,10 @@ class App(tk.Tk):
def main(): def main():
with vban_cmd.api("banana", ldirty=True) as vban: with vban_cmd.api('banana', ldirty=True) as vban:
app = App(vban) app = App(vban)
app.mainloop() app.mainloop()
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@ -1,4 +1,4 @@
import time import threading
from logging import config from logging import config
import obsws_python as obsws import obsws_python as obsws
@ -7,85 +7,98 @@ import vban_cmd
config.dictConfig( config.dictConfig(
{ {
"version": 1, 'version': 1,
"formatters": { 'formatters': {
"standard": { 'standard': {
"format": "%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s" 'format': '%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s'
} }
}, },
"handlers": { 'handlers': {
"stream": { 'stream': {
"level": "DEBUG", 'level': 'DEBUG',
"class": "logging.StreamHandler", 'class': 'logging.StreamHandler',
"formatter": "standard", 'formatter': 'standard',
} }
}, },
"loggers": {"vban_cmd.iremote": {"handlers": ["stream"], "level": "DEBUG"}}, 'loggers': {
'vban_cmd.iremote': {
'handlers': ['stream'],
'level': 'DEBUG',
'propagate': False,
}
},
'root': {'handlers': ['stream'], 'level': 'WARNING'},
} }
) )
class Observer: class Observer:
def __init__(self, vban): def __init__(self, vban, stop_event):
self.vban = vban self._vban = vban
self.client = obsws.EventClient() self._stop_event = stop_event
self.client.callback.register( self._client = obsws.EventClient()
self._client.callback.register(
( (
self.on_current_program_scene_changed, self.on_current_program_scene_changed,
self.on_exit_started, self.on_exit_started,
) )
) )
self.is_running = True
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
self._client.disconnect()
def on_start(self): def on_start(self):
self.vban.strip[0].mute = True self._vban.strip[0].mute = True
self.vban.strip[1].B1 = True self._vban.strip[1].B1 = True
self.vban.strip[2].B2 = True self._vban.strip[2].B2 = True
def on_brb(self): def on_brb(self):
self.vban.strip[7].fadeto(0, 500) self._vban.strip[7].fadeto(0, 500)
self.vban.bus[0].mute = True self._vban.bus[0].mute = True
def on_end(self): def on_end(self):
self.vban.apply( self._vban.apply(
{ {
"strip-0": {"mute": True}, 'strip-0': {'mute': True},
"strip-1": {"mute": True, "B1": False}, 'strip-1': {'mute': True, 'B1': False},
"strip-2": {"mute": True, "B1": False}, 'strip-2': {'mute': True, 'B1': False},
} }
) )
def on_live(self): def on_live(self):
self.vban.strip[0].mute = False self._vban.strip[0].mute = False
self.vban.strip[7].fadeto(-6, 500) self._vban.strip[7].fadeto(-6, 500)
self.vban.strip[7].A3 = True self._vban.strip[7].A3 = True
def on_current_program_scene_changed(self, data): def on_current_program_scene_changed(self, data):
def fget(scene):
run = {
"START": self.on_start,
"BRB": self.on_brb,
"END": self.on_end,
"LIVE": self.on_live,
}
return run.get(scene)
scene = data.scene_name scene = data.scene_name
print(f"Switched to scene {scene}") print(f'Switched to scene {scene}')
if fn := fget(scene): match scene:
fn() case 'START':
self.on_start()
case 'BRB':
self.on_brb()
case 'END':
self.on_end()
case 'LIVE':
self.on_live()
def on_exit_started(self, _): def on_exit_started(self, _):
self.client.unsubscribe() self._stop_event.set()
self.is_running = False
def main(): def main():
with vban_cmd.api("potato") as vban: KIND_ID = 'potato'
observer = Observer(vban)
while observer.is_running: with vban_cmd.api(KIND_ID) as vban:
time.sleep(0.1) stop_event = threading.Event()
with Observer(vban, stop_event):
stop_event.wait()
if __name__ == "__main__": if __name__ == '__main__':
main() main()

View File

@ -1,7 +1,7 @@
from setuptools import setup from setuptools import setup
setup( setup(
name="obs", name='obs',
description="OBS Example", description='OBS Example',
install_requires=["obsws-python"], install_requires=['obsws-python'],
) )

View File

@ -13,23 +13,23 @@ class App:
# define an 'on_update' callback function to receive event updates # define an 'on_update' callback function to receive event updates
def on_update(self, event): def on_update(self, event):
if event == "pdirty": if event == 'pdirty':
print("pdirty!") print('pdirty!')
elif event == "ldirty": elif event == 'ldirty':
for bus in self.vban.bus: for bus in self.vban.bus:
if bus.levels.isdirty: if bus.levels.isdirty:
print(bus, bus.levels.all) print(bus, bus.levels.all)
def main(): def main():
KIND_ID = "banana" KIND_ID = 'banana'
with vban_cmd.api(KIND_ID, pdirty=True, ldirty=True) as vban: with vban_cmd.api(KIND_ID, pdirty=True, ldirty=True) as vban:
App(vban) App(vban)
while cmd := input("Press <Enter> to exit\n"): while _ := input('Press <Enter> to exit\n'):
pass pass
if __name__ == "__main__": if __name__ == '__main__':
main() main()

360
poetry.lock generated
View File

@ -1,93 +1,36 @@
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]]
name = "black"
version = "24.3.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"},
{file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"},
{file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"},
{file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"},
{file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"},
{file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"},
{file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"},
{file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"},
{file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"},
{file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"},
{file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"},
{file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"},
{file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"},
{file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"},
{file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"},
{file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"},
{file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"},
{file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"},
{file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"},
{file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"},
{file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"},
{file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]] [[package]]
name = "cachetools" name = "cachetools"
version = "5.3.1" version = "5.5.0"
description = "Extensible memoizing collections and decorators" description = "Extensible memoizing collections and decorators"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "cachetools-5.3.1-py3-none-any.whl", hash = "sha256:95ef631eeaea14ba2e36f06437f36463aac3a096799e876ee55e5cdccb102590"}, {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"},
{file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"},
] ]
[[package]] [[package]]
name = "chardet" name = "chardet"
version = "5.1.0" version = "5.2.0"
description = "Universal encoding detector for Python 3" description = "Universal encoding detector for Python 3"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "chardet-5.1.0-py3-none-any.whl", hash = "sha256:362777fb014af596ad31334fde1e8c327dfdb076e1960d1694662d46a6917ab9"}, {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
{file = "chardet-5.1.0.tar.gz", hash = "sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
] ]
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"]
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@ -99,6 +42,7 @@ version = "0.3.9"
description = "Distribution utilities" description = "Distribution utilities"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["dev"]
files = [ files = [
{file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"},
{file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"},
@ -106,13 +50,15 @@ files = [
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.2.0" version = "1.2.2"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"]
markers = "python_version < \"3.11\""
files = [ files = [
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
] ]
[package.extras] [package.extras]
@ -120,78 +66,43 @@ test = ["pytest (>=6)"]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.12.2" version = "3.16.1"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
{file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
] ]
[package.extras] [package.extras]
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
typing = ["typing-extensions (>=4.12.2)"]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "1.1.1" version = "2.0.0"
description = "iniconfig: brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
optional = false optional = false
python-versions = "*" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "isort"
version = "5.10.1"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.6.1,<4.0"
files = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
[package.extras]
colors = ["colorama (>=0.4.3,<0.5.0)"]
pipfile-deprecated-finder = ["pipreqs", "requirementslib"]
plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
optional = false
python-versions = "*"
files = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
] ]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "23.1" version = "24.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
name = "pathspec"
version = "0.10.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
{file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
] ]
[[package]] [[package]]
@ -200,6 +111,7 @@ version = "4.3.6"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
@ -212,47 +124,62 @@ type = ["mypy (>=1.11.2)"]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.0.0" version = "1.5.0"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
] ]
[package.extras] [package.extras]
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"] testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pyenv-inspect"
version = "0.4.0"
description = "An auxiliary library for the virtualenv-pyenv and tox-pyenv-redux plugins"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pyenv-inspect-0.4.0.tar.gz", hash = "sha256:ec429d1d81b67ab0b08a0408414722a79d24fd1845a5b264267e44e19d8d60f0"},
{file = "pyenv_inspect-0.4.0-py3-none-any.whl", hash = "sha256:618683ae7d3e6db14778d58aa0fc6b3170180d944669b5d35a8aa4fb7db550d2"},
]
[[package]] [[package]]
name = "pyproject-api" name = "pyproject-api"
version = "1.5.2" version = "1.8.0"
description = "API to interact with the python pyproject.toml based projects" description = "API to interact with the python pyproject.toml based projects"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pyproject_api-1.5.2-py3-none-any.whl", hash = "sha256:9cffcbfb64190f207444d7579d315f3278f2c04ba46d685fad93197b5326d348"}, {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"},
{file = "pyproject_api-1.5.2.tar.gz", hash = "sha256:999f58fa3c92b23ebd31a6bad5d1f87d456744d75e05391be7f5c729015d3d91"}, {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"},
] ]
[package.dependencies] [package.dependencies]
packaging = ">=23.1" packaging = ">=24.1"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] docs = ["furo (>=2024.8.6)", "sphinx-autodoc-typehints (>=2.4.1)"]
testing = ["covdefaults (>=2.3)", "importlib-metadata (>=6.6)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "setuptools (>=67.8)", "wheel (>=0.40)"] testing = ["covdefaults (>=2.3)", "pytest (>=8.3.3)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "setuptools (>=75.1)"]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.4.4" version = "8.3.4"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
] ]
[package.dependencies] [package.dependencies]
@ -260,98 +187,149 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
iniconfig = "*" iniconfig = "*"
packaging = "*" packaging = "*"
pluggy = ">=0.12,<2.0" pluggy = ">=1.5,<2"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} tomli = {version = ">=1", markers = "python_version < \"3.11\""}
[package.extras] [package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]] [[package]]
name = "pytest-randomly" name = "pytest-randomly"
version = "3.12.0" version = "3.16.0"
description = "Pytest plugin to randomly order tests and control random.seed." description = "Pytest plugin to randomly order tests and control random.seed."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.9"
groups = ["dev"]
files = [ files = [
{file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, {file = "pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6"},
{file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, {file = "pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26"},
] ]
[package.dependencies] [package.dependencies]
pytest = "*" pytest = "*"
[[package]] [[package]]
name = "pytest-repeat" name = "ruff"
version = "0.9.1" version = "0.9.2"
description = "pytest plugin for repeating tests" description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "pytest-repeat-0.9.1.tar.gz", hash = "sha256:5cd3289745ab3156d43eb9c8e7f7d00a926f3ae5c9cf425bec649b2fe15bad5b"}, {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"},
{file = "pytest_repeat-0.9.1-py2.py3-none-any.whl", hash = "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e"}, {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"},
{file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"},
{file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"},
{file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"},
{file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"},
{file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"},
{file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"},
{file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"},
] ]
[package.dependencies]
pytest = ">=3.6"
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.1" version = "2.2.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["main", "dev"]
markers = "python_version < \"3.11\""
files = [ files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
] ]
[[package]] [[package]]
name = "tox" name = "tox"
version = "4.6.3" version = "4.23.2"
description = "tox is a generic virtualenv management and test command line tool" description = "tox is a generic virtualenv management and test command line tool"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "tox-4.6.3-py3-none-any.whl", hash = "sha256:2946a0bb38924c3a9f9575c7fb4ca1f4c11a7c69c61592f176778892155cb50c"}, {file = "tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"},
{file = "tox-4.6.3.tar.gz", hash = "sha256:9e2c5091a117d03b583c57c4c40aecd068099c17d40520e7b165e85c19334534"}, {file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"},
] ]
[package.dependencies] [package.dependencies]
cachetools = ">=5.3.1" cachetools = ">=5.5"
chardet = ">=5.1" chardet = ">=5.2"
colorama = ">=0.4.6" colorama = ">=0.4.6"
filelock = ">=3.12.2" filelock = ">=3.16.1"
packaging = ">=23.1" packaging = ">=24.1"
platformdirs = ">=3.5.3" platformdirs = ">=4.3.6"
pluggy = ">=1" pluggy = ">=1.5"
pyproject-api = ">=1.5.2" pyproject-api = ">=1.8"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
virtualenv = ">=20.23.1" typing-extensions = {version = ">=4.12.2", markers = "python_version < \"3.11\""}
virtualenv = ">=20.26.6"
[package.extras] [package.extras]
docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.2,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"]
testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest (>=7.3.2)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.10.0" version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+" description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version < \"3.11\""
files = [ files = [
{file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
] ]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.26.6" version = "20.29.0"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.29.0-py3-none-any.whl", hash = "sha256:c12311863497992dc4b8644f8ea82d3b35bb7ef8ee82e6630d76d0197c39baf9"},
{file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, {file = "virtualenv-20.29.0.tar.gz", hash = "sha256:6345e1ff19d4b1296954cee076baaf58ff2a12a84a338c62b02eda39f20aa982"},
] ]
[package.dependencies] [package.dependencies]
@ -363,7 +341,23 @@ platformdirs = ">=3.9.1,<5"
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]]
name = "virtualenv-pyenv"
version = "0.5.0"
description = "A virtualenv Python discovery plugin for pyenv-installed interpreters"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "virtualenv-pyenv-0.5.0.tar.gz", hash = "sha256:7b0e5fe3dfbdf484f4cf9b01e1f98111e398db6942237910f666356e6293597f"},
{file = "virtualenv_pyenv-0.5.0-py3-none-any.whl", hash = "sha256:21750247e36c55b3c547cfdeb08f51a3867fe7129922991a4f9c96980c0a4a5d"},
]
[package.dependencies]
pyenv-inspect = ">=0.4,<0.5"
virtualenv = "*"
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.1"
python-versions = "^3.10" python-versions = ">=3.10"
content-hash = "a42b70c2ebaa214c9447f0bfc42a75a09e63066211668202a29c7dc6c40aaab9" content-hash = "13fc9d0eb15d5fc09b54c1c8cd8f528b260259e97ee6813b50ab4724c35d6677"

View File

@ -1,36 +1,39 @@
[tool.poetry] [project]
name = "vban-cmd" name = "vban-cmd"
version = "2.4.12" version = "2.5.0"
description = "Python interface for the VBAN RT Packet Service (Sendtext)" description = "Python interface for the VBAN RT Packet Service (Sendtext)"
authors = ["onyx-and-iris <code@onyxandiris.online>"] authors = [
license = "MIT" {name = "Onyx and Iris",email = "code@onyxandiris.online"}
]
license = {text = "MIT"}
readme = "README.md" readme = "README.md"
repository = "https://github.com/onyx-and-iris/vban-cmd-python" requires-python = ">=3.10"
dependencies = [
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
]
[tool.poetry.dependencies] [tool.poetry.requires-plugins]
python = "^3.10" poethepoet = "^0.32.1"
tomli = { version = "^2.0.1", python = "<3.11" }
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
pytest = "^7.4.4" pytest = "^8.3.4"
pytest-randomly = "^3.12.0" pytest-randomly = "^3.16.0"
pytest-repeat = "^0.9.1" ruff = "^0.9.2"
black = ">=22.3,<25.0" tox = "^4.23.2"
isort = "^5.10.1" virtualenv-pyenv = "^0.5.0"
tox = "^4.6.3"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts] [tool.poe.tasks]
gui = "scripts:ex_gui" gui.script = "scripts:ex_gui"
obs = "scripts:ex_obs" obs.script = "scripts:ex_obs"
observer = "scripts:ex_observer" observer.script = "scripts:ex_observer"
basic = "scripts:test_basic" basic.script = "scripts:test_basic"
banana = "scripts:test_banana" banana.script = "scripts:test_banana"
potato = "scripts:test_potato" potato.script = "scripts:test_potato"
all = "scripts:test_all" all.script = "scripts:test_all"
[tool.tox] [tool.tox]
legacy_tox_ini = """ legacy_tox_ini = """
@ -38,8 +41,101 @@ legacy_tox_ini = """
envlist = py310,py311,py312 envlist = py310,py311,py312
[testenv] [testenv]
setenv = VIRTUALENV_DISCOVERY=pyenv
allowlist_externals = poetry allowlist_externals = poetry
commands = commands =
poetry install -v poetry install -v
poetry run pytest tests/ poetry run pytest tests/
[testenv:obs]
setenv = VIRTUALENV_DISCOVERY=pyenv
allowlist_externals = poetry
deps = obsws-python
commands =
poetry install -v --without dev
poetry run python examples/obs/
""" """
[tool.ruff]
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".mypy_cache",
".nox",
".pants.d",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"venv",
]
# Same as Black.
line-length = 88
indent-width = 4
# Assume Python 3.10
target-version = "py310"
[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["E4", "E7", "E9", "F"]
ignore = []
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format]
# Unlike Black, use single quotes for strings.
quote-style = "single"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.ruff.lint.per-file-ignores]
"__init__.py" = [
"E402",
"F401",
]

View File

@ -5,33 +5,32 @@ from pathlib import Path
def ex_gui(): def ex_gui():
scriptpath = Path.cwd() / "examples" / "gui" / "." scriptpath = Path.cwd() / 'examples' / 'gui' / '.'
subprocess.run([sys.executable, str(scriptpath)]) subprocess.run([sys.executable, str(scriptpath)])
def ex_obs(): def ex_obs():
scriptpath = Path.cwd() / "examples" / "obs" / "." subprocess.run(['tox', 'r', '-e', 'obs'])
subprocess.run([sys.executable, str(scriptpath)])
def ex_observer(): def ex_observer():
scriptpath = Path.cwd() / "examples" / "observer" / "." scriptpath = Path.cwd() / 'examples' / 'observer' / '.'
subprocess.run([sys.executable, str(scriptpath)]) subprocess.run([sys.executable, str(scriptpath)])
def test_basic(): def test_basic():
os.environ["KIND"] = "basic" os.environ['KIND'] = 'basic'
subprocess.run(["tox"]) subprocess.run(['tox'])
def test_banana(): def test_banana():
os.environ["KIND"] = "banana" os.environ['KIND'] = 'banana'
subprocess.run(["tox"]) subprocess.run(['tox'])
def test_potato(): def test_potato():
os.environ["KIND"] = "potato" os.environ['KIND'] = 'potato'
subprocess.run(["tox"]) subprocess.run(['tox'])
def test_all(): def test_all():

View File

@ -9,14 +9,14 @@ from vban_cmd.kinds import request_kind_map as kindmap
# get KIND_ID from env var, otherwise set to random # get KIND_ID from env var, otherwise set to random
KIND_ID = os.environ.get( KIND_ID = os.environ.get(
"KIND", random.choice(tuple(kind_id.name.lower() for kind_id in KindId)) 'KIND', random.choice(tuple(kind_id.name.lower() for kind_id in KindId))
) )
opts = { opts = {
"ip": "testing.local", 'ip': 'ws.local',
"streamname": "testing", 'streamname': 'workstation',
"port": 6990, 'port': 6980,
"bps": 0, 'bps': 0,
} }
vban = vban_cmd.api(KIND_ID, **opts) vban = vban_cmd.api(KIND_ID, **opts)
@ -42,7 +42,7 @@ data = Data()
def setup_module(): def setup_module():
print(f"\nRunning tests for kind [{data.name}]\n", file=sys.stdout) print(f'\nRunning tests for kind [{data.name}]\n', file=sys.stdout)
vban.login() vban.login()
vban.command.reset() vban.command.reset()

View File

@ -1,3 +1,3 @@
from .factory import request_vbancmd_obj as api from .factory import request_vbancmd_obj as api
__ALL__ = ["api"] __ALL__ = ['api']

View File

@ -7,8 +7,8 @@ from .iremote import IRemote
from .meta import bus_mode_prop, channel_bool_prop, channel_label_prop from .meta import bus_mode_prop, channel_bool_prop, channel_label_prop
BusModes = IntEnum( BusModes = IntEnum(
"BusModes", 'BusModes',
"normal amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly", 'normal amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly',
start=0, start=0,
) )
@ -26,7 +26,7 @@ class Bus(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"bus[{self.index}]" return f'bus[{self.index}]'
@property @property
def gain(self) -> float: def gain(self) -> float:
@ -36,19 +36,19 @@ class Bus(IRemote):
return val * 0.01 return val * 0.01
return (((1 << 16) - 1) - val) * -0.01 return (((1 << 16) - 1) - val) * -0.01
val = self.getter("gain") val = self.getter('gain')
return round(val if val else fget(), 1) return round(val if val else fget(), 1)
@gain.setter @gain.setter
def gain(self, val: float): def gain(self, val: float):
self.setter("gain", val) self.setter('gain', val)
def fadeto(self, target: float, time_: int): def fadeto(self, target: float, time_: int):
self.setter("FadeTo", f"({target}, {time_})") self.setter('FadeTo', f'({target}, {time_})')
time.sleep(self._remote.DELAY) time.sleep(self._remote.DELAY)
def fadeby(self, change: float, time_: int): def fadeby(self, change: float, time_: int):
self.setter("FadeBy", f"({change}, {time_})") self.setter('FadeBy', f'({change}, {time_})')
time.sleep(self._remote.DELAY) time.sleep(self._remote.DELAY)
@ -56,22 +56,22 @@ class BusEQ(IRemote):
@classmethod @classmethod
def make(cls, remote, index): def make(cls, remote, index):
BUSEQ_cls = type( BUSEQ_cls = type(
f"BusEQ{remote.kind}", f'BusEQ{remote.kind}',
(cls,), (cls,),
{ {
**{param: channel_bool_prop(param) for param in ["on", "ab"]}, **{param: channel_bool_prop(param) for param in ['on', 'ab']},
}, },
) )
return BUSEQ_cls(remote, index) return BUSEQ_cls(remote, index)
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"bus[{self.index}].eq" return f'bus[{self.index}].eq'
class PhysicalBus(Bus): class PhysicalBus(Bus):
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self.index}" return f'{type(self).__name__}{self.index}'
@property @property
def device(self) -> str: def device(self) -> str:
@ -84,7 +84,7 @@ class PhysicalBus(Bus):
class VirtualBus(Bus): class VirtualBus(Bus):
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self.index}" return f'{type(self).__name__}{self.index}'
class BusLevel(IRemote): class BusLevel(IRemote):
@ -105,7 +105,7 @@ class BusLevel(IRemote):
if not self._remote.stopped() and self._remote.event.ldirty: if not self._remote.stopped() and self._remote.event.ldirty:
return tuple( return tuple(
fget(i) fget(i)
for i in self._remote.cache["bus_level"][self.range[0] : self.range[-1]] for i in self._remote.cache['bus_level'][self.range[0] : self.range[-1]]
) )
return tuple( return tuple(
fget(i) fget(i)
@ -116,7 +116,7 @@ class BusLevel(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"bus[{self.index}]" return f'bus[{self.index}]'
@property @property
def all(self) -> tuple: def all(self) -> tuple:
@ -138,26 +138,26 @@ def _make_bus_mode_mixin():
"""Creates a mixin of Bus Modes.""" """Creates a mixin of Bus Modes."""
modestates = { modestates = {
"normal": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'normal': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"amix": [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1], 'amix': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
"repeat": [0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2], 'repeat': [0, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2],
"bmix": [1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3], 'bmix': [1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3],
"composite": [0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0], 'composite': [0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0],
"tvmix": [1, 0, 1, 4, 5, 4, 5, 0, 1, 0, 1], 'tvmix': [1, 0, 1, 4, 5, 4, 5, 0, 1, 0, 1],
"upmix21": [0, 2, 2, 4, 4, 6, 6, 0, 0, 2, 2], 'upmix21': [0, 2, 2, 4, 4, 6, 6, 0, 0, 2, 2],
"upmix41": [1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3], 'upmix41': [1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3],
"upmix61": [0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8], 'upmix61': [0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8],
"centeronly": [1, 0, 1, 0, 1, 0, 1, 8, 9, 8, 9], 'centeronly': [1, 0, 1, 0, 1, 0, 1, 8, 9, 8, 9],
"lfeonly": [0, 2, 2, 0, 0, 2, 2, 8, 8, 10, 10], 'lfeonly': [0, 2, 2, 0, 0, 2, 2, 8, 8, 10, 10],
"rearonly": [1, 2, 3, 0, 1, 2, 3, 8, 9, 10, 11], 'rearonly': [1, 2, 3, 0, 1, 2, 3, 8, 9, 10, 11],
} }
def identifier(self) -> str: def identifier(self) -> str:
return f"bus[{self.index}].mode" return f'bus[{self.index}].mode'
def get(self): def get(self):
states = [ states = [
(int.from_bytes(self.public_packet.busstate[self.index], "little") & val) (int.from_bytes(self.public_packet.busstate[self.index], 'little') & val)
>> 4 >> 4
for val in self._modes.modevals for val in self._modes.modevals
] ]
@ -166,13 +166,13 @@ def _make_bus_mode_mixin():
return k return k
return type( return type(
"BusModeMixin", 'BusModeMixin',
(IRemote,), (IRemote,),
{ {
"identifier": property(identifier), 'identifier': property(identifier),
"modestates": modestates, 'modestates': modestates,
**{mode.name: bus_mode_prop(mode.name) for mode in BusModes}, **{mode.name: bus_mode_prop(mode.name) for mode in BusModes},
"get": get, 'get': get,
}, },
) )
@ -186,14 +186,14 @@ def bus_factory(phys_bus, remote, i) -> Union[PhysicalBus, VirtualBus]:
BUS_cls = PhysicalBus if phys_bus else VirtualBus BUS_cls = PhysicalBus if phys_bus else VirtualBus
BUSMODEMIXIN_cls = _make_bus_mode_mixin() BUSMODEMIXIN_cls = _make_bus_mode_mixin()
return type( return type(
f"{BUS_cls.__name__}{remote.kind}", f'{BUS_cls.__name__}{remote.kind}',
(BUS_cls,), (BUS_cls,),
{ {
"eq": BusEQ.make(remote, i), 'eq': BusEQ.make(remote, i),
"levels": BusLevel(remote, i), 'levels': BusLevel(remote, i),
"mode": BUSMODEMIXIN_cls(remote, i), 'mode': BUSMODEMIXIN_cls(remote, i),
**{param: channel_bool_prop(param) for param in ["mute", "mono"]}, **{param: channel_bool_prop(param) for param in ['mute', 'mono']},
"label": channel_label_prop(), 'label': channel_label_prop(),
}, },
)(remote, i) )(remote, i)

View File

@ -17,30 +17,30 @@ class Command(IRemote):
Returns a Command class of a kind. Returns a Command class of a kind.
""" """
CMD_cls = type( CMD_cls = type(
f"Command{remote.kind}", f'Command{remote.kind}',
(cls,), (cls,),
{ {
**{ **{
param: action_fn(param) for param in ["show", "shutdown", "restart"] param: action_fn(param) for param in ['show', 'shutdown', 'restart']
}, },
"hide": action_fn("show", val=0), 'hide': action_fn('show', val=0),
}, },
) )
return CMD_cls(remote) return CMD_cls(remote)
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return "command" return 'command'
def set_showvbanchat(self, val: bool): def set_showvbanchat(self, val: bool):
self.setter("DialogShow.VBANCHAT", 1 if val else 0) self.setter('DialogShow.VBANCHAT', 1 if val else 0)
showvbanchat = property(fset=set_showvbanchat) showvbanchat = property(fset=set_showvbanchat)
def set_lock(self, val: bool): def set_lock(self, val: bool):
self.setter("lock", 1 if val else 0) self.setter('lock', 1 if val else 0)
lock = property(fset=set_lock) lock = property(fset=set_lock)
def reset(self): def reset(self):
self._remote.apply_config("reset") self._remote.apply_config('reset')

View File

@ -20,73 +20,73 @@ class TOMLStrBuilder:
def __init__(self, kind): def __init__(self, kind):
self.kind = kind self.kind = kind
self.higher = itertools.chain( self.higher = itertools.chain(
[f"strip-{i}" for i in range(kind.num_strip)], [f'strip-{i}' for i in range(kind.num_strip)],
[f"bus-{i}" for i in range(kind.num_bus)], [f'bus-{i}' for i in range(kind.num_bus)],
) )
def init_config(self, profile=None): def init_config(self, profile=None):
self.virt_strip_params = ( self.virt_strip_params = (
[ [
"mute = false", 'mute = false',
"mono = false", 'mono = false',
"solo = false", 'solo = false',
"gain = 0.0", 'gain = 0.0',
] ]
+ [f"A{i} = false" for i in range(1, self.kind.phys_out + 1)] + [f'A{i} = false' for i in range(1, self.kind.phys_out + 1)]
+ [f"B{i} = false" for i in range(1, self.kind.virt_out + 1)] + [f'B{i} = false' for i in range(1, self.kind.virt_out + 1)]
) )
self.phys_strip_params = self.virt_strip_params + [ self.phys_strip_params = self.virt_strip_params + [
"comp.knob = 0.0", 'comp.knob = 0.0',
"gate.knob = 0.0", 'gate.knob = 0.0',
"denoiser.knob = 0.0", 'denoiser.knob = 0.0',
"eq.on = false", 'eq.on = false',
] ]
self.bus_float = ["gain = 0.0"] self.bus_float = ['gain = 0.0']
self.bus_params = [ self.bus_params = [
"mono = false", 'mono = false',
"eq.on = false", 'eq.on = false',
"mute = false", 'mute = false',
"gain = 0.0", 'gain = 0.0',
] ]
if profile == "reset": if profile == 'reset':
self.reset_config() self.reset_config()
def reset_config(self): def reset_config(self):
self.phys_strip_params = list( self.phys_strip_params = list(
map(lambda x: x.replace("B1 = false", "B1 = true"), self.phys_strip_params) map(lambda x: x.replace('B1 = false', 'B1 = true'), self.phys_strip_params)
) )
self.virt_strip_params = list( self.virt_strip_params = list(
map(lambda x: x.replace("A1 = false", "A1 = true"), self.virt_strip_params) map(lambda x: x.replace('A1 = false', 'A1 = true'), self.virt_strip_params)
) )
def build(self, profile="reset"): def build(self, profile='reset'):
self.init_config(profile) self.init_config(profile)
toml_str = str() toml_str = str()
for eachclass in self.higher: for eachclass in self.higher:
toml_str += f"[{eachclass}]\n" toml_str += f'[{eachclass}]\n'
toml_str = self.join(eachclass, toml_str) toml_str = self.join(eachclass, toml_str)
return toml_str return toml_str
def join(self, eachclass, toml_str): def join(self, eachclass, toml_str):
kls, index = eachclass.split("-") kls, index = eachclass.split('-')
match kls: match kls:
case "strip": case 'strip':
toml_str += ("\n").join( toml_str += ('\n').join(
self.phys_strip_params self.phys_strip_params
if int(index) < self.kind.phys_in if int(index) < self.kind.phys_in
else self.virt_strip_params else self.virt_strip_params
) )
case "bus": case 'bus':
toml_str += ("\n").join(self.bus_params) toml_str += ('\n').join(self.bus_params)
case _: case _:
pass pass
return toml_str + "\n" return toml_str + '\n'
class TOMLDataExtractor: class TOMLDataExtractor:
def __init__(self, file): def __init__(self, file):
with open(file, "rb") as f: with open(file, 'rb') as f:
self._data = tomllib.load(f) self._data = tomllib.load(f)
@property @property
@ -104,10 +104,10 @@ def dataextraction_factory(file):
this opens the possibility for other parsers to be added this opens the possibility for other parsers to be added
""" """
if file.suffix == ".toml": if file.suffix == '.toml':
extractor = TOMLDataExtractor extractor = TOMLDataExtractor
else: else:
raise ValueError("Cannot extract data from {}".format(file)) raise ValueError('Cannot extract data from {}'.format(file))
return extractor(file) return extractor(file)
@ -141,25 +141,25 @@ class Loader(metaclass=SingletonType):
def defaults(self, kind): def defaults(self, kind):
self.builder = TOMLStrBuilder(kind) self.builder = TOMLStrBuilder(kind)
toml_str = self.builder.build() toml_str = self.builder.build()
self.register("reset", tomllib.loads(toml_str)) self.register('reset', tomllib.loads(toml_str))
def parse(self, identifier, data): def parse(self, identifier, data):
if identifier in self._configs: if identifier in self._configs:
self.logger.info( self.logger.info(
f"config file with name {identifier} already in memory, skipping.." f'config file with name {identifier} already in memory, skipping..'
) )
return return
try: try:
self.parser = dataextraction_factory(data) self.parser = dataextraction_factory(data)
except tomllib.TOMLDecodeError as e: except tomllib.TOMLDecodeError as e:
ERR_MSG = (str(e), f"When attempting to load {identifier}.toml") ERR_MSG = (str(e), f'When attempting to load {identifier}.toml')
self.logger.error(f"{type(e).__name__}: {' '.join(ERR_MSG)}") self.logger.error(f'{type(e).__name__}: {" ".join(ERR_MSG)}')
return return
return True return True
def register(self, identifier, data=None): def register(self, identifier, data=None):
self._configs[identifier] = data if data else self.parser.data self._configs[identifier] = data if data else self.parser.data
self.logger.info(f"config {self.name}/{identifier} loaded into memory") self.logger.info(f'config {self.name}/{identifier} loaded into memory')
def deregister(self): def deregister(self):
self._configs.clear() self._configs.clear()
@ -182,18 +182,18 @@ def loader(kind):
returns configs loaded into memory returns configs loaded into memory
""" """
logger_loader = logger.getChild("loader") logger_loader = logger.getChild('loader')
loader = Loader(kind) loader = Loader(kind)
for path in ( for path in (
Path.cwd() / "configs" / kind.name, Path.cwd() / 'configs' / kind.name,
Path.home() / ".config" / "vban-cmd" / kind.name, Path.home() / '.config' / 'vban-cmd' / kind.name,
Path.home() / "Documents" / "Voicemeeter" / "configs" / kind.name, Path.home() / 'Documents' / 'Voicemeeter' / 'configs' / kind.name,
): ):
if path.is_dir(): if path.is_dir():
logger_loader.info(f"Checking [{path}] for TOML config files:") logger_loader.info(f'Checking [{path}] for TOML config files:')
for file in path.glob("*.toml"): for file in path.glob('*.toml'):
identifier = file.with_suffix("").stem identifier = file.with_suffix('').stem
if loader.parse(identifier, file): if loader.parse(identifier, file):
loader.register(identifier) loader.register(identifier)
return loader.configs return loader.configs
@ -208,5 +208,5 @@ def request_config(kind_id: str):
try: try:
configs = loader(kindmap(kind_id)) configs = loader(kindmap(kind_id))
except KeyError: except KeyError:
raise VBANCMDError(f"Unknown Voicemeeter kind {kind_id}") raise VBANCMDError(f'Unknown Voicemeeter kind {kind_id}')
return configs return configs

View File

@ -12,30 +12,30 @@ class Event:
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
def info(self, msg=None): def info(self, msg=None):
info = (f"{msg} events",) if msg else () info = (f'{msg} events',) if msg else ()
if self.any(): if self.any():
info += (f"now listening for {', '.join(self.get())} events",) info += (f'now listening for {", ".join(self.get())} events',)
else: else:
info += (f"not listening for any events",) info += ('not listening for any events',)
self.logger.info(", ".join(info)) self.logger.info(', '.join(info))
@property @property
def pdirty(self) -> bool: def pdirty(self) -> bool:
return self.subs["pdirty"] return self.subs['pdirty']
@pdirty.setter @pdirty.setter
def pdirty(self, val: bool): def pdirty(self, val: bool):
self.subs["pdirty"] = val self.subs['pdirty'] = val
self.info(f"pdirty {'added to' if val else 'removed from'}") self.info(f'pdirty {"added to" if val else "removed from"}')
@property @property
def ldirty(self) -> bool: def ldirty(self) -> bool:
return self.subs["ldirty"] return self.subs['ldirty']
@ldirty.setter @ldirty.setter
def ldirty(self, val: bool): def ldirty(self, val: bool):
self.subs["ldirty"] = val self.subs['ldirty'] = val
self.info(f"ldirty {'added to' if val else 'removed from'}") self.info(f'ldirty {"added to" if val else "removed from"}')
def get(self) -> list: def get(self) -> list:
return [k for k, v in self.subs.items() if v] return [k for k, v in self.subs.items() if v]

View File

@ -26,24 +26,24 @@ class FactoryBuilder:
""" """
BuilderProgress = IntEnum( BuilderProgress = IntEnum(
"BuilderProgress", "strip bus command macrobutton vban", start=0 'BuilderProgress', 'strip bus command macrobutton vban', start=0
) )
def __init__(self, factory, kind: KindMapClass): def __init__(self, factory, kind: KindMapClass):
self._factory = factory self._factory = factory
self.kind = kind self.kind = kind
self._info = ( self._info = (
f"Finished building strips for {self._factory}", f'Finished building strips for {self._factory}',
f"Finished building buses for {self._factory}", f'Finished building buses for {self._factory}',
f"Finished building commands for {self._factory}", f'Finished building commands for {self._factory}',
f"Finished building macrobuttons for {self._factory}", f'Finished building macrobuttons for {self._factory}',
f"Finished building vban in/out streams for {self._factory}", f'Finished building vban in/out streams for {self._factory}',
) )
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
def _pinfo(self, name: str) -> None: def _pinfo(self, name: str) -> None:
"""prints progress status for each step""" """prints progress status for each step"""
name = name.split("_")[1] name = name.split('_')[1]
self.logger.info(self._info[int(getattr(self.BuilderProgress, name))]) self.logger.info(self._info[int(getattr(self.BuilderProgress, name))])
def make_strip(self): def make_strip(self):
@ -78,20 +78,20 @@ class FactoryBase(VbanCmd):
def __init__(self, kind_id: str, **kwargs): def __init__(self, kind_id: str, **kwargs):
defaultkwargs = { defaultkwargs = {
"ip": None, 'ip': None,
"port": 6980, 'port': 6980,
"streamname": "Command1", 'streamname': 'Command1',
"bps": 0, 'bps': 0,
"channel": 0, 'channel': 0,
"ratelimit": 0.01, 'ratelimit': 0.01,
"timeout": 5, 'timeout': 5,
"outbound": False, 'outbound': False,
"sync": False, 'sync': False,
"pdirty": False, 'pdirty': False,
"ldirty": False, 'ldirty': False,
} }
if "subs" in kwargs: if 'subs' in kwargs:
defaultkwargs |= kwargs.pop("subs") # for backwards compatibility defaultkwargs |= kwargs.pop('subs') # for backwards compatibility
kwargs = defaultkwargs | kwargs kwargs = defaultkwargs | kwargs
self.kind = kindmap(kind_id) self.kind = kindmap(kind_id)
super().__init__(**kwargs) super().__init__(**kwargs)
@ -106,7 +106,7 @@ class FactoryBase(VbanCmd):
self._configs = None self._configs = None
def __str__(self) -> str: def __str__(self) -> str:
return f"Voicemeeter {self.kind}" return f'Voicemeeter {self.kind}'
def __repr__(self): def __repr__(self):
return ( return (
@ -198,15 +198,15 @@ def vbancmd_factory(kind_id: str, **kwargs) -> VbanCmd:
Returns a VbanCmd class of a kind Returns a VbanCmd class of a kind
""" """
match kind_id: match kind_id:
case "basic": case 'basic':
_factory = BasicFactory _factory = BasicFactory
case "banana": case 'banana':
_factory = BananaFactory _factory = BananaFactory
case "potato": case 'potato':
_factory = PotatoFactory _factory = PotatoFactory
case _: case _:
raise ValueError(f"Unknown Voicemeeter kind '{kind_id}'") raise ValueError(f"Unknown Voicemeeter kind '{kind_id}'")
return type(f"VbanCmd{kind_id.capitalize()}", (_factory,), {})(kind_id, **kwargs) return type(f'VbanCmd{kind_id.capitalize()}', (_factory,), {})(kind_id, **kwargs)
def request_vbancmd_obj(kind_id: str, **kwargs) -> VbanCmd: def request_vbancmd_obj(kind_id: str, **kwargs) -> VbanCmd:
@ -215,12 +215,12 @@ def request_vbancmd_obj(kind_id: str, **kwargs) -> VbanCmd:
Returns a reference to a VbanCmd class of a kind Returns a reference to a VbanCmd class of a kind
""" """
logger_entry = logger.getChild("factory.request_vbancmd_obj") logger_entry = logger.getChild('factory.request_vbancmd_obj')
VBANCMD_obj = None VBANCMD_obj = None
try: try:
VBANCMD_obj = vbancmd_factory(kind_id, **kwargs) VBANCMD_obj = vbancmd_factory(kind_id, **kwargs)
except (ValueError, TypeError) as e: except (ValueError, TypeError) as e:
logger_entry.exception(f"{type(e).__name__}: {e}") logger_entry.exception(f'{type(e).__name__}: {e}')
raise VBANCMDError(str(e)) from e raise VBANCMDError(str(e)) from e
return VBANCMD_obj return VBANCMD_obj

View File

@ -93,7 +93,7 @@ class IRemote(metaclass=ABCMeta):
def getter(self, param): def getter(self, param):
cmd = self._cmd(param) cmd = self._cmd(param)
self.logger.debug(f"getter: {cmd}") self.logger.debug(f'getter: {cmd}')
if cmd in self._remote.cache: if cmd in self._remote.cache:
return self._remote.cache.pop(cmd) return self._remote.cache.pop(cmd)
if self._remote.sync: if self._remote.sync:
@ -101,14 +101,14 @@ class IRemote(metaclass=ABCMeta):
def setter(self, param, val): def setter(self, param, val):
"""Sends a string request RT packet.""" """Sends a string request RT packet."""
self.logger.debug(f"setter: {self._cmd(param)}={val}") self.logger.debug(f'setter: {self._cmd(param)}={val}')
self._remote._set_rt(self._cmd(param), val) self._remote._set_rt(self._cmd(param), val)
def _cmd(self, param): def _cmd(self, param):
cmd = (self.identifier,) cmd = (self.identifier,)
if param: if param:
cmd += (f".{param}",) cmd += (f'.{param}',)
return "".join(cmd) return ''.join(cmd)
@property @property
@abstractmethod @abstractmethod
@ -124,10 +124,10 @@ class IRemote(metaclass=ABCMeta):
"""Sets all parameters of a dict for the channel.""" """Sets all parameters of a dict for the channel."""
def fget(attr, val): def fget(attr, val):
if attr == "mode": if attr == 'mode':
return (f"mode.{val}", 1) return (f'mode.{val}', 1)
elif attr == "knob": elif attr == 'knob':
return ("", val) return ('', val)
return (attr, val) return (attr, val)
for attr, val in data.items(): for attr, val in data.items():
@ -138,7 +138,7 @@ class IRemote(metaclass=ABCMeta):
val = 1 if val else 0 val = 1 if val else 0
self._remote.cache[self._cmd(attr)] = val self._remote.cache[self._cmd(attr)] = val
self._remote._script += f"{self._cmd(attr)}={val};" self._remote._script += f'{self._cmd(attr)}={val};'
else: else:
target = getattr(self, attr) target = getattr(self, attr)
target.apply(val) target.apply(val)

View File

@ -91,14 +91,14 @@ class PotatoMap(KindMapClass):
def kind_factory(kind_id): def kind_factory(kind_id):
match kind_id: match kind_id:
case "basic": case 'basic':
_kind_map = BasicMap _kind_map = BasicMap
case "banana": case 'banana':
_kind_map = BananaMap _kind_map = BananaMap
case "potato": case 'potato':
_kind_map = PotatoMap _kind_map = PotatoMap
case _: case _:
raise ValueError(f"Unknown Voicemeeter kind {kind_id}") raise ValueError(f'Unknown Voicemeeter kind {kind_id}')
return _kind_map(name=kind_id) return _kind_map(name=kind_id)

View File

@ -5,32 +5,32 @@ class MacroButton(IRemote):
"""A placeholder class in case this interface is being used interchangeably with the Remote API""" """A placeholder class in case this interface is being used interchangeably with the Remote API"""
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self._remote.kind}{self.index}" return f'{type(self).__name__}{self._remote.kind}{self.index}'
@property @property
def identifier(self): def identifier(self):
return f"command.button[{self.index}]" return f'command.button[{self.index}]'
@property @property
def state(self) -> bool: def state(self) -> bool:
self.logger.warning("button.state commands are not supported over VBAN") self.logger.warning('button.state commands are not supported over VBAN')
@state.setter @state.setter
def state(self, _): def state(self, _):
self.logger.warning("button.state commands are not supported over VBAN") self.logger.warning('button.state commands are not supported over VBAN')
@property @property
def stateonly(self) -> bool: def stateonly(self) -> bool:
self.logger.warning("button.stateonly commands are not supported over VBAN") self.logger.warning('button.stateonly commands are not supported over VBAN')
@stateonly.setter @stateonly.setter
def stateonly(self, v): def stateonly(self, v):
self.logger.warning("button.stateonly commands are not supported over VBAN") self.logger.warning('button.stateonly commands are not supported over VBAN')
@property @property
def trigger(self) -> bool: def trigger(self) -> bool:
self.logger.warning("button.trigger commands are not supported over VBAN") self.logger.warning('button.trigger commands are not supported over VBAN')
@trigger.setter @trigger.setter
def trigger(self, _): def trigger(self, _):
self.logger.warning("button.trigger commands are not supported over VBAN") self.logger.warning('button.trigger commands are not supported over VBAN')

View File

@ -9,16 +9,16 @@ def channel_bool_prop(param):
@partial(cache_bool, param=param) @partial(cache_bool, param=param)
def fget(self): def fget(self):
cmd = self._cmd(param) cmd = self._cmd(param)
self.logger.debug(f"getter: {cmd}") self.logger.debug(f'getter: {cmd}')
return ( return (
not int.from_bytes( not int.from_bytes(
getattr( getattr(
self.public_packet, self.public_packet,
f"{'strip' if 'strip' in type(self).__name__.lower() else 'bus'}state", f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}state',
)[self.index], )[self.index],
"little", 'little',
) )
& getattr(self._modes, f"_{param.lower()}") & getattr(self._modes, f'_{param.lower()}')
== 0 == 0
) )
@ -31,15 +31,15 @@ def channel_bool_prop(param):
def channel_label_prop(): def channel_label_prop():
"""meta function for channel label parameters""" """meta function for channel label parameters"""
@partial(cache_string, param="label") @partial(cache_string, param='label')
def fget(self) -> str: def fget(self) -> str:
return getattr( return getattr(
self.public_packet, self.public_packet,
f"{'strip' if 'strip' in type(self).__name__.lower() else 'bus'}labels", f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}labels',
)[self.index] )[self.index]
def fset(self, val: str): def fset(self, val: str):
self.setter("label", str(val)) self.setter('label', str(val))
return property(fget, fset) return property(fget, fset)
@ -50,10 +50,10 @@ def strip_output_prop(param):
@partial(cache_bool, param=param) @partial(cache_bool, param=param)
def fget(self): def fget(self):
cmd = self._cmd(param) cmd = self._cmd(param)
self.logger.debug(f"getter: {cmd}") self.logger.debug(f'getter: {cmd}')
return ( return (
not int.from_bytes(self.public_packet.stripstate[self.index], "little") not int.from_bytes(self.public_packet.stripstate[self.index], 'little')
& getattr(self._modes, f"_bus{param.lower()}") & getattr(self._modes, f'_bus{param.lower()}')
== 0 == 0
) )
@ -69,9 +69,9 @@ def bus_mode_prop(param):
@partial(cache_bool, param=param) @partial(cache_bool, param=param)
def fget(self): def fget(self):
cmd = self._cmd(param) cmd = self._cmd(param)
self.logger.debug(f"getter: {cmd}") self.logger.debug(f'getter: {cmd}')
return [ return [
(int.from_bytes(self.public_packet.busstate[self.index], "little") & val) (int.from_bytes(self.public_packet.busstate[self.index], 'little') & val)
>> 4 >> 4
for val in self._modes.modevals for val in self._modes.modevals
] == self.modestates[param] ] == self.modestates[param]

View File

@ -43,7 +43,7 @@ class VbanRtPacket:
def _generate_levels(self, levelarray) -> tuple: def _generate_levels(self, levelarray) -> tuple:
return tuple( return tuple(
int.from_bytes(levelarray[i : i + 2], "little") int.from_bytes(levelarray[i : i + 2], 'little')
for i in range(0, len(levelarray), 2) for i in range(0, len(levelarray), 2)
) )
@ -79,13 +79,13 @@ class VbanRtPacket:
tuple(not val for val in comp(strip_cache, self.strip_levels)), tuple(not val for val in comp(strip_cache, self.strip_levels)),
tuple(not val for val in comp(bus_cache, self.bus_levels)), tuple(not val for val in comp(bus_cache, self.bus_levels)),
) )
return any(any(l) for l in (self._strip_comp, self._bus_comp)) return any(any(li) for li in (self._strip_comp, self._bus_comp))
@property @property
def voicemeetertype(self) -> str: def voicemeetertype(self) -> str:
"""returns voicemeeter type as a string""" """returns voicemeeter type as a string"""
type_ = ("basic", "banana", "potato") type_ = ('basic', 'banana', 'potato')
return type_[int.from_bytes(self._voicemeeterType, "little") - 1] return type_[int.from_bytes(self._voicemeeterType, 'little') - 1]
@property @property
def voicemeeterversion(self) -> tuple: def voicemeeterversion(self) -> tuple:
@ -93,7 +93,7 @@ class VbanRtPacket:
return tuple( return tuple(
reversed( reversed(
tuple( tuple(
int.from_bytes(self._voicemeeterVersion[i : i + 1], "little") int.from_bytes(self._voicemeeterVersion[i : i + 1], 'little')
for i in range(4) for i in range(4)
) )
) )
@ -102,7 +102,7 @@ class VbanRtPacket:
@property @property
def samplerate(self) -> int: def samplerate(self) -> int:
"""returns samplerate as an int""" """returns samplerate as an int"""
return int.from_bytes(self._samplerate, "little") return int.from_bytes(self._samplerate, 'little')
@property @property
def inputlevels(self) -> tuple: def inputlevels(self) -> tuple:
@ -132,56 +132,56 @@ class VbanRtPacket:
@property @property
def stripgainlayer1(self) -> tuple: def stripgainlayer1(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer1[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer1[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer2(self) -> tuple: def stripgainlayer2(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer2[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer2[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer3(self) -> tuple: def stripgainlayer3(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer3[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer3[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer4(self) -> tuple: def stripgainlayer4(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer4[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer4[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer5(self) -> tuple: def stripgainlayer5(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer5[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer5[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer6(self) -> tuple: def stripgainlayer6(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer6[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer6[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer7(self) -> tuple: def stripgainlayer7(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer7[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer7[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@property @property
def stripgainlayer8(self) -> tuple: def stripgainlayer8(self) -> tuple:
return tuple( return tuple(
int.from_bytes(self._stripGaindB100Layer8[i : i + 2], "little") int.from_bytes(self._stripGaindB100Layer8[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@ -189,7 +189,7 @@ class VbanRtPacket:
def busgain(self) -> tuple: def busgain(self) -> tuple:
"""returns tuple of bus gains""" """returns tuple of bus gains"""
return tuple( return tuple(
int.from_bytes(self._busGaindB100[i : i + 2], "little") int.from_bytes(self._busGaindB100[i : i + 2], 'little')
for i in range(0, 16, 2) for i in range(0, 16, 2)
) )
@ -197,7 +197,7 @@ class VbanRtPacket:
def striplabels(self) -> tuple: def striplabels(self) -> tuple:
"""returns tuple of strip labels""" """returns tuple of strip labels"""
return tuple( return tuple(
self._stripLabelUTF8c60[i : i + 60].decode().split("\x00")[0] self._stripLabelUTF8c60[i : i + 60].decode().split('\x00')[0]
for i in range(0, 480, 60) for i in range(0, 480, 60)
) )
@ -205,7 +205,7 @@ class VbanRtPacket:
def buslabels(self) -> tuple: def buslabels(self) -> tuple:
"""returns tuple of bus labels""" """returns tuple of bus labels"""
return tuple( return tuple(
self._busLabelUTF8c60[i : i + 60].decode().split("\x00")[0] self._busLabelUTF8c60[i : i + 60].decode().split('\x00')[0]
for i in range(0, 480, 60) for i in range(0, 480, 60)
) )
@ -214,15 +214,15 @@ class VbanRtPacket:
class SubscribeHeader: class SubscribeHeader:
"""Represents the header an RT Packet Service subscription packet""" """Represents the header an RT Packet Service subscription packet"""
name = "Register RTP" name = 'Register RTP'
timeout = 15 timeout = 15
vban: bytes = "VBAN".encode() vban: bytes = 'VBAN'.encode()
format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, "little") format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, 'little')
format_nbs: bytes = (0).to_bytes(1, "little") format_nbs: bytes = (0).to_bytes(1, 'little')
format_nbc: bytes = (VBAN_SERVICE_RTPACKETREGISTER).to_bytes(1, "little") format_nbc: bytes = (VBAN_SERVICE_RTPACKETREGISTER).to_bytes(1, 'little')
format_bit: bytes = (timeout & 0x000000FF).to_bytes(1, "little") # timeout format_bit: bytes = (timeout & 0x000000FF).to_bytes(1, 'little') # timeout
streamname: bytes = name.encode("ascii") + bytes(16 - len(name)) streamname: bytes = name.encode('ascii') + bytes(16 - len(name))
framecounter: bytes = (0).to_bytes(4, "little") framecounter: bytes = (0).to_bytes(4, 'little')
@property @property
def header(self): def header(self):
@ -233,9 +233,9 @@ class SubscribeHeader:
header += self.format_bit header += self.format_bit
header += self.streamname header += self.streamname
header += self.framecounter header += self.framecounter
assert ( assert len(header) == HEADER_SIZE + 4, (
len(header) == HEADER_SIZE + 4 f'expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE + 4} bytes total)'
), f"expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE +4} bytes total)" )
return header return header
@ -243,13 +243,13 @@ class SubscribeHeader:
class VbanRtPacketHeader: class VbanRtPacketHeader:
"""Represents the header of a VBAN RT response packet""" """Represents the header of a VBAN RT response packet"""
name = "Voicemeeter-RTP" name = 'Voicemeeter-RTP'
vban: bytes = "VBAN".encode() vban: bytes = 'VBAN'.encode()
format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, "little") format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, 'little')
format_nbs: bytes = (0).to_bytes(1, "little") format_nbs: bytes = (0).to_bytes(1, 'little')
format_nbc: bytes = (VBAN_SERVICE_RTPACKET).to_bytes(1, "little") format_nbc: bytes = (VBAN_SERVICE_RTPACKET).to_bytes(1, 'little')
format_bit: bytes = (0).to_bytes(1, "little") format_bit: bytes = (0).to_bytes(1, 'little')
streamname: bytes = name.encode("ascii") + bytes(16 - len(name)) streamname: bytes = name.encode('ascii') + bytes(16 - len(name))
@property @property
def header(self): def header(self):
@ -259,7 +259,7 @@ class VbanRtPacketHeader:
header += self.format_nbc header += self.format_nbc
header += self.format_bit header += self.format_bit
header += self.streamname header += self.streamname
assert len(header) == HEADER_SIZE, f"expected header size {HEADER_SIZE} bytes" assert len(header) == HEADER_SIZE, f'expected header size {HEADER_SIZE} bytes'
return header return header
@ -270,18 +270,18 @@ class RequestHeader:
name: str name: str
bps_index: int bps_index: int
channel: int channel: int
vban: bytes = "VBAN".encode() vban: bytes = 'VBAN'.encode()
nbs: bytes = (0).to_bytes(1, "little") nbs: bytes = (0).to_bytes(1, 'little')
bit: bytes = (0x10).to_bytes(1, "little") bit: bytes = (0x10).to_bytes(1, 'little')
framecounter: bytes = (0).to_bytes(4, "little") framecounter: bytes = (0).to_bytes(4, 'little')
@property @property
def sr(self): def sr(self):
return (VBAN_PROTOCOL_TXT + self.bps_index).to_bytes(1, "little") return (VBAN_PROTOCOL_TXT + self.bps_index).to_bytes(1, 'little')
@property @property
def nbc(self): def nbc(self):
return (self.channel).to_bytes(1, "little") return (self.channel).to_bytes(1, 'little')
@property @property
def streamname(self): def streamname(self):
@ -296,7 +296,7 @@ class RequestHeader:
header += self.bit header += self.bit
header += self.streamname header += self.streamname
header += self.framecounter header += self.framecounter
assert ( assert len(header) == HEADER_SIZE + 4, (
len(header) == HEADER_SIZE + 4 f'expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE + 4} bytes total)'
), f"expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE +4} bytes total)" )
return header return header

View File

@ -20,7 +20,7 @@ class Strip(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}]" return f'strip[{self.index}]'
@property @property
def limit(self) -> int: def limit(self) -> int:
@ -28,25 +28,25 @@ class Strip(IRemote):
@limit.setter @limit.setter
def limit(self, val: int): def limit(self, val: int):
self.setter("limit", val) self.setter('limit', val)
@property @property
def gain(self) -> float: def gain(self) -> float:
val = self.getter("gain") val = self.getter('gain')
if val is None: if val is None:
val = self.gainlayer[0].gain val = self.gainlayer[0].gain
return round(val, 1) return round(val, 1)
@gain.setter @gain.setter
def gain(self, val: float): def gain(self, val: float):
self.setter("gain", val) self.setter('gain', val)
def fadeto(self, target: float, time_: int): def fadeto(self, target: float, time_: int):
self.setter("FadeTo", f"({target}, {time_})") self.setter('FadeTo', f'({target}, {time_})')
time.sleep(self._remote.DELAY) time.sleep(self._remote.DELAY)
def fadeby(self, change: float, time_: int): def fadeby(self, change: float, time_: int):
self.setter("FadeBy", f"({change}, {time_})") self.setter('FadeBy', f'({change}, {time_})')
time.sleep(self._remote.DELAY) time.sleep(self._remote.DELAY)
@ -54,18 +54,18 @@ class PhysicalStrip(Strip):
@classmethod @classmethod
def make(cls, remote, index): def make(cls, remote, index):
return type( return type(
f"PhysicalStrip{remote.kind}", f'PhysicalStrip{remote.kind}',
(cls,), (cls,),
{ {
"comp": StripComp(remote, index), 'comp': StripComp(remote, index),
"gate": StripGate(remote, index), 'gate': StripGate(remote, index),
"denoiser": StripDenoiser(remote, index), 'denoiser': StripDenoiser(remote, index),
"eq": StripEQ(remote, index), 'eq': StripEQ(remote, index),
}, },
) )
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self.index}" return f'{type(self).__name__}{self.index}'
@property @property
def device(self): def device(self):
@ -79,7 +79,7 @@ class PhysicalStrip(Strip):
class StripComp(IRemote): class StripComp(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}].comp" return f'strip[{self.index}].comp'
@property @property
def knob(self) -> float: def knob(self) -> float:
@ -87,7 +87,7 @@ class StripComp(IRemote):
@knob.setter @knob.setter
def knob(self, val: float): def knob(self, val: float):
self.setter("", val) self.setter('', val)
@property @property
def gainin(self) -> float: def gainin(self) -> float:
@ -95,7 +95,7 @@ class StripComp(IRemote):
@gainin.setter @gainin.setter
def gainin(self, val: float): def gainin(self, val: float):
self.setter("GainIn", val) self.setter('GainIn', val)
@property @property
def ratio(self) -> float: def ratio(self) -> float:
@ -103,7 +103,7 @@ class StripComp(IRemote):
@ratio.setter @ratio.setter
def ratio(self, val: float): def ratio(self, val: float):
self.setter("Ratio", val) self.setter('Ratio', val)
@property @property
def threshold(self) -> float: def threshold(self) -> float:
@ -111,7 +111,7 @@ class StripComp(IRemote):
@threshold.setter @threshold.setter
def threshold(self, val: float): def threshold(self, val: float):
self.setter("Threshold", val) self.setter('Threshold', val)
@property @property
def attack(self) -> float: def attack(self) -> float:
@ -119,7 +119,7 @@ class StripComp(IRemote):
@attack.setter @attack.setter
def attack(self, val: float): def attack(self, val: float):
self.setter("Attack", val) self.setter('Attack', val)
@property @property
def release(self) -> float: def release(self) -> float:
@ -127,7 +127,7 @@ class StripComp(IRemote):
@release.setter @release.setter
def release(self, val: float): def release(self, val: float):
self.setter("Release", val) self.setter('Release', val)
@property @property
def knee(self) -> float: def knee(self) -> float:
@ -135,7 +135,7 @@ class StripComp(IRemote):
@knee.setter @knee.setter
def knee(self, val: float): def knee(self, val: float):
self.setter("Knee", val) self.setter('Knee', val)
@property @property
def gainout(self) -> float: def gainout(self) -> float:
@ -143,7 +143,7 @@ class StripComp(IRemote):
@gainout.setter @gainout.setter
def gainout(self, val: float): def gainout(self, val: float):
self.setter("GainOut", val) self.setter('GainOut', val)
@property @property
def makeup(self) -> bool: def makeup(self) -> bool:
@ -151,13 +151,13 @@ class StripComp(IRemote):
@makeup.setter @makeup.setter
def makeup(self, val: bool): def makeup(self, val: bool):
self.setter("makeup", 1 if val else 0) self.setter('makeup', 1 if val else 0)
class StripGate(IRemote): class StripGate(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}].gate" return f'strip[{self.index}].gate'
@property @property
def knob(self) -> float: def knob(self) -> float:
@ -165,7 +165,7 @@ class StripGate(IRemote):
@knob.setter @knob.setter
def knob(self, val: float): def knob(self, val: float):
self.setter("", val) self.setter('', val)
@property @property
def threshold(self) -> float: def threshold(self) -> float:
@ -173,7 +173,7 @@ class StripGate(IRemote):
@threshold.setter @threshold.setter
def threshold(self, val: float): def threshold(self, val: float):
self.setter("Threshold", val) self.setter('Threshold', val)
@property @property
def damping(self) -> float: def damping(self) -> float:
@ -181,7 +181,7 @@ class StripGate(IRemote):
@damping.setter @damping.setter
def damping(self, val: float): def damping(self, val: float):
self.setter("Damping", val) self.setter('Damping', val)
@property @property
def bpsidechain(self) -> int: def bpsidechain(self) -> int:
@ -189,7 +189,7 @@ class StripGate(IRemote):
@bpsidechain.setter @bpsidechain.setter
def bpsidechain(self, val: int): def bpsidechain(self, val: int):
self.setter("BPSidechain", val) self.setter('BPSidechain', val)
@property @property
def attack(self) -> float: def attack(self) -> float:
@ -197,7 +197,7 @@ class StripGate(IRemote):
@attack.setter @attack.setter
def attack(self, val: float): def attack(self, val: float):
self.setter("Attack", val) self.setter('Attack', val)
@property @property
def hold(self) -> float: def hold(self) -> float:
@ -205,7 +205,7 @@ class StripGate(IRemote):
@hold.setter @hold.setter
def hold(self, val: float): def hold(self, val: float):
self.setter("Hold", val) self.setter('Hold', val)
@property @property
def release(self) -> float: def release(self) -> float:
@ -213,13 +213,13 @@ class StripGate(IRemote):
@release.setter @release.setter
def release(self, val: float): def release(self, val: float):
self.setter("Release", val) self.setter('Release', val)
class StripDenoiser(IRemote): class StripDenoiser(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}].denoiser" return f'strip[{self.index}].denoiser'
@property @property
def knob(self) -> float: def knob(self) -> float:
@ -227,13 +227,13 @@ class StripDenoiser(IRemote):
@knob.setter @knob.setter
def knob(self, val: float): def knob(self, val: float):
self.setter("", val) self.setter('', val)
class StripEQ(IRemote): class StripEQ(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}].eq" return f'strip[{self.index}].eq'
@property @property
def on(self): def on(self):
@ -241,7 +241,7 @@ class StripEQ(IRemote):
@on.setter @on.setter
def on(self, val: bool): def on(self, val: bool):
self.setter("on", 1 if val else 0) self.setter('on', 1 if val else 0)
@property @property
def ab(self): def ab(self):
@ -249,14 +249,14 @@ class StripEQ(IRemote):
@ab.setter @ab.setter
def ab(self, val: bool): def ab(self, val: bool):
self.setter("ab", 1 if val else 0) self.setter('ab', 1 if val else 0)
class VirtualStrip(Strip): class VirtualStrip(Strip):
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self.index}" return f'{type(self).__name__}{self.index}'
mc = channel_bool_prop("mc") mc = channel_bool_prop('mc')
mono = mc mono = mc
@ -266,13 +266,13 @@ class VirtualStrip(Strip):
@k.setter @k.setter
def k(self, val: int): def k(self, val: int):
self.setter("karaoke", val) self.setter('karaoke', val)
def appgain(self, name: str, gain: float): def appgain(self, name: str, gain: float):
self.setter("AppGain", f'("{name}", {gain})') self.setter('AppGain', f'("{name}", {gain})')
def appmute(self, name: str, mute: bool = None): def appmute(self, name: str, mute: bool = None):
self.setter("AppMute", f'("{name}", {1 if mute else 0})') self.setter('AppMute', f'("{name}", {1 if mute else 0})')
class StripLevel(IRemote): class StripLevel(IRemote):
@ -299,7 +299,7 @@ class StripLevel(IRemote):
if not self._remote.stopped() and self._remote.event.ldirty: if not self._remote.stopped() and self._remote.event.ldirty:
return tuple( return tuple(
fget(i) fget(i)
for i in self._remote.cache["strip_level"][ for i in self._remote.cache['strip_level'][
self.range[0] : self.range[-1] self.range[0] : self.range[-1]
] ]
) )
@ -312,7 +312,7 @@ class StripLevel(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}]" return f'strip[{self.index}]'
@property @property
def prefader(self) -> tuple: def prefader(self) -> tuple:
@ -345,31 +345,33 @@ class GainLayer(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"strip[{self.index}]" return f'strip[{self.index}]'
@property @property
def gain(self) -> float: def gain(self) -> float:
def fget(): def fget():
val = getattr(self.public_packet, f"stripgainlayer{self._i+1}")[self.index] val = getattr(self.public_packet, f'stripgainlayer{self._i + 1}')[
self.index
]
if 0 <= val <= 1200: if 0 <= val <= 1200:
return val * 0.01 return val * 0.01
return (((1 << 16) - 1) - val) * -0.01 return (((1 << 16) - 1) - val) * -0.01
val = self.getter(f"GainLayer[{self._i}]") val = self.getter(f'GainLayer[{self._i}]')
return round(val if val else fget(), 1) return round(val if val else fget(), 1)
@gain.setter @gain.setter
def gain(self, val: float): def gain(self, val: float):
self.setter(f"GainLayer[{self._i}]", val) self.setter(f'GainLayer[{self._i}]', val)
def _make_gainlayer_mixin(remote, index): def _make_gainlayer_mixin(remote, index):
"""Creates a GainLayer mixin""" """Creates a GainLayer mixin"""
return type( return type(
f"GainlayerMixin", 'GainlayerMixin',
(), (),
{ {
"gainlayer": tuple( 'gainlayer': tuple(
GainLayer(remote, index, i) for i in range(remote.kind.num_bus) GainLayer(remote, index, i) for i in range(remote.kind.num_bus)
) )
}, },
@ -379,14 +381,14 @@ def _make_gainlayer_mixin(remote, index):
def _make_channelout_mixin(kind): def _make_channelout_mixin(kind):
"""Creates a channel out property mixin""" """Creates a channel out property mixin"""
return type( return type(
f"ChannelOutMixin{kind}", f'ChannelOutMixin{kind}',
(), (),
{ {
**{ **{
f"A{i}": strip_output_prop(f"A{i}") for i in range(1, kind.phys_out + 1) f'A{i}': strip_output_prop(f'A{i}') for i in range(1, kind.phys_out + 1)
}, },
**{ **{
f"B{i}": strip_output_prop(f"B{i}") for i in range(1, kind.virt_out + 1) f'B{i}': strip_output_prop(f'B{i}') for i in range(1, kind.virt_out + 1)
}, },
}, },
) )
@ -410,12 +412,12 @@ def strip_factory(is_phys_strip, remote, i) -> Union[PhysicalStrip, VirtualStrip
GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i) GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i)
return type( return type(
f"{STRIP_cls.__name__}{remote.kind}", f'{STRIP_cls.__name__}{remote.kind}',
(STRIP_cls, CHANNELOUTMIXIN_cls, GAINLAYERMIXIN_cls), (STRIP_cls, CHANNELOUTMIXIN_cls, GAINLAYERMIXIN_cls),
{ {
"levels": StripLevel(remote, i), 'levels': StripLevel(remote, i),
**{param: channel_bool_prop(param) for param in ["mono", "solo", "mute"]}, **{param: channel_bool_prop(param) for param in ['mono', 'solo', 'mute']},
"label": channel_label_prop(), 'label': channel_label_prop(),
}, },
)(remote, i) )(remote, i)

View File

@ -20,10 +20,10 @@ class Subject:
"""run callbacks on update""" """run callbacks on update"""
for o in self._observers: for o in self._observers:
if hasattr(o, "on_update"): if hasattr(o, 'on_update'):
o.on_update(event) o.on_update(event)
else: else:
if o.__name__ == f"on_{event}": if o.__name__ == f'on_{event}':
o() o()
def add(self, observer): def add(self, observer):
@ -34,15 +34,15 @@ class Subject:
for o in iterator: for o in iterator:
if o not in self._observers: if o not in self._observers:
self._observers.append(o) self._observers.append(o)
self.logger.info(f"{o} added to event observers") self.logger.info(f'{o} added to event observers')
else: else:
self.logger.error(f"Failed to add {o} to event observers") self.logger.error(f'Failed to add {o} to event observers')
except TypeError: except TypeError:
if observer not in self._observers: if observer not in self._observers:
self._observers.append(observer) self._observers.append(observer)
self.logger.info(f"{observer} added to event observers") self.logger.info(f'{observer} added to event observers')
else: else:
self.logger.error(f"Failed to add {observer} to event observers") self.logger.error(f'Failed to add {observer} to event observers')
register = add register = add
@ -54,15 +54,15 @@ class Subject:
for o in iterator: for o in iterator:
try: try:
self._observers.remove(o) self._observers.remove(o)
self.logger.info(f"{o} removed from event observers") self.logger.info(f'{o} removed from event observers')
except ValueError: except ValueError:
self.logger.error(f"Failed to remove {o} from event observers") self.logger.error(f'Failed to remove {o} from event observers')
except TypeError: except TypeError:
try: try:
self._observers.remove(observer) self._observers.remove(observer)
self.logger.info(f"{observer} removed from event observers") self.logger.info(f'{observer} removed from event observers')
except ValueError: except ValueError:
self.logger.error(f"Failed to remove {observer} from event observers") self.logger.error(f'Failed to remove {observer} from event observers')
deregister = remove deregister = remove

View File

@ -1,4 +1,3 @@
from enum import IntEnum
from typing import Iterator from typing import Iterator
@ -42,15 +41,15 @@ def script(func):
def wrapper(*args): def wrapper(*args):
remote, script = args remote, script = args
if isinstance(script, dict): if isinstance(script, dict):
params = "" params = ''
for key, val in script.items(): for key, val in script.items():
obj, m2, *rem = key.split("-") obj, m2, *rem = key.split('-')
index = int(m2) if m2.isnumeric() else int(*rem) index = int(m2) if m2.isnumeric() else int(*rem)
params += ";".join( params += ';'.join(
f"{obj}{f'.{m2}stream' if not m2.isnumeric() else ''}[{index}].{k}={int(v) if isinstance(v, bool) else v}" f'{obj}{f".{m2}stream" if not m2.isnumeric() else ""}[{index}].{k}={int(v) if isinstance(v, bool) else v}'
for k, v in val.items() for k, v in val.items()
) )
params += ";" params += ';'
script = params script = params
return func(remote, script) return func(remote, script)
@ -83,6 +82,3 @@ def deep_merge(dict1, dict2):
yield k, dict1[k] yield k, dict1[k]
else: else:
yield k, dict2[k] yield k, dict2[k]
Socket = IntEnum("Socket", "register request response", start=0)

View File

@ -17,7 +17,7 @@ class VbanStream(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f"vban.{self.direction}stream[{self.index}]" return f'vban.{self.direction}stream[{self.index}]'
@property @property
def on(self) -> bool: def on(self) -> bool:
@ -25,7 +25,7 @@ class VbanStream(IRemote):
@on.setter @on.setter
def on(self, val: bool): def on(self, val: bool):
self.setter("on", 1 if val else 0) self.setter('on', 1 if val else 0)
@property @property
def name(self) -> str: def name(self) -> str:
@ -33,7 +33,7 @@ class VbanStream(IRemote):
@name.setter @name.setter
def name(self, val: str): def name(self, val: str):
self.setter("name", val) self.setter('name', val)
@property @property
def ip(self) -> str: def ip(self) -> str:
@ -41,7 +41,7 @@ class VbanStream(IRemote):
@ip.setter @ip.setter
def ip(self, val: str): def ip(self, val: str):
self.setter("ip", val) self.setter('ip', val)
@property @property
def port(self) -> int: def port(self) -> int:
@ -51,9 +51,9 @@ class VbanStream(IRemote):
def port(self, val: int): def port(self, val: int):
if not 1024 <= val <= 65535: if not 1024 <= val <= 65535:
self.logger.warning( self.logger.warning(
f"port got: {val} but expected a value from 1024 to 65535" f'port got: {val} but expected a value from 1024 to 65535'
) )
self.setter("port", val) self.setter('port', val)
@property @property
def sr(self) -> int: def sr(self) -> int:
@ -63,8 +63,8 @@ class VbanStream(IRemote):
def sr(self, val: int): def sr(self, val: int):
opts = (11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000) opts = (11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000)
if val not in opts: if val not in opts:
self.logger.warning(f"sr got: {val} but expected a value in {opts}") self.logger.warning(f'sr got: {val} but expected a value in {opts}')
self.setter("sr", val) self.setter('sr', val)
@property @property
def channel(self) -> int: def channel(self) -> int:
@ -73,8 +73,8 @@ class VbanStream(IRemote):
@channel.setter @channel.setter
def channel(self, val: int): def channel(self, val: int):
if not 1 <= val <= 8: if not 1 <= val <= 8:
self.logger.warning(f"channel got: {val} but expected a value from 1 to 8") self.logger.warning(f'channel got: {val} but expected a value from 1 to 8')
self.setter("channel", val) self.setter('channel', val)
@property @property
def bit(self) -> int: def bit(self) -> int:
@ -83,8 +83,8 @@ class VbanStream(IRemote):
@bit.setter @bit.setter
def bit(self, val: int): def bit(self, val: int):
if val not in (16, 24): if val not in (16, 24):
self.logger.warning(f"bit got: {val} but expected value 16 or 24") self.logger.warning(f'bit got: {val} but expected value 16 or 24')
self.setter("bit", 1 if (val == 16) else 2) self.setter('bit', 1 if (val == 16) else 2)
@property @property
def quality(self) -> int: def quality(self) -> int:
@ -93,8 +93,8 @@ class VbanStream(IRemote):
@quality.setter @quality.setter
def quality(self, val: int): def quality(self, val: int):
if not 0 <= val <= 4: if not 0 <= val <= 4:
self.logger.warning(f"quality got: {val} but expected a value from 0 to 4") self.logger.warning(f'quality got: {val} but expected a value from 0 to 4')
self.setter("quality", val) self.setter('quality', val)
@property @property
def route(self) -> int: def route(self) -> int:
@ -103,8 +103,8 @@ class VbanStream(IRemote):
@route.setter @route.setter
def route(self, val: int): def route(self, val: int):
if not 0 <= val <= 8: if not 0 <= val <= 8:
self.logger.warning(f"route got: {val} but expected a value from 0 to 8") self.logger.warning(f'route got: {val} but expected a value from 0 to 8')
self.setter("route", val) self.setter('route', val)
class VbanInstream(VbanStream): class VbanInstream(VbanStream):
@ -115,11 +115,11 @@ class VbanInstream(VbanStream):
""" """
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self._remote.kind}{self.index}" return f'{type(self).__name__}{self._remote.kind}{self.index}'
@property @property
def direction(self) -> str: def direction(self) -> str:
return "in" return 'in'
@property @property
def sr(self) -> int: def sr(self) -> int:
@ -154,11 +154,11 @@ class VbanOutstream(VbanStream):
""" """
def __str__(self): def __str__(self):
return f"{type(self).__name__}{self._remote.kind}{self.index}" return f'{type(self).__name__}{self._remote.kind}{self.index}'
@property @property
def direction(self) -> str: def direction(self) -> str:
return "out" return 'out'
class VbanAudioOutstream(VbanOutstream): class VbanAudioOutstream(VbanOutstream):
@ -174,22 +174,22 @@ def _make_stream_pair(remote, kind):
def _make_cls(i, direction): def _make_cls(i, direction):
match direction: match direction:
case "in": case 'in':
if i < num_instream: if i < num_instream:
return VbanAudioInstream(remote, i) return VbanAudioInstream(remote, i)
elif i < num_instream + num_midi: elif i < num_instream + num_midi:
return VbanMidiInstream(remote, i) return VbanMidiInstream(remote, i)
else: else:
return VbanTextInstream(remote, i) return VbanTextInstream(remote, i)
case "out": case 'out':
if i < num_outstream: if i < num_outstream:
return VbanAudioOutstream(remote, i) return VbanAudioOutstream(remote, i)
else: else:
return VbanMidiOutstream(remote, i) return VbanMidiOutstream(remote, i)
return ( return (
tuple(_make_cls(i, "in") for i in range(num_instream + num_midi + num_text)), tuple(_make_cls(i, 'in') for i in range(num_instream + num_midi + num_text)),
tuple(_make_cls(i, "out") for i in range(num_outstream + num_midi)), tuple(_make_cls(i, 'out') for i in range(num_outstream + num_midi)),
) )
@ -212,7 +212,7 @@ class Vban:
"""if VBAN disabled there can be no communication with it""" """if VBAN disabled there can be no communication with it"""
def disable(self): def disable(self):
self.remote._set_rt("vban.Enable", 0) self.remote._set_rt('vban.Enable', 0)
def vban_factory(remote) -> Vban: def vban_factory(remote) -> Vban:
@ -222,7 +222,7 @@ def vban_factory(remote) -> Vban:
Returns a class that represents the VBAN module. Returns a class that represents the VBAN module.
""" """
VBAN_cls = Vban VBAN_cls = Vban
return type(f"{VBAN_cls.__name__}", (VBAN_cls,), {})(remote) return type(f'{VBAN_cls.__name__}', (VBAN_cls,), {})(remote)
def request_vban_obj(remote) -> Vban: def request_vban_obj(remote) -> Vban:

View File

@ -11,7 +11,7 @@ from .error import VBANCMDError
from .event import Event from .event import Event
from .packet import RequestHeader from .packet import RequestHeader
from .subject import Subject from .subject import Subject
from .util import Socket, deep_merge, script from .util import deep_merge, script
from .worker import Producer, Subscriber, Updater from .worker import Producer, Subscriber, Updater
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -31,8 +31,8 @@ class VbanCmd(metaclass=ABCMeta):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
self.event = Event({k: kwargs.pop(k) for k in ("pdirty", "ldirty")}) self.event = Event({k: kwargs.pop(k) for k in ('pdirty', 'ldirty')})
if not kwargs["ip"]: if not kwargs['ip']:
kwargs |= self._conn_from_toml() kwargs |= self._conn_from_toml()
for attr, val in kwargs.items(): for attr, val in kwargs.items():
setattr(self, attr, val) setattr(self, attr, val)
@ -42,9 +42,7 @@ class VbanCmd(metaclass=ABCMeta):
bps_index=self.BPS_OPTS.index(self.bps), bps_index=self.BPS_OPTS.index(self.bps),
channel=self.channel, channel=self.channel,
) )
self.socks = tuple( self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for _ in Socket
)
self.subject = self.observer = Subject() self.subject = self.observer = Subject()
self.cache = {} self.cache = {}
self._pdirty = False self._pdirty = False
@ -65,29 +63,30 @@ class VbanCmd(metaclass=ABCMeta):
import tomli as tomllib import tomli as tomllib
def get_filepath(): def get_filepath():
filepaths = [ for pn in (
Path.cwd() / "vban.toml", Path.cwd() / 'vban.toml',
Path.cwd() / "configs" / "vban.toml", Path.cwd() / 'configs' / 'vban.toml',
Path.home() / ".config" / "vban-cmd" / "vban.toml", Path.home() / '.config' / 'vban-cmd' / 'vban.toml',
Path.home() / "Documents" / "Voicemeeter" / "configs" / "vban.toml", Path.home() / 'Documents' / 'Voicemeeter' / 'configs' / 'vban.toml',
] ):
for filepath in filepaths: if pn.exists():
if filepath.exists(): return pn
return filepath
if filepath := get_filepath(): if not (filepath := get_filepath()):
with open(filepath, "rb") as f: raise VBANCMDError('no ip provided and no vban.toml located.')
conn = tomllib.load(f) try:
assert ( with open(filepath, 'rb') as f:
"connection" in conn and "ip" in conn["connection"] return tomllib.load(f)['connection']
), "expected [connection][ip] in vban config" except tomllib.TomlDecodeError as e:
return conn["connection"] raise VBANCMDError(f'Error decoding {filepath}: {e}') from e
raise VBANCMDError("no ip provided and no vban.toml located.")
def __enter__(self): def __enter__(self):
self.login() self.login()
return self return self
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
self.logout()
def login(self) -> None: def login(self) -> None:
"""Starts the subscriber and updater threads (unless in outbound mode)""" """Starts the subscriber and updater threads (unless in outbound mode)"""
if not self.outbound: if not self.outbound:
@ -110,31 +109,41 @@ class VbanCmd(metaclass=ABCMeta):
) )
) )
def logout(self) -> None:
if not self.stopped():
self.logger.debug('events thread shutdown started')
self.stop_event.set()
if self.producer is not None:
for t in (self.producer, self.subscriber):
t.join()
self.sock.close()
self.logger.info(f'{type(self).__name__}: Successfully logged out of {self}')
def stopped(self): def stopped(self):
return self.stop_event is None or self.stop_event.is_set() return self.stop_event is None or self.stop_event.is_set()
def _set_rt(self, cmd: str, val: Union[str, float]): def _set_rt(self, cmd: str, val: Union[str, float]):
"""Sends a string request command over a network.""" """Sends a string request command over a network."""
self.socks[Socket.request].sendto( self.sock.sendto(
self.packet_request.header + f"{cmd}={val};".encode(), self.packet_request.header + f'{cmd}={val};'.encode(),
(socket.gethostbyname(self.ip), self.port), (socket.gethostbyname(self.ip), self.port),
) )
self.packet_request.framecounter = ( self.packet_request.framecounter = (
int.from_bytes(self.packet_request.framecounter, "little") + 1 int.from_bytes(self.packet_request.framecounter, 'little') + 1
).to_bytes(4, "little") ).to_bytes(4, 'little')
self.cache[cmd] = val self.cache[cmd] = val
@script @script
def sendtext(self, script): def sendtext(self, script):
"""Sends a multiple parameter string over a network.""" """Sends a multiple parameter string over a network."""
self.socks[Socket.request].sendto( self.sock.sendto(
self.packet_request.header + script.encode(), self.packet_request.header + script.encode(),
(socket.gethostbyname(self.ip), self.port), (socket.gethostbyname(self.ip), self.port),
) )
self.packet_request.framecounter = ( self.packet_request.framecounter = (
int.from_bytes(self.packet_request.framecounter, "little") + 1 int.from_bytes(self.packet_request.framecounter, 'little') + 1
).to_bytes(4, "little") ).to_bytes(4, 'little')
self.logger.debug(f"sendtext: {script}") self.logger.debug(f'sendtext: {script}')
time.sleep(self.DELAY) time.sleep(self.DELAY)
@property @property
@ -145,7 +154,7 @@ class VbanCmd(metaclass=ABCMeta):
@property @property
def version(self) -> str: def version(self) -> str:
"""Returns Voicemeeter's version as a string""" """Returns Voicemeeter's version as a string"""
return "{0}.{1}.{2}.{3}".format(*self.public_packet.voicemeeterversion) return '{0}.{1}.{2}.{3}'.format(*self.public_packet.voicemeeterversion)
@property @property
def pdirty(self): def pdirty(self):
@ -184,16 +193,16 @@ class VbanCmd(metaclass=ABCMeta):
""" """
def target(key): def target(key):
match key.split("-"): match key.split('-'):
case ["strip" | "bus" as kls, index] if index.isnumeric(): case ['strip' | 'bus' as kls, index] if index.isnumeric():
target = getattr(self, kls) target = getattr(self, kls)
case [ case [
"vban", 'vban',
"in" | "instream" | "out" | "outstream" as direction, 'in' | 'instream' | 'out' | 'outstream' as direction,
index, index,
] if index.isnumeric(): ] if index.isnumeric():
target = getattr( target = getattr(
self.vban, f"{direction.removesuffix('stream')}stream" self.vban, f'{direction.removesuffix("stream")}stream'
) )
case _: case _:
ERR_MSG = f"invalid config key '{key}'" ERR_MSG = f"invalid config key '{key}'"
@ -207,36 +216,23 @@ class VbanCmd(metaclass=ABCMeta):
"""applies a config from memory""" """applies a config from memory"""
ERR_MSG = ( ERR_MSG = (
f"No config with name '{name}' is loaded into memory", f"No config with name '{name}' is loaded into memory",
f"Known configs: {list(self.configs.keys())}", f'Known configs: {list(self.configs.keys())}',
) )
try: try:
config = self.configs[name] config = self.configs[name]
except KeyError as e: except KeyError as e:
self.logger.error(("\n").join(ERR_MSG)) self.logger.error(('\n').join(ERR_MSG))
raise VBANCMDError(("\n").join(ERR_MSG)) from e raise VBANCMDError(('\n').join(ERR_MSG)) from e
if "extends" in config: if 'extends' in config:
extended = config["extends"] extended = config['extends']
config = { config = {
k: v k: v
for k, v in deep_merge(self.configs[extended], config) for k, v in deep_merge(self.configs[extended], config)
if k not in ("extends") if k not in ('extends')
} }
self.logger.debug( self.logger.debug(
f"profile '{name}' extends '{extended}', profiles merged.." f"profile '{name}' extends '{extended}', profiles merged.."
) )
self.apply(config) self.apply(config)
self.logger.info(f"Profile '{name}' applied!") self.logger.info(f"Profile '{name}' applied!")
def logout(self) -> None:
if not self.stopped():
self.logger.debug("events thread shutdown started")
self.stop_event.set()
if self.producer is not None:
for t in (self.producer, self.subscriber):
t.join()
[sock.close() for sock in self.socks]
self.logger.info(f"{type(self).__name__}: Successfully logged out of {self}")
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
self.logout()

View File

@ -6,7 +6,6 @@ from typing import Optional
from .error import VBANCMDConnectionError from .error import VBANCMDConnectionError
from .packet import HEADER_SIZE, SubscribeHeader, VbanRtPacket, VbanRtPacketHeader from .packet import HEADER_SIZE, SubscribeHeader, VbanRtPacket, VbanRtPacketHeader
from .util import Socket
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,7 +14,7 @@ class Subscriber(threading.Thread):
"""fire a subscription packet every 10 seconds""" """fire a subscription packet every 10 seconds"""
def __init__(self, remote, stop_event): def __init__(self, remote, stop_event):
super().__init__(name="subscriber", daemon=False) super().__init__(name='subscriber', daemon=False)
self._remote = remote self._remote = remote
self.stop_event = stop_event self.stop_event = stop_event
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
@ -24,20 +23,20 @@ class Subscriber(threading.Thread):
def run(self): def run(self):
while not self.stopped(): while not self.stopped():
try: try:
self._remote.socks[Socket.register].sendto( self._remote.sock.sendto(
self.packet.header, self.packet.header,
(socket.gethostbyname(self._remote.ip), self._remote.port), (socket.gethostbyname(self._remote.ip), self._remote.port),
) )
self.packet.framecounter = ( self.packet.framecounter = (
int.from_bytes(self.packet.framecounter, "little") + 1 int.from_bytes(self.packet.framecounter, 'little') + 1
).to_bytes(4, "little") ).to_bytes(4, 'little')
self.wait_until_stopped(10) self.wait_until_stopped(10)
except socket.gaierror as e: except socket.gaierror as e:
self.logger.exception(f"{type(e).__name__}: {e}") self.logger.exception(f'{type(e).__name__}: {e}')
raise VBANCMDConnectionError( raise VBANCMDConnectionError(
f"unable to resolve hostname {self._remote.ip}" f'unable to resolve hostname {self._remote.ip}'
) from e ) from e
self.logger.debug(f"terminating {self.name} thread") self.logger.debug(f'terminating {self.name} thread')
def stopped(self): def stopped(self):
return self.stop_event.is_set() return self.stop_event.is_set()
@ -54,20 +53,17 @@ class Producer(threading.Thread):
"""Continously send job queue to the Updater thread at a rate of self._remote.ratelimit.""" """Continously send job queue to the Updater thread at a rate of self._remote.ratelimit."""
def __init__(self, remote, queue, stop_event): def __init__(self, remote, queue, stop_event):
super().__init__(name="producer", daemon=False) super().__init__(name='producer', daemon=False)
self._remote = remote self._remote = remote
self.queue = queue self.queue = queue
self.stop_event = stop_event self.stop_event = stop_event
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
self.packet_expected = VbanRtPacketHeader() self.packet_expected = VbanRtPacketHeader()
self._remote.socks[Socket.response].settimeout(self._remote.timeout) self._remote.sock.settimeout(self._remote.timeout)
self._remote.socks[Socket.response].bind(
(socket.gethostbyname(socket.gethostname()), self._remote.port)
)
self._remote._public_packet = self._get_rt() self._remote._public_packet = self._get_rt()
( (
self._remote.cache["strip_level"], self._remote.cache['strip_level'],
self._remote.cache["bus_level"], self._remote.cache['bus_level'],
) = self._remote._get_levels(self._remote.public_packet) ) = self._remote._get_levels(self._remote.public_packet)
def _get_rt(self) -> VbanRtPacket: def _get_rt(self) -> VbanRtPacket:
@ -83,7 +79,7 @@ class Producer(threading.Thread):
def _fetch_rt_packet(self) -> Optional[VbanRtPacket]: def _fetch_rt_packet(self) -> Optional[VbanRtPacket]:
try: try:
data, _ = self._remote.socks[Socket.response].recvfrom(2048) data, _ = self._remote.sock.recvfrom(2048)
# do we have packet data? # do we have packet data?
if len(data) > HEADER_SIZE: if len(data) > HEADER_SIZE:
# is the packet of type VBAN RT response? # is the packet of type VBAN RT response?
@ -114,9 +110,9 @@ class Producer(threading.Thread):
_busLabelUTF8c60=data[932:1412], _busLabelUTF8c60=data[932:1412],
) )
except TimeoutError as e: except TimeoutError as e:
self.logger.exception(f"{type(e).__name__}: {e}") self.logger.exception(f'{type(e).__name__}: {e}')
raise VBANCMDConnectionError( raise VBANCMDConnectionError(
f"timeout waiting for RtPacket from {self._remote.ip}" f'timeout waiting for RtPacket from {self._remote.ip}'
) from e ) from e
def stopped(self): def stopped(self):
@ -127,7 +123,7 @@ class Producer(threading.Thread):
_pp = self._get_rt() _pp = self._get_rt()
pdirty = _pp.pdirty(self._remote.public_packet) pdirty = _pp.pdirty(self._remote.public_packet)
ldirty = _pp.ldirty( ldirty = _pp.ldirty(
self._remote.cache["strip_level"], self._remote.cache["bus_level"] self._remote.cache['strip_level'], self._remote.cache['bus_level']
) )
if pdirty or ldirty: if pdirty or ldirty:
@ -136,11 +132,11 @@ class Producer(threading.Thread):
self._remote._ldirty = ldirty self._remote._ldirty = ldirty
if self._remote.event.pdirty: if self._remote.event.pdirty:
self.queue.put("pdirty") self.queue.put('pdirty')
if self._remote.event.ldirty: if self._remote.event.ldirty:
self.queue.put("ldirty") self.queue.put('ldirty')
time.sleep(self._remote.ratelimit) time.sleep(self._remote.ratelimit)
self.logger.debug(f"terminating {self.name} thread") self.logger.debug(f'terminating {self.name} thread')
self.queue.put(None) self.queue.put(None)
@ -152,7 +148,7 @@ class Updater(threading.Thread):
""" """
def __init__(self, remote, queue): def __init__(self, remote, queue):
super().__init__(name="updater", daemon=True) super().__init__(name='updater', daemon=True)
self._remote = remote self._remote = remote
self.queue = queue self.queue = queue
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
@ -166,19 +162,19 @@ class Updater(threading.Thread):
Generate _strip_comp, _bus_comp and update level cache if ldirty. Generate _strip_comp, _bus_comp and update level cache if ldirty.
""" """
while event := self.queue.get(): while event := self.queue.get():
if event == "pdirty" and self._remote.pdirty: if event == 'pdirty' and self._remote.pdirty:
self._remote.subject.notify(event) self._remote.subject.notify(event)
elif event == "ldirty" and self._remote.ldirty: elif event == 'ldirty' and self._remote.ldirty:
self._remote._strip_comp, self._remote._bus_comp = ( self._remote._strip_comp, self._remote._bus_comp = (
self._remote._public_packet._strip_comp, self._remote._public_packet._strip_comp,
self._remote._public_packet._bus_comp, self._remote._public_packet._bus_comp,
) )
( (
self._remote.cache["strip_level"], self._remote.cache['strip_level'],
self._remote.cache["bus_level"], self._remote.cache['bus_level'],
) = ( ) = (
self._remote._public_packet.inputlevels, self._remote._public_packet.inputlevels,
self._remote._public_packet.outputlevels, self._remote._public_packet.outputlevels,
) )
self._remote.subject.notify(event) self._remote.subject.notify(event)
self.logger.debug(f"terminating {self.name} thread") self.logger.debug(f'terminating {self.name} thread')