Compare commits

..

No commits in common. "da1d5132a82f533991b843555e75b1e9dad55531" and "01633f06dacce048a16bab354df2f19f4a5e938a" have entirely different histories.

33 changed files with 705 additions and 778 deletions

View File

@ -44,24 +44,24 @@ class ManyThings:
self.vm = vm self.vm = vm
def things(self): def things(self):
self.vm.strip[0].label = 'podmic' self.vm.strip[0].label = "podmic"
self.vm.strip[0].mute = True self.vm.strip[0].mute = True
print( print(
f'strip 0 ({self.vm.strip[0].label}) mute has been set to {self.vm.strip[0].mute}' f"strip 0 ({self.vm.strip[0].label}) mute has been set to {self.vm.strip[0].mute}"
) )
def other_things(self): def other_things(self):
self.vm.bus[3].gain = -6.3 self.vm.bus[3].gain = -6.3
self.vm.bus[4].eq.on = True self.vm.bus[4].eq.on = True
info = ( info = (
f'bus 3 gain has been set to {self.vm.bus[3].gain}', f"bus 3 gain has been set to {self.vm.bus[3].gain}",
f'bus 4 eq has been set to {self.vm.bus[4].eq.on}', f"bus 4 eq has been set to {self.vm.bus[4].eq.on}",
) )
print('\n'.join(info)) print("\n".join(info))
def main(): def main():
KIND_ID = 'banana' KIND_ID = "banana"
with voicemeeterlib.api(KIND_ID) as vm: with voicemeeterlib.api(KIND_ID) as vm:
do = ManyThings(vm) do = ManyThings(vm)
@ -71,17 +71,18 @@ def main():
# set many parameters at once # set many parameters at once
vm.apply( vm.apply(
{ {
'strip-2': {'A1': True, 'B1': True, 'gain': -6.0}, "strip-2": {"A1": True, "B1": True, "gain": -6.0},
'bus-2': {'mute': True, 'eq': {'on': True}}, "bus-2": {"mute": True, "eq": {"on": True}},
'button-0': {'state': True}, "button-0": {"state": True},
'vban-in-0': {'on': True}, "vban-in-0": {"on": True},
'vban-out-1': {'name': 'streamname'}, "vban-out-1": {"name": "streamname"},
} }
) )
if __name__ == '__main__': if __name__ == "__main__":
main() main()
``` ```
Otherwise you must remember to call `vm.login()`, `vm.logout()` at the start/end of your code. Otherwise you must remember to call `vm.login()`, `vm.logout()` at the start/end of your code.
@ -148,8 +149,8 @@ Set mute state as value for the app matching name.
example: example:
```python ```python
vm.strip[5].appmute('Spotify', True) vm.strip[5].appmute("Spotify", True)
vm.strip[5].appgain('Spotify', 0.5) vm.strip[5].appgain("Spotify", 0.5)
``` ```
#### Strip.Comp #### Strip.Comp
@ -365,7 +366,7 @@ example:
```python ```python
print(vm.strip[0].device.name) print(vm.strip[0].device.name)
vm.bus[0].device.asio = 'Audient USB Audio ASIO Driver' vm.bus[0].device.asio = "Audient USB Audio ASIO Driver"
``` ```
strip|bus device parameters are defined for physical channels only. strip|bus device parameters are defined for physical channels only.
@ -424,7 +425,7 @@ vm.recorder.B2 = False
vm.recorder.load(r'C:\music\mytune.mp3') vm.recorder.load(r'C:\music\mytune.mp3')
# set the goto time to 1m 30s # set the goto time to 1m 30s
vm.recorder.goto('00:01:30') vm.recorder.goto("00:01:30")
``` ```
#### Recorder.Mode #### Recorder.Mode
@ -678,11 +679,11 @@ get() may return None if no value for requested key in midi cache
```python ```python
vm.apply( vm.apply(
{ {
'strip-2': {'A1': True, 'B1': True, 'gain': -6.0}, "strip-2": {"A1": True, "B1": True, "gain": -6.0},
'bus-2': {'mute': True, 'eq': {'on': True}}, "bus-2": {"mute": True, "eq": {"on": True}},
'button-0': {'state': True}, "button-0": {"state": True},
'vban-in-0': {'on': True}, "vban-in-0": {"on": True},
'vban-out-1': {'name': 'streamname'}, "vban-out-1": {"name": "streamname"},
} }
) )
``` ```
@ -690,8 +691,8 @@ vm.apply(
Or for each class you may do: Or for each class you may do:
```python ```python
vm.strip[0].apply({'mute': True, 'gain': 3.2, 'A1': True}) vm.strip[0].apply({"mute": True, "gain": 3.2, "A1": True})
vm.vban.outstream[0].apply({'on': True, 'name': 'streamname', 'bit': 24}) vm.vban.outstream[0].apply({"on": True, "name": "streamname", "bit": 24})
``` ```
## Config Files ## Config Files
@ -792,7 +793,7 @@ The following methods are available:
example: example:
```python ```python
vm.event.remove(['pdirty', 'mdirty', 'midi']) vm.event.remove(["pdirty", "mdirty", "midi"])
# get a list of currently subscribed # get a list of currently subscribed
print(vm.event.get()) print(vm.event.get())
@ -862,8 +863,8 @@ import voicemeeterlib
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
with voicemeeterlib.api('banana') as vm: with voicemeeterlib.api("banana") as vm:
... ...
``` ```

View File

@ -6,24 +6,24 @@ class ManyThings:
self.vm = vm self.vm = vm
def things(self): def things(self):
self.vm.strip[0].label = 'podmic' self.vm.strip[0].label = "podmic"
self.vm.strip[0].mute = True self.vm.strip[0].mute = True
print( print(
f'strip 0 ({self.vm.strip[0].label}) mute has been set to {self.vm.strip[0].mute}' f"strip 0 ({self.vm.strip[0].label}) mute has been set to {self.vm.strip[0].mute}"
) )
def other_things(self): def other_things(self):
self.vm.bus[3].gain = -6.3 self.vm.bus[3].gain = -6.3
self.vm.bus[4].eq.on = True self.vm.bus[4].eq.on = True
info = ( info = (
f'bus 3 gain has been set to {self.vm.bus[3].gain}', f"bus 3 gain has been set to {self.vm.bus[3].gain}",
f'bus 4 eq has been set to {self.vm.bus[4].eq.on}', f"bus 4 eq has been set to {self.vm.bus[4].eq.on}",
) )
print('\n'.join(info)) print("\n".join(info))
def main(): def main():
KIND_ID = 'banana' KIND_ID = "banana"
with voicemeeterlib.api(KIND_ID) as vm: with voicemeeterlib.api(KIND_ID) as vm:
do = ManyThings(vm) do = ManyThings(vm)
@ -33,14 +33,14 @@ def main():
# set many parameters at once # set many parameters at once
vm.apply( vm.apply(
{ {
'strip-2': {'A1': True, 'B1': True, 'gain': -6.0}, "strip-2": {"A1": True, "B1": True, "gain": -6.0},
'bus-2': {'mute': True, 'eq': {'on': True}}, "bus-2": {"mute": True, "eq": {"on": True}},
'button-0': {'state': True}, "button-0": {"state": True},
'vban-in-0': {'on': True}, "vban-in-0": {"on": True},
'vban-out-1': {'name': 'streamname'}, "vban-out-1": {"name": "streamname"},
} }
) )
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -21,14 +21,14 @@ logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
argparser = argparse.ArgumentParser(description='creates a basic dsl') argparser = argparse.ArgumentParser(description="creates a basic dsl")
argparser.add_argument('-i', action='store_true') argparser.add_argument("-i", action="store_true")
args = argparser.parse_args() args = argparser.parse_args()
ParamKinds = IntEnum( ParamKinds = IntEnum(
'ParamKinds', "ParamKinds",
'bool float string', "bool float string",
) )
@ -51,12 +51,12 @@ class BoolStrategy(Strategy):
"""Convert a string representation of truth to it's numeric form.""" """Convert a string representation of truth to it's numeric form."""
val = val.lower() val = val.lower()
if val in ('y', 'yes', 't', 'true', 'on', '1'): if val in ("y", "yes", "t", "true", "on", "1"):
return 1 return 1
elif val in ('n', 'no', 'f', 'false', 'off', '0'): elif val in ("n", "no", "f", "false", "off", "0"):
return 0 return 0
else: else:
raise ValueError('invalid truth value %r' % (val,)) raise ValueError("invalid truth value %r" % (val,))
class FloatStrategy(Strategy): class FloatStrategy(Strategy):
@ -66,7 +66,7 @@ class FloatStrategy(Strategy):
class StringStrategy(Strategy): class StringStrategy(Strategy):
def run(self): def run(self):
setattr(self.target, self.param, ' '.join(self.val)) setattr(self.target, self.param, " ".join(self.val))
class Context: class Context:
@ -86,16 +86,16 @@ class Context:
class Parser: class Parser:
IS_STRING = ('label',) IS_STRING = ("label",)
def __init__(self, vm): def __init__(self, vm):
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
self.vm = vm self.vm = vm
self.kls = Group(OneOrMore(Word(alphanums))) self.kls = Group(OneOrMore(Word(alphanums)))
self.token = Suppress('->') self.token = Suppress("->")
self.param = Group(OneOrMore(Word(alphanums))) self.param = Group(OneOrMore(Word(alphanums)))
self.value = Combine( self.value = Combine(
Optional('-') + Word(nums) + Optional('.') + Optional(Word(nums)) Optional("-") + Word(nums) + Optional(".") + Optional(Word(nums))
) | Group(OneOrMore(Word(alphanums))) ) | Group(OneOrMore(Word(alphanums)))
self.event = ( self.event = (
self.kls self.kls
@ -110,7 +110,7 @@ class Parser:
res = list() res = list()
for cmd in cmds: for cmd in cmds:
self.logger.debug(f'running command: {cmd}') self.logger.debug(f"running command: {cmd}")
match cmd_parsed := self.event.parseString(cmd): match cmd_parsed := self.event.parseString(cmd):
case [[kls, index], [param]]: case [[kls, index], [param]]:
target = getattr(self.vm, kls)[int(index)] target = getattr(self.vm, kls)[int(index)]
@ -125,14 +125,13 @@ class Parser:
context = self._get_context(ParamKinds.bool, target, param, val) context = self._get_context(ParamKinds.bool, target, param, val)
context.run() context.run()
except ValueError as e: except ValueError as e:
self.logger.error(f'{e}... switching to float strategy') self.logger.error(f"{e}... switching to float strategy")
context.strategy = FloatStrategy(target, param, val) context.strategy = FloatStrategy(target, param, val)
context.run() context.run()
case [ case [
[kls, index], [kls, index],
[secondary, param], [secondary, param],
[val] [val] | val,
| val,
]: ]:
primary = getattr(self.vm, kls)[int(index)] primary = getattr(self.vm, kls)[int(index)]
target = getattr(primary, secondary) target = getattr(primary, secondary)
@ -140,12 +139,12 @@ class Parser:
context = self._get_context(ParamKinds.bool, target, param, val) context = self._get_context(ParamKinds.bool, target, param, val)
context.run() context.run()
except ValueError as e: except ValueError as e:
self.logger.error(f'{e}... switching to float strategy') self.logger.error(f"{e}... switching to float strategy")
context.strategy = FloatStrategy(target, param, val) context.strategy = FloatStrategy(target, param, val)
context.run() context.run()
case _: case _:
self.logger.error( self.logger.error(
f'unable to determine the kind of parameter from {cmd_parsed}' f"unable to determine the kind of parameter from {cmd_parsed}"
) )
time.sleep(0.05) time.sleep(0.05)
return res return res
@ -166,7 +165,7 @@ class Parser:
def interactive_mode(parser): def interactive_mode(parser):
while cmd := input('Please enter command (Press <Enter> to exit)\n'): while cmd := input("Please enter command (Press <Enter> to exit)\n"):
if res := parser.parse((cmd,)): if res := parser.parse((cmd,)):
print(res) print(res)
@ -184,7 +183,7 @@ def main():
) )
# fmt: on # fmt: on
with voicemeeterlib.api('potato') as vm: with voicemeeterlib.api("potato") as vm:
parser = Parser(vm) parser = Parser(vm)
if args.i: if args.i:
interactive_mode(parser) interactive_mode(parser)
@ -194,5 +193,5 @@ def main():
print(res) print(res)
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -22,10 +22,10 @@ class App:
self.vm.end_thread() self.vm.end_thread()
def on_pdirty(self): def on_pdirty(self):
print('pdirty!') print("pdirty!")
def on_mdirty(self): def on_mdirty(self):
print('mdirty!') print("mdirty!")
def on_ldirty(self): def on_ldirty(self):
for bus in self.vm.bus: for bus in self.vm.bus:
@ -34,20 +34,20 @@ class App:
def on_midi(self): def on_midi(self):
current = self.vm.midi.current current = self.vm.midi.current
print(f'Value of midi button {current} is {self.vm.midi.get(current)}') print(f"Value of midi button {current} is {self.vm.midi.get(current)}")
def main(): def main():
KIND_ID = 'banana' KIND_ID = "banana"
with voicemeeterlib.api(KIND_ID) as vm: with voicemeeterlib.api(KIND_ID) as vm:
with App(vm): with App(vm) as app:
for i in range(5, 0, -1): for i in range(5, 0, -1):
print(f'events start in {i} seconds') print(f"events start in {i} seconds")
time.sleep(1) time.sleep(1)
vm.event.add(['pdirty', 'ldirty', 'midi', 'mdirty']) vm.event.add(["pdirty", "ldirty", "midi", "mdirty"])
time.sleep(30) time.sleep(30)
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 voicemeeterlib import voicemeeterlib
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, vm): def __init__(self, vm):
super().__init__() super().__init__()
self.vm = vm self.vm = vm
self.title(f'{vm} - version {vm.version}') self.title(f"{vm} - version {vm.version}")
self.vm.observer.add(self.on_ldirty) self.vm.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 vm.strip[self.INDEX].mute else '#5a5a5a', foreground="#cd5c5c" if vm.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.vm.strip[self.INDEX].mute = self.button_var.get() self.vm.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 voicemeeterlib.api('banana', ldirty=True) as vm: with voicemeeterlib.api("banana", ldirty=True) as vm:
app = App(vm) app = App(vm)
app.mainloop() app.mainloop()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -7,16 +7,16 @@ logging.basicConfig(level=logging.INFO)
def main(): def main():
KIND_ID = 'potato' KIND_ID = "potato"
vm = voicemeeterlib.api(KIND_ID) vm = voicemeeterlib.api(KIND_ID)
vm.login() vm.login()
for _ in range(500): for _ in range(500):
print( print(
'\n'.join( "\n".join(
[ [
f'{vm.strip[5]}: {vm.strip[5].levels.postmute}', f"{vm.strip[5]}: {vm.strip[5].levels.postmute}",
f'{vm.bus[0]}: {vm.bus[0].levels.all}', f"{vm.bus[0]}: {vm.bus[0].levels.all}",
] ]
) )
) )
@ -24,5 +24,5 @@ def main():
vm.logout() vm.logout()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -19,7 +19,7 @@ class App:
def get_info(self): def get_info(self):
current = self.vm.midi.current current = self.vm.midi.current
print(f'Value of midi button {current} is {self.vm.midi.get(current)}') print(f"Value of midi button {current} is {self.vm.midi.get(current)}")
return current return current
def on_midi_press(self): def on_midi_press(self):
@ -30,7 +30,7 @@ class App:
and max(self.vm.strip[3].levels.postfader) > -40 and max(self.vm.strip[3].levels.postfader) > -40
): ):
print( print(
f'Strip 3 level max is greater than -40 and midi button {self.MIDI_BUTTON} is pressed' f"Strip 3 level max is greater than -40 and midi button {self.MIDI_BUTTON} is pressed"
) )
self.vm.button[self.MACROBUTTON].trigger = True self.vm.button[self.MACROBUTTON].trigger = True
else: else:
@ -38,14 +38,14 @@ class App:
def main(): def main():
KIND_ID = 'banana' KIND_ID = "banana"
with voicemeeterlib.api(KIND_ID, midi=True) as vm: with voicemeeterlib.api(KIND_ID, midi=True) as vm:
App(vm) App(vm)
while _ := input('Press <Enter> to exit\n'): while cmd := input("Press <Enter> to exit\n"):
pass pass
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -1,4 +1,4 @@
import threading import time
from logging import config from logging import config
import obsws_python as obsws import obsws_python as obsws
@ -7,43 +7,37 @@ import voicemeeterlib
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': { "loggers": {
'voicemeeterlib.iremote': {'handlers': ['stream'], 'level': 'DEBUG'} "voicemeeterlib.iremote": {"handlers": ["stream"], "level": "DEBUG"}
}, },
} }
) )
class MyClient: class MyClient:
def __init__(self, vm, stop_event): def __init__(self, vm):
self.vm = vm self.vm = vm
self._stop_event = stop_event self.client = obsws.EventClient()
self._client = obsws.EventClient() self.client.callback.register(
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.vm.strip[0].mute = True self.vm.strip[0].mute = True
@ -57,10 +51,10 @@ class MyClient:
def on_end(self): def on_end(self):
self.vm.apply( self.vm.apply(
{ {
'strip-0': {'mute': True, 'comp': {'ratio': 4.3}}, "strip-0": {"mute": True, "comp": {"ratio": 4.3}},
'strip-1': {'mute': True, 'B1': False, 'gate': {'attack': 2.3}}, "strip-1": {"mute": True, "B1": False, "gate": {"attack": 2.3}},
'strip-2': {'mute': True, 'B1': False}, "strip-2": {"mute": True, "B1": False},
'vban-in-0': {'on': False}, "vban-in-0": {"on": False},
} }
) )
@ -71,31 +65,33 @@ class MyClient:
self.vm.vban.instream[0].on = True self.vm.vban.instream[0].on = 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}")
match scene: if fn := fget(scene):
case 'START': fn()
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._stop_event.set() self.client.unsubscribe()
self.is_running = False
def main(): def main():
KIND_ID = 'potato' KIND_ID = "potato"
with voicemeeterlib.api(KIND_ID) as vm: with voicemeeterlib.api(KIND_ID) as vm:
stop_event = threading.Event() client = MyClient(vm)
while client.is_running:
with MyClient(vm, stop_event): time.sleep(0.1)
stop_event.wait()
if __name__ == '__main__': if __name__ == "__main__":
main() main()

View File

@ -37,7 +37,7 @@ def main():
) as vm: ) as vm:
App(vm) App(vm)
while _ := input("Press <Enter> to exit\n"): while cmd := input("Press <Enter> to exit\n"):
pass pass

83
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
[[package]] [[package]]
name = "cachetools" name = "cachetools"
@ -6,7 +6,6 @@ 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.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"},
{file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"},
@ -18,7 +17,6 @@ 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.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"},
{file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"},
@ -30,7 +28,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"},
@ -42,7 +39,6 @@ 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"},
@ -50,15 +46,13 @@ files = [
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
version = "1.2.2" version = "1.1.1"
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.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
] ]
[package.extras] [package.extras]
@ -70,7 +64,6 @@ version = "3.16.1"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
@ -87,7 +80,6 @@ version = "2.0.0"
description = "brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@ -99,7 +91,6 @@ version = "24.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
@ -111,7 +102,6 @@ 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"},
@ -128,7 +118,6 @@ 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.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
{file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@ -144,7 +133,6 @@ version = "0.4.0"
description = "An auxiliary library for the virtualenv-pyenv and tox-pyenv-redux plugins" description = "An auxiliary library for the virtualenv-pyenv and tox-pyenv-redux plugins"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pyenv-inspect-0.4.0.tar.gz", hash = "sha256:ec429d1d81b67ab0b08a0408414722a79d24fd1845a5b264267e44e19d8d60f0"}, {file = "pyenv-inspect-0.4.0.tar.gz", hash = "sha256:ec429d1d81b67ab0b08a0408414722a79d24fd1845a5b264267e44e19d8d60f0"},
{file = "pyenv_inspect-0.4.0-py3-none-any.whl", hash = "sha256:618683ae7d3e6db14778d58aa0fc6b3170180d944669b5d35a8aa4fb7db550d2"}, {file = "pyenv_inspect-0.4.0-py3-none-any.whl", hash = "sha256:618683ae7d3e6db14778d58aa0fc6b3170180d944669b5d35a8aa4fb7db550d2"},
@ -156,7 +144,6 @@ 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.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"}, {file = "pyproject_api-1.8.0-py3-none-any.whl", hash = "sha256:3d7d347a047afe796fd5d1885b1e391ba29be7169bd2f102fcd378f04273d228"},
{file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"}, {file = "pyproject_api-1.8.0.tar.gz", hash = "sha256:77b8049f2feb5d33eefcc21b57f1e279636277a8ac8ad6b5871037b243778496"},
@ -176,7 +163,6 @@ version = "7.4.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.7"
groups = ["dev"]
files = [ files = [
{file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
{file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
@ -195,14 +181,13 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
[[package]] [[package]]
name = "pytest-randomly" name = "pytest-randomly"
version = "3.16.0" version = "3.12.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.9" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "pytest_randomly-3.16.0-py3-none-any.whl", hash = "sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6"}, {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"},
{file = "pytest_randomly-3.16.0.tar.gz", hash = "sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26"}, {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"},
] ]
[package.dependencies] [package.dependencies]
@ -214,7 +199,6 @@ version = "0.8.6"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"]
files = [ files = [
{file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"}, {file = "ruff-0.8.6-py3-none-linux_armv6l.whl", hash = "sha256:defed167955d42c68b407e8f2e6f56ba52520e790aba4ca707a9c88619e580e3"},
{file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"}, {file = "ruff-0.8.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:54799ca3d67ae5e0b7a7ac234baa657a9c1784b48ec954a094da7c206e0365b1"},
@ -238,45 +222,13 @@ files = [
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.2.1" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.7"
groups = ["main", "dev"]
markers = "python_version < \"3.11\""
files = [ files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
{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]]
@ -285,7 +237,6 @@ 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.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"}, {file = "tox-4.23.2-py3-none-any.whl", hash = "sha256:452bc32bb031f2282881a2118923176445bac783ab97c874b8770ab4c3b76c38"},
{file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"}, {file = "tox-4.23.2.tar.gz", hash = "sha256:86075e00e555df6e82e74cfc333917f91ecb47ffbc868dcafbd2672e332f4a2c"},
@ -313,8 +264,6 @@ 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.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@ -326,7 +275,6 @@ version = "20.28.1"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"}, {file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"},
{file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"}, {file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"},
@ -347,7 +295,6 @@ version = "0.5.0"
description = "A virtualenv Python discovery plugin for pyenv-installed interpreters" description = "A virtualenv Python discovery plugin for pyenv-installed interpreters"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"]
files = [ files = [
{file = "virtualenv-pyenv-0.5.0.tar.gz", hash = "sha256:7b0e5fe3dfbdf484f4cf9b01e1f98111e398db6942237910f666356e6293597f"}, {file = "virtualenv-pyenv-0.5.0.tar.gz", hash = "sha256:7b0e5fe3dfbdf484f4cf9b01e1f98111e398db6942237910f666356e6293597f"},
{file = "virtualenv_pyenv-0.5.0-py3-none-any.whl", hash = "sha256:21750247e36c55b3c547cfdeb08f51a3867fe7129922991a4f9c96980c0a4a5d"}, {file = "virtualenv_pyenv-0.5.0-py3-none-any.whl", hash = "sha256:21750247e36c55b3c547cfdeb08f51a3867fe7129922991a4f9c96980c0a4a5d"},
@ -358,6 +305,6 @@ pyenv-inspect = ">=0.4,<0.5"
virtualenv = "*" virtualenv = "*"
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.0"
python-versions = "<4.0,>=3.10" python-versions = "^3.10"
content-hash = "fd3332b6e69588ff2902930c08a7882610bb8b18430f8b41edb11420ad2b597d" content-hash = "8bcc3eed97ab7796fa7196b74c91caa66ccabfb3a807e55f6d2c132cd4131f06"

View File

@ -1,23 +1,21 @@
[project]
name = "voicemeeter-api"
version = "2.6.1"
description = "A Python wrapper for the Voiceemeter API"
authors = [
{name = "Onyx and Iris",email = "code@onyxandiris.online"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = "<4.0,>=3.10"
dependencies = [
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
]
[tool.poetry] [tool.poetry]
name = "voicemeeter-api"
version = "2.6.0"
description = "A Python wrapper for the Voiceemeter API"
authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/onyx-and-iris/voicemeeter-api-python"
packages = [{ include = "voicemeeterlib" }] packages = [{ include = "voicemeeterlib" }]
[tool.poetry.requires-plugins] [tool.poetry.requires-plugins]
poethepoet = "^0.32.1" poethepoet = "^0.32.1"
[tool.poetry.dependencies]
python = "^3.10"
tomli = { version = "^2.0.1", python = "<3.11" }
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
pytest = "^7.4.4" pytest = "^7.4.4"
pytest-randomly = "^3.12.0" pytest-randomly = "^3.12.0"
@ -26,7 +24,7 @@ tox = "^4.23.2"
virtualenv-pyenv = "^0.5.0" virtualenv-pyenv = "^0.5.0"
[build-system] [build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poe.tasks] [tool.poe.tasks]
@ -53,22 +51,6 @@ allowlist_externals = poetry
commands = commands =
poetry install -v poetry install -v
poetry run pytest tests/ poetry run pytest tests/
[testenv:dsl]
setenv = VIRTUALENV_DISCOVERY=pyenv
allowlist_externals = poetry
deps = pyparsing
commands =
poetry install -v --without dev
poetry run python examples/dsl/
[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] [tool.ruff]
@ -119,8 +101,8 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.format] [tool.ruff.format]
# Unlike Black, use single quotes for strings. # Like Black, use double quotes for strings.
quote-style = "single" quote-style = "double"
# Like Black, indent with spaces, rather than tabs. # Like Black, indent with spaces, rather than tabs.
indent-style = "space" indent-style = "space"

View File

@ -5,7 +5,8 @@ from pathlib import Path
def ex_dsl(): def ex_dsl():
subprocess.run(["tox", "r", "-e", "dsl"]) scriptpath = Path.cwd() / "examples" / "dsl" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_events(): def ex_events():
@ -29,7 +30,8 @@ def ex_midi():
def ex_obs(): def ex_obs():
subprocess.run(["tox", "r", "-e", "obs"]) scriptpath = Path.cwd() / "examples" / "obs" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_observer(): def ex_observer():

View File

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

View File

@ -4,13 +4,13 @@ from enum import IntEnum
from math import log from math import log
from typing import Union from typing import Union
from . import kinds
from .iremote import IRemote from .iremote import IRemote
from .kinds import kinds_all
from .meta import bus_mode_prop, device_prop, float_prop from .meta import bus_mode_prop, device_prop, float_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,
) )
@ -28,85 +28,85 @@ 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 mute(self) -> bool: def mute(self) -> bool:
return self.getter('mute') == 1 return self.getter("mute") == 1
@mute.setter @mute.setter
def mute(self, val: bool): def mute(self, val: bool):
self.setter('mute', 1 if val else 0) self.setter("mute", 1 if val else 0)
@property @property
def mono(self) -> bool: def mono(self) -> bool:
return self.getter('mono') == 1 return self.getter("mono") == 1
@mono.setter @mono.setter
def mono(self, val: bool): def mono(self, val: bool):
self.setter('mono', 1 if val else 0) self.setter("mono", 1 if val else 0)
@property @property
def sel(self) -> bool: def sel(self) -> bool:
return self.getter('sel') == 1 return self.getter("sel") == 1
@sel.setter @sel.setter
def sel(self, val: bool): def sel(self, val: bool):
self.setter('sel', 1 if val else 0) self.setter("sel", 1 if val else 0)
@property @property
def label(self) -> str: def label(self) -> str:
return self.getter('Label', is_string=True) return self.getter("Label", is_string=True)
@label.setter @label.setter
def label(self, val: str): def label(self, val: str):
self.setter('Label', str(val)) self.setter("Label", str(val))
@property @property
def gain(self) -> float: def gain(self) -> float:
return round(self.getter('gain'), 1) return round(self.getter("gain"), 1)
@gain.setter @gain.setter
def gain(self, val: float): def gain(self, val: float):
self.setter('gain', val) self.setter("gain", val)
@property @property
def monitor(self) -> bool: def monitor(self) -> bool:
return self.getter('monitor') == 1 return self.getter("monitor") == 1
@monitor.setter @monitor.setter
def monitor(self, val: bool): def monitor(self, val: bool):
self.setter('monitor', 1 if val else 0) self.setter("monitor", 1 if val else 0)
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)
class BusEQ(IRemote): class BusEQ(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f'Bus[{self.index}].eq' return f"Bus[{self.index}].eq"
@property @property
def on(self) -> bool: def on(self) -> bool:
return self.getter('on') == 1 return self.getter("on") == 1
@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) -> bool: def ab(self) -> bool:
return self.getter('ab') == 1 return self.getter("ab") == 1
@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 PhysicalBus(Bus): class PhysicalBus(Bus):
@ -118,19 +118,19 @@ class PhysicalBus(Bus):
Returns a PhysicalBus class. Returns a PhysicalBus class.
""" """
kls = (cls,) kls = (cls,)
if kind.name == 'potato': if kind.name == "potato":
EFFECTS_cls = _make_effects_mixin() EFFECTS_cls = _make_effects_mixin()
kls += (EFFECTS_cls,) kls += (EFFECTS_cls,)
return type( return type(
'PhysicalBus', "PhysicalBus",
kls, kls,
{ {
'device': BusDevice.make(remote, i), "device": BusDevice.make(remote, i),
}, },
) )
def __str__(self): def __str__(self):
return f'{type(self).__name__}{self.index}' return f"{type(self).__name__}{self.index}"
class BusDevice(IRemote): class BusDevice(IRemote):
@ -142,16 +142,16 @@ class BusDevice(IRemote):
Returns a BusDevice class of a kind. Returns a BusDevice class of a kind.
""" """
DEVICE_cls = type( DEVICE_cls = type(
f'BusDevice{remote.kind}', f"BusDevice{remote.kind}",
(cls,), (cls,),
{ {
**{ **{
param: device_prop(param) param: device_prop(param)
for param in [ for param in [
'wdm', "wdm",
'ks', "ks",
'mme', "mme",
'asio', "asio",
] ]
}, },
}, },
@ -160,15 +160,15 @@ class BusDevice(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f'Bus[{self.index}].device' return f"Bus[{self.index}].device"
@property @property
def name(self) -> str: def name(self) -> str:
return self.getter('name', is_string=True) return self.getter("name", is_string=True)
@property @property
def sr(self) -> int: def sr(self) -> int:
return int(self.getter('sr')) return int(self.getter("sr"))
class VirtualBus(Bus): class VirtualBus(Bus):
@ -182,21 +182,21 @@ class VirtualBus(Bus):
Returns a VirtualBus class. Returns a VirtualBus class.
""" """
kls = (cls,) kls = (cls,)
if kind.name == 'basic': if kind.name == "basic":
return type( return type(
'VirtualBus', "VirtualBus",
kls, kls,
{ {
'device': BusDevice.make(remote, i), "device": BusDevice.make(remote, i),
}, },
) )
elif kind.name == 'potato': elif kind.name == "potato":
EFFECTS_cls = _make_effects_mixin() EFFECTS_cls = _make_effects_mixin()
kls += (EFFECTS_cls,) kls += (EFFECTS_cls,)
return type('VirtualBus', kls, {}) return type("VirtualBus", kls, {})
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):
@ -217,7 +217,7 @@ class BusLevel(IRemote):
return round(20 * log(x, 10), 1) if x > 0 else -200.0 return round(20 * log(x, 10), 1) if x > 0 else -200.0
if not self._remote.stopped() and self._remote.event.ldirty: if not self._remote.stopped() and self._remote.event.ldirty:
vals = self._remote.cache['bus_level'][self.range[0] : self.range[-1]] vals = self._remote.cache["bus_level"][self.range[0] : self.range[-1]]
else: else:
vals = [self._remote.get_level(mode, i) for i in range(*self.range)] vals = [self._remote.get_level(mode, i) for i in range(*self.range)]
@ -225,7 +225,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:
@ -248,14 +248,14 @@ def make_bus_level_map(kind):
return tuple((i, i + 8) for i in range(0, (kind.phys_out + kind.virt_out) * 8, 8)) return tuple((i, i + 8) for i in range(0, (kind.phys_out + kind.virt_out) * 8, 8))
_make_bus_level_maps = {kind.name: make_bus_level_map(kind) for kind in kinds.all} _make_bus_level_maps = {kind.name: make_bus_level_map(kind) for kind in kinds_all}
def _make_bus_mode_mixin(): def _make_bus_mode_mixin():
"""Creates a mixin of Bus Modes.""" """Creates a mixin of Bus Modes."""
def identifier(self) -> str: def identifier(self) -> str:
return f'Bus[{self.index}].mode' return f"Bus[{self.index}].mode"
def get(self) -> str: def get(self) -> str:
time.sleep(0.01) time.sleep(0.01)
@ -276,15 +276,15 @@ def _make_bus_mode_mixin():
): ):
if val: if val:
return BusModes(i + 1).name return BusModes(i + 1).name
return 'normal' return "normal"
return type( return type(
'BusModeMixin', "BusModeMixin",
(IRemote,), (IRemote,),
{ {
'identifier': property(identifier), "identifier": property(identifier),
**{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,
}, },
) )
@ -292,12 +292,12 @@ def _make_bus_mode_mixin():
def _make_effects_mixin(): def _make_effects_mixin():
"""creates an fx mixin""" """creates an fx mixin"""
return type( return type(
'FX', "FX",
(), (),
{ {
**{ **{
f'return{param}': float_prop(f'return{param}') f"return{param}": float_prop(f"return{param}")
for param in ['reverb', 'delay', 'fx1', 'fx2'] for param in ["reverb", "delay", "fx1", "fx2"]
}, },
}, },
) )
@ -316,12 +316,12 @@ def bus_factory(is_phys_bus, remote, i) -> Union[PhysicalBus, 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,),
{ {
'levels': BusLevel(remote, i), "levels": BusLevel(remote, i),
'mode': BUSMODEMIXIN_cls(remote, i), "mode": BUSMODEMIXIN_cls(remote, i),
'eq': BusEQ(remote, i), "eq": BusEQ(remote, i),
}, },
)(remote, i) )(remote, i)

View File

@ -16,7 +16,7 @@ class CBindings(metaclass=ABCMeta):
Maps expected ctype argument and res types for each binding. Maps expected ctype argument and res types for each binding.
""" """
logger_cbindings = logger.getChild('CBindings') logger_cbindings = logger.getChild("CBindings")
bind_login = libc.VBVMR_Login bind_login = libc.VBVMR_Login
bind_login.restype = LONG bind_login.restype = LONG
@ -38,17 +38,17 @@ class CBindings(metaclass=ABCMeta):
bind_get_voicemeeter_version.restype = LONG bind_get_voicemeeter_version.restype = LONG
bind_get_voicemeeter_version.argtypes = [ct.POINTER(LONG)] bind_get_voicemeeter_version.argtypes = [ct.POINTER(LONG)]
if hasattr(libc, 'VBVMR_MacroButton_IsDirty'): if hasattr(libc, "VBVMR_MacroButton_IsDirty"):
bind_macro_button_is_dirty = libc.VBVMR_MacroButton_IsDirty bind_macro_button_is_dirty = libc.VBVMR_MacroButton_IsDirty
bind_macro_button_is_dirty.restype = LONG bind_macro_button_is_dirty.restype = LONG
bind_macro_button_is_dirty.argtypes = None bind_macro_button_is_dirty.argtypes = None
if hasattr(libc, 'VBVMR_MacroButton_GetStatus'): if hasattr(libc, "VBVMR_MacroButton_GetStatus"):
bind_macro_button_get_status = libc.VBVMR_MacroButton_GetStatus bind_macro_button_get_status = libc.VBVMR_MacroButton_GetStatus
bind_macro_button_get_status.restype = LONG bind_macro_button_get_status.restype = LONG
bind_macro_button_get_status.argtypes = [LONG, ct.POINTER(FLOAT), LONG] bind_macro_button_get_status.argtypes = [LONG, ct.POINTER(FLOAT), LONG]
if hasattr(libc, 'VBVMR_MacroButton_SetStatus'): if hasattr(libc, "VBVMR_MacroButton_SetStatus"):
bind_macro_button_set_status = libc.VBVMR_MacroButton_SetStatus bind_macro_button_set_status = libc.VBVMR_MacroButton_SetStatus
bind_macro_button_set_status.restype = LONG bind_macro_button_set_status.restype = LONG
bind_macro_button_set_status.argtypes = [LONG, FLOAT, LONG] bind_macro_button_set_status.argtypes = [LONG, FLOAT, LONG]
@ -121,5 +121,5 @@ class CBindings(metaclass=ABCMeta):
raise CAPIError(func.__name__, res) raise CAPIError(func.__name__, res)
return res return res
except CAPIError as e: except CAPIError as e:
self.logger_cbindings.exception(f'{type(e).__name__}: {e}') self.logger_cbindings.exception(f"{type(e).__name__}: {e}")
raise raise

View File

@ -17,33 +17,33 @@ 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)
def __str__(self): def __str__(self):
return f'{type(self).__name__}' return f"{type(self).__name__}"
@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,72 +20,72 @@ 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_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
@ -103,10 +103,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)
@ -140,25 +140,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()
@ -181,18 +181,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' / 'voicemeeter' / kind.name, Path.home() / ".config" / "voicemeeter" / 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
@ -207,5 +207,5 @@ def request_config(kind_id: str):
try: try:
configs = loader(kindmap(kind_id)) configs = loader(kindmap(kind_id))
except KeyError as e: except KeyError as e:
raise VMError(f'Unknown Voicemeeter kind {kind_id}') from e raise VMError(f"Unknown Voicemeeter kind {kind_id}") from e
return configs return configs

View File

@ -31,8 +31,8 @@ class Adapter(IRemote):
return self._remote.get_num_devices(direction) return self._remote.get_num_devices(direction)
vals = self._remote.get_device_description(index, direction) vals = self._remote.get_device_description(index, direction)
types = {1: 'mme', 3: 'wdm', 4: 'ks', 5: 'asio'} types = {1: "mme", 3: "wdm", 4: "ks", 5: "asio"}
return {'name': vals[0], 'type': types[vals[1]], 'id': vals[2]} return {"name": vals[0], "type": types[vals[1]], "id": vals[2]}
class Device(Adapter): class Device(Adapter):
@ -47,26 +47,26 @@ class Device(Adapter):
""" """
def num_ins(cls) -> int: def num_ins(cls) -> int:
return cls.getter(direction='in') return cls.getter(direction="in")
def num_outs(cls) -> int: def num_outs(cls) -> int:
return cls.getter(direction='out') return cls.getter(direction="out")
DEVICE_cls = type( DEVICE_cls = type(
f'Device{remote.kind}', f"Device{remote.kind}",
(cls,), (cls,),
{ {
'ins': property(num_ins), "ins": property(num_ins),
'outs': property(num_outs), "outs": property(num_outs),
}, },
) )
return DEVICE_cls(remote) return DEVICE_cls(remote)
def __str__(self): def __str__(self):
return f'{type(self).__name__}' return f"{type(self).__name__}"
def input(self, index: int) -> dict: def input(self, index: int) -> dict:
return self.getter(index=index, direction='in') return self.getter(index=index, direction="in")
def output(self, index: int) -> dict: def output(self, index: int) -> dict:
return self.getter(index=index, direction='out') return self.getter(index=index, direction="out")

View File

@ -13,12 +13,12 @@ class CAPIError(VMError):
self.fn_name = fn_name self.fn_name = fn_name
self.code = code self.code = code
if self.code == -9: if self.code == -9:
message = ' '.join( message = " ".join(
( (
f'no bind for {self.fn_name}.', f"no bind for {self.fn_name}.",
'are you using an old version of the API?', "are you using an old version of the API?",
) )
) )
else: else:
message = f'{self.fn_name} returned {self.code}' message = f"{self.fn_name} returned {self.code}"
super().__init__(message) super().__init__(message)

View File

@ -12,47 +12,47 @@ 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 += ('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 mdirty(self) -> bool: def mdirty(self) -> bool:
return self.subs['mdirty'] return self.subs["mdirty"]
@mdirty.setter @mdirty.setter
def mdirty(self, val: bool): def mdirty(self, val: bool):
self.subs['mdirty'] = val self.subs["mdirty"] = val
self.info(f"mdirty {'added to' if val else 'removed from'}") self.info(f"mdirty {'added to' if val else 'removed from'}")
@property @property
def midi(self) -> bool: def midi(self) -> bool:
return self.subs['midi'] return self.subs["midi"]
@midi.setter @midi.setter
def midi(self, val: bool): def midi(self, val: bool):
self.subs['midi'] = val self.subs["midi"] = val
self.info(f"midi {'added to' if val else 'removed from'}") self.info(f"midi {'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:

View File

@ -29,8 +29,8 @@ class FactoryBuilder:
""" """
BuilderProgress = IntEnum( BuilderProgress = IntEnum(
'BuilderProgress', "BuilderProgress",
'strip bus command macrobutton vban device option recorder patch fx', "strip bus command macrobutton vban device option recorder patch fx",
start=0, start=0,
) )
@ -38,22 +38,22 @@ class FactoryBuilder:
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}",
f'Finished building device for {self._factory}', f"Finished building device for {self._factory}",
f'Finished building option for {self._factory}', f"Finished building option for {self._factory}",
f'Finished building recorder for {self._factory}', f"Finished building recorder for {self._factory}",
f'Finished building patch for {self._factory}', f"Finished building patch for {self._factory}",
f'Finished building fx for {self._factory}', f"Finished building fx 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.debug(self._info[int(getattr(self.BuilderProgress, name))]) self.logger.debug(self._info[int(getattr(self.BuilderProgress, name))])
def make_strip(self): def make_strip(self):
@ -108,17 +108,17 @@ class FactoryBase(Remote):
def __init__(self, kind_id: str, **kwargs): def __init__(self, kind_id: str, **kwargs):
defaultkwargs = { defaultkwargs = {
'sync': False, "sync": False,
'ratelimit': 0.033, "ratelimit": 0.033,
'pdirty': False, "pdirty": False,
'mdirty': False, "mdirty": False,
'midi': False, "midi": False,
'ldirty': False, "ldirty": False,
'timeout': 2, "timeout": 2,
'bits': 64, "bits": 64,
} }
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)
@ -135,7 +135,7 @@ class FactoryBase(Remote):
self._configs = None self._configs = None
def __str__(self) -> str: def __str__(self) -> str:
return f'Voicemeeter {self.kind}' return f"Voicemeeter {self.kind}"
@property @property
@abstractmethod @abstractmethod
@ -225,15 +225,15 @@ def remote_factory(kind_id: str, **kwargs) -> Remote:
Returns a Remote class of a kind Returns a Remote 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'Remote{kind_id.capitalize()}', (_factory,), {})(kind_id, **kwargs) return type(f"Remote{kind_id.capitalize()}", (_factory,), {})(kind_id, **kwargs)
def request_remote_obj(kind_id: str, **kwargs) -> Remote: def request_remote_obj(kind_id: str, **kwargs) -> Remote:
@ -243,12 +243,12 @@ def request_remote_obj(kind_id: str, **kwargs) -> Remote:
Returns a reference to a Remote class of a kind Returns a reference to a Remote class of a kind
""" """
logger_entry = logger.getChild('request_remote_obj') logger_entry = logger.getChild("request_remote_obj")
REMOTE_obj = None REMOTE_obj = None
try: try:
REMOTE_obj = remote_factory(kind_id, **kwargs) REMOTE_obj = remote_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 VMError(str(e)) from e raise VMError(str(e)) from e
return REMOTE_obj return REMOTE_obj

View File

@ -7,21 +7,21 @@ from .error import InstallError
BITS = 64 if ct.sizeof(ct.c_void_p) == 8 else 32 BITS = 64 if ct.sizeof(ct.c_void_p) == 8 else 32
if platform.system() != 'Windows': if platform.system() != "Windows":
raise InstallError('Only Windows OS supported') raise InstallError("Only Windows OS supported")
VM_KEY = 'VB:Voicemeeter {17359A74-1236-5467}' VM_KEY = "VB:Voicemeeter {17359A74-1236-5467}"
REG_KEY = '\\'.join( REG_KEY = "\\".join(
filter( filter(
None, None,
( (
'SOFTWARE', "SOFTWARE",
'WOW6432Node' if BITS == 64 else '', "WOW6432Node" if BITS == 64 else "",
'Microsoft', "Microsoft",
'Windows', "Windows",
'CurrentVersion', "CurrentVersion",
'Uninstall', "Uninstall",
), ),
) )
) )
@ -29,20 +29,20 @@ REG_KEY = '\\'.join(
def get_vmpath(): def get_vmpath():
with winreg.OpenKey( with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE, r'{}'.format('\\'.join((REG_KEY, VM_KEY))) winreg.HKEY_LOCAL_MACHINE, r"{}".format("\\".join((REG_KEY, VM_KEY)))
) as vm_key: ) as vm_key:
return winreg.QueryValueEx(vm_key, r'UninstallString')[0].strip('"') return winreg.QueryValueEx(vm_key, r"UninstallString")[0].strip('"')
try: try:
vm_parent = Path(get_vmpath()).parent vm_parent = Path(get_vmpath()).parent
except FileNotFoundError as e: except FileNotFoundError as e:
raise InstallError('Unable to fetch DLL path from the registry') from e raise InstallError("Unable to fetch DLL path from the registry") from e
DLL_NAME = f'VoicemeeterRemote{"64" if BITS == 64 else ""}.dll' DLL_NAME = f'VoicemeeterRemote{"64" if BITS == 64 else ""}.dll'
dll_path = vm_parent.joinpath(DLL_NAME) dll_path = vm_parent.joinpath(DLL_NAME)
if not dll_path.is_file(): if not dll_path.is_file():
raise InstallError(f'Could not find {dll_path}') raise InstallError(f"Could not find {dll_path}")
libc = ct.CDLL(str(dll_path)) libc = ct.CDLL(str(dll_path))

View File

@ -19,19 +19,19 @@ class IRemote(metaclass=ABCMeta):
def getter(self, param, **kwargs): def getter(self, param, **kwargs):
"""Gets a parameter value""" """Gets a parameter value"""
self.logger.debug(f'getter: {self._cmd(param)}') self.logger.debug(f"getter: {self._cmd(param)}")
return self._remote.get(self._cmd(param), **kwargs) return self._remote.get(self._cmd(param), **kwargs)
def setter(self, param, val): def setter(self, param, val):
"""Sets a parameter value""" """Sets a parameter value"""
self.logger.debug(f'setter: {self._cmd(param)}={val}') self.logger.debug(f"setter: {self._cmd(param)}={val}")
self._remote.set(self._cmd(param), val) self._remote.set(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)
@abstractmethod @abstractmethod
def identifier(self): def identifier(self):
@ -39,7 +39,7 @@ class IRemote(metaclass=ABCMeta):
def apply(self, data: dict): def apply(self, data: dict):
def fget(attr, val): def fget(attr, val):
if attr == 'mode': if attr == "mode":
return (getattr(self, attr), val, 1) return (getattr(self, attr), val, 1)
return (self, attr, val) return (self, attr, val)
@ -49,7 +49,7 @@ class IRemote(metaclass=ABCMeta):
target, attr, val = fget(attr, val) target, attr, val = fget(attr, val)
setattr(target, attr, val) setattr(target, attr, val)
else: else:
self.logger.error(f'invalid attribute {attr} for {self}') self.logger.error(f"invalid attribute {attr} for {self}")
else: else:
target = getattr(self, attr) target = getattr(self, attr)
target.apply(val) target.apply(val)

View File

@ -22,7 +22,7 @@ class SingletonType(type):
return cls._instances[cls] return cls._instances[cls]
@dataclass(frozen=True) @dataclass
class KindMapClass(metaclass=SingletonType): class KindMapClass(metaclass=SingletonType):
name: str name: str
ins: tuple ins: tuple
@ -68,8 +68,9 @@ class KindMapClass(metaclass=SingletonType):
return self.name.capitalize() return self.name.capitalize()
@dataclass(frozen=True) @dataclass
class BasicMap(KindMapClass): class BasicMap(KindMapClass):
name: str
ins: tuple = (2, 1) ins: tuple = (2, 1)
outs: tuple = (1, 1) outs: tuple = (1, 1)
vban: tuple = (4, 4, 1, 1) vban: tuple = (4, 4, 1, 1)
@ -78,8 +79,9 @@ class BasicMap(KindMapClass):
composite: int = 0 composite: int = 0
@dataclass(frozen=True) @dataclass
class BananaMap(KindMapClass): class BananaMap(KindMapClass):
name: str
ins: tuple = (3, 2) ins: tuple = (3, 2)
outs: tuple = (3, 2) outs: tuple = (3, 2)
vban: tuple = (8, 8, 1, 1) vban: tuple = (8, 8, 1, 1)
@ -88,8 +90,9 @@ class BananaMap(KindMapClass):
composite: int = 8 composite: int = 8
@dataclass(frozen=True) @dataclass
class PotatoMap(KindMapClass): class PotatoMap(KindMapClass):
name: str
ins: tuple = (5, 3) ins: tuple = (5, 3)
outs: tuple = (5, 3) outs: tuple = (5, 3)
vban: tuple = (8, 8, 1, 1) vban: tuple = (8, 8, 1, 1)
@ -100,14 +103,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)
@ -120,4 +123,4 @@ def request_kind_map(kind_id):
return KIND_obj return KIND_obj
all = kinds_all = [request_kind_map(kind_id.name.lower()) for kind_id in KindId] kinds_all = list(request_kind_map(kind_id.name.lower()) for kind_id in KindId)

View File

@ -3,8 +3,8 @@ from enum import IntEnum
from .iremote import IRemote from .iremote import IRemote
ButtonModes = IntEnum( ButtonModes = IntEnum(
'ButtonModes', "ButtonModes",
'state stateonly trigger', "state stateonly trigger",
start=1, start=1,
) )
@ -16,12 +16,12 @@ class Adapter(IRemote):
pass pass
def getter(self, mode): def getter(self, mode):
self.logger.debug(f'getter: button[{self.index}].{ButtonModes(mode).name}') self.logger.debug(f"getter: button[{self.index}].{ButtonModes(mode).name}")
return self._remote.get_buttonstatus(self.index, mode) return self._remote.get_buttonstatus(self.index, mode)
def setter(self, mode, val): def setter(self, mode, val):
self.logger.debug( self.logger.debug(
f'setter: button[{self.index}].{ButtonModes(mode).name}={val}' f"setter: button[{self.index}].{ButtonModes(mode).name}={val}"
) )
self._remote.set_buttonstatus(self.index, val, mode) self._remote.set_buttonstatus(self.index, val, mode)
@ -30,7 +30,7 @@ class MacroButton(Adapter):
"""Defines concrete implementation for macrobutton""" """Defines concrete implementation for macrobutton"""
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 state(self) -> bool: def state(self) -> bool:

View File

@ -1,48 +1,48 @@
from typing import Optional from typing import Optional
from . import kinds
from .iremote import IRemote from .iremote import IRemote
from .kinds import kinds_all
class FX(IRemote): class FX(IRemote):
def __str__(self): def __str__(self):
return f'{type(self).__name__}' return f"{type(self).__name__}"
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'FX' return "FX"
@property @property
def reverb(self) -> bool: def reverb(self) -> bool:
return self.getter('reverb.On') == 1 return self.getter("reverb.On") == 1
@reverb.setter @reverb.setter
def reverb(self, val: bool): def reverb(self, val: bool):
self.setter('reverb.On', 1 if val else 0) self.setter("reverb.On", 1 if val else 0)
@property @property
def reverb_ab(self) -> bool: def reverb_ab(self) -> bool:
return self.getter('reverb.ab') == 1 return self.getter("reverb.ab") == 1
@reverb_ab.setter @reverb_ab.setter
def reverb_ab(self, val: bool): def reverb_ab(self, val: bool):
self.setter('reverb.ab', 1 if val else 0) self.setter("reverb.ab", 1 if val else 0)
@property @property
def delay(self) -> bool: def delay(self) -> bool:
return self.getter('delay.On') == 1 return self.getter("delay.On") == 1
@delay.setter @delay.setter
def delay(self, val: bool): def delay(self, val: bool):
self.setter('delay.On', 1 if val else 0) self.setter("delay.On", 1 if val else 0)
@property @property
def delay_ab(self) -> bool: def delay_ab(self) -> bool:
return self.getter('delay.ab') == 1 return self.getter("delay.ab") == 1
@delay_ab.setter @delay_ab.setter
def delay_ab(self, val: bool): def delay_ab(self, val: bool):
self.setter('delay.ab', 1 if val else 0) self.setter("delay.ab", 1 if val else 0)
class Patch(IRemote): class Patch(IRemote):
@ -57,50 +57,50 @@ class Patch(IRemote):
""" """
ASIO_cls = _make_asio_mixins(remote)[remote.kind.name] ASIO_cls = _make_asio_mixins(remote)[remote.kind.name]
return type( return type(
f'Patch{remote.kind}', f"Patch{remote.kind}",
(cls, ASIO_cls), (cls, ASIO_cls),
{ {
'composite': tuple(Composite(remote, i) for i in range(8)), "composite": tuple(Composite(remote, i) for i in range(8)),
'insert': tuple(Insert(remote, i) for i in range(remote.kind.insert)), "insert": tuple(Insert(remote, i) for i in range(remote.kind.insert)),
}, },
)(remote) )(remote)
def __str__(self): def __str__(self):
return f'{type(self).__name__}' return f"{type(self).__name__}"
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'patch' return "patch"
@property @property
def postfadercomp(self) -> bool: def postfadercomp(self) -> bool:
return self.getter('postfadercomposite') == 1 return self.getter("postfadercomposite") == 1
@postfadercomp.setter @postfadercomp.setter
def postfadercomp(self, val: bool): def postfadercomp(self, val: bool):
self.setter('postfadercomposite', 1 if val else 0) self.setter("postfadercomposite", 1 if val else 0)
@property @property
def postfxinsert(self) -> bool: def postfxinsert(self) -> bool:
return self.getter('postfxinsert') == 1 return self.getter("postfxinsert") == 1
@postfxinsert.setter @postfxinsert.setter
def postfxinsert(self, val: bool): def postfxinsert(self, val: bool):
self.setter('postfxinsert', 1 if val else 0) self.setter("postfxinsert", 1 if val else 0)
class Asio(IRemote): class Asio(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'patch' return "patch"
class AsioIn(Asio): class AsioIn(Asio):
def get(self) -> int: def get(self) -> int:
return int(self.getter(f'asio[{self.index}]')) return int(self.getter(f"asio[{self.index}]"))
def set(self, val: int): def set(self, val: int):
self.setter(f'asio[{self.index}]', val) self.setter(f"asio[{self.index}]", val)
class AsioOut(Asio): class AsioOut(Asio):
@ -109,10 +109,10 @@ class AsioOut(Asio):
self._param = param self._param = param
def get(self) -> int: def get(self) -> int:
return int(self.getter(f'out{self._param}[{self.index}]')) return int(self.getter(f"out{self._param}[{self.index}]"))
def set(self, val: int): def set(self, val: int):
self.setter(f'out{self._param}[{self.index}]', val) self.setter(f"out{self._param}[{self.index}]", val)
def _make_asio_mixin(remote, kind): def _make_asio_mixin(remote, kind):
@ -120,46 +120,46 @@ def _make_asio_mixin(remote, kind):
asio_in, asio_out = kind.asio asio_in, asio_out = kind.asio
return type( return type(
f'ASIO{kind}', f"ASIO{kind}",
(IRemote,), (IRemote,),
{ {
'asio': tuple(AsioIn(remote, i) for i in range(asio_in)), "asio": tuple(AsioIn(remote, i) for i in range(asio_in)),
**{ **{
param: tuple(AsioOut(remote, i, param) for i in range(asio_out)) param: tuple(AsioOut(remote, i, param) for i in range(asio_out))
for param in ['A2', 'A3', 'A4', 'A5'] for param in ["A2", "A3", "A4", "A5"]
}, },
}, },
) )
def _make_asio_mixins(remote): def _make_asio_mixins(remote):
return {kind.name: _make_asio_mixin(remote, kind) for kind in kinds.all} return {kind.name: _make_asio_mixin(remote, kind) for kind in kinds_all}
class Composite(IRemote): class Composite(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'patch' return "patch"
def get(self) -> int: def get(self) -> int:
return int(self.getter(f'composite[{self.index}]')) return int(self.getter(f"composite[{self.index}]"))
def set(self, val: int): def set(self, val: int):
self.setter(f'composite[{self.index}]', val) self.setter(f"composite[{self.index}]", val)
class Insert(IRemote): class Insert(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'patch' return "patch"
@property @property
def on(self) -> bool: def on(self) -> bool:
return self.getter(f'insert[{self.index}]') == 1 return self.getter(f"insert[{self.index}]") == 1
@on.setter @on.setter
def on(self, val: bool): def on(self, val: bool):
self.setter(f'insert[{self.index}]', 1 if val else 0) self.setter(f"insert[{self.index}]", 1 if val else 0)
class Option(IRemote): class Option(IRemote):
@ -173,61 +173,61 @@ class Option(IRemote):
Returns a Option class of a kind. Returns a Option class of a kind.
""" """
return type( return type(
f'Option{remote.kind}', f"Option{remote.kind}",
(cls,), (cls,),
{ {
'delay': tuple(Delay(remote, i) for i in range(remote.kind.phys_out)), "delay": tuple(Delay(remote, i) for i in range(remote.kind.phys_out)),
}, },
)(remote) )(remote)
def __str__(self): def __str__(self):
return f'{type(self).__name__}' return f"{type(self).__name__}"
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'option' return "option"
@property @property
def sr(self) -> int: def sr(self) -> int:
return int(self.getter('sr')) return int(self.getter("sr"))
@sr.setter @sr.setter
def sr(self, val: int): def sr(self, val: int):
opts = (44100, 48000, 88200, 96000, 176400, 192000) opts = (44100, 48000, 88200, 96000, 176400, 192000)
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 asiosr(self) -> bool: def asiosr(self) -> bool:
return self.getter('asiosr') == 1 return self.getter("asiosr") == 1
@asiosr.setter @asiosr.setter
def asiosr(self, val: bool): def asiosr(self, val: bool):
self.setter('asiosr', 1 if val else 0) self.setter("asiosr", 1 if val else 0)
@property @property
def monitoronsel(self) -> bool: def monitoronsel(self) -> bool:
return self.getter('monitoronsel') == 1 return self.getter("monitoronsel") == 1
@monitoronsel.setter @monitoronsel.setter
def monitoronsel(self, val: bool): def monitoronsel(self, val: bool):
self.setter('monitoronsel', 1 if val else 0) self.setter("monitoronsel", 1 if val else 0)
def buffer(self, driver, buffer): def buffer(self, driver, buffer):
self.setter(f'buffer.{driver}', buffer) self.setter(f"buffer.{driver}", buffer)
class Delay(IRemote): class Delay(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'option' return "option"
def get(self) -> int: def get(self) -> int:
return int(self.getter(f'delay[{self.index}]')) return int(self.getter(f"delay[{self.index}]"))
def set(self, val: int): def set(self, val: int):
self.setter(f'delay[{self.index}]', val) self.setter(f"delay[{self.index}]", val)
class Midi: class Midi:

View File

@ -1,8 +1,8 @@
import re import re
from . import kinds
from .error import VMError from .error import VMError
from .iremote import IRemote from .iremote import IRemote
from .kinds import kinds_all
from .meta import action_fn, bool_prop from .meta import action_fn, bool_prop
@ -23,91 +23,91 @@ class Recorder(IRemote):
CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name] CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name]
ARMCHANNELMIXIN_cls = _make_armchannel_mixins(remote)[remote.kind.name] ARMCHANNELMIXIN_cls = _make_armchannel_mixins(remote)[remote.kind.name]
REC_cls = type( REC_cls = type(
f'Recorder{remote.kind}', f"Recorder{remote.kind}",
(cls, CHANNELOUTMIXIN_cls, ARMCHANNELMIXIN_cls), (cls, CHANNELOUTMIXIN_cls, ARMCHANNELMIXIN_cls),
{ {
**{ **{
param: action_fn(param) param: action_fn(param)
for param in [ for param in [
'play', "play",
'stop', "stop",
'pause', "pause",
'replay', "replay",
'record', "record",
'ff', "ff",
'rew', "rew",
] ]
}, },
'mode': RecorderMode(remote), "mode": RecorderMode(remote),
}, },
) )
return REC_cls(remote) return REC_cls(remote)
def __str__(self): def __str__(self):
return f'{type(self).__name__}' return f"{type(self).__name__}"
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return 'recorder' return "recorder"
@property @property
def samplerate(self) -> int: def samplerate(self) -> int:
return int(self.getter('samplerate')) return int(self.getter("samplerate"))
@samplerate.setter @samplerate.setter
def samplerate(self, val: int): def samplerate(self, val: int):
opts = (22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000) opts = (22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000)
if val not in opts: if val not in opts:
self.logger.warning(f'samplerate got: {val} but expected a value in {opts}') self.logger.warning(f"samplerate got: {val} but expected a value in {opts}")
self.setter('samplerate', val) self.setter("samplerate", val)
@property @property
def bitresolution(self) -> int: def bitresolution(self) -> int:
return int(self.getter('bitresolution')) return int(self.getter("bitresolution"))
@bitresolution.setter @bitresolution.setter
def bitresolution(self, val: int): def bitresolution(self, val: int):
opts = (8, 16, 24, 32) opts = (8, 16, 24, 32)
if val not in opts: if val not in opts:
self.logger.warning( self.logger.warning(
f'bitresolution got: {val} but expected a value in {opts}' f"bitresolution got: {val} but expected a value in {opts}"
) )
self.setter('bitresolution', val) self.setter("bitresolution", val)
@property @property
def channel(self) -> int: def channel(self) -> int:
return int(self.getter('channel')) return int(self.getter("channel"))
@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 kbps(self): def kbps(self):
return int(self.getter('kbps')) return int(self.getter("kbps"))
@kbps.setter @kbps.setter
def kbps(self, val: int): def kbps(self, val: int):
opts = (32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320) opts = (32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320)
if val not in opts: if val not in opts:
self.logger.warning(f'kbps got: {val} but expected a value in {opts}') self.logger.warning(f"kbps got: {val} but expected a value in {opts}")
self.setter('kbps', val) self.setter("kbps", val)
@property @property
def gain(self) -> float: def gain(self) -> float:
return round(self.getter('gain'), 1) return round(self.getter("gain"), 1)
@gain.setter @gain.setter
def gain(self, val: float): def gain(self, val: float):
self.setter('gain', val) self.setter("gain", val)
def load(self, file: str): def load(self, file: str):
try: try:
self.setter('load', file) self.setter("load", file)
except UnicodeError: except UnicodeError:
raise VMError('File full directory must be a raw string') raise VMError("File full directory must be a raw string")
# loop forwarder methods, for backwards compatibility # loop forwarder methods, for backwards compatibility
@property @property
@ -121,69 +121,69 @@ class Recorder(IRemote):
def goto(self, time_str): def goto(self, time_str):
def get_sec(): def get_sec():
"""Get seconds from time string""" """Get seconds from time string"""
h, m, s = time_str.split(':') h, m, s = time_str.split(":")
return int(h) * 3600 + int(m) * 60 + int(s) return int(h) * 3600 + int(m) * 60 + int(s)
time_str = str(time_str) # coerce the type time_str = str(time_str) # coerce the type
if ( if (
re.match( re.match(
r'^(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)$', r"^(?:[01]\d|2[0123]):(?:[012345]\d):(?:[012345]\d)$",
time_str, time_str,
) )
is not None is not None
): ):
self.setter('goto', get_sec()) self.setter("goto", get_sec())
else: else:
self.logger.warning( self.logger.warning(
"goto expects a string that matches the format 'hh:mm:ss'" "goto expects a string that matches the format 'hh:mm:ss'"
) )
def filetype(self, val: str): def filetype(self, val: str):
opts = {'wav': 1, 'aiff': 2, 'bwf': 3, 'mp3': 100} opts = {"wav": 1, "aiff": 2, "bwf": 3, "mp3": 100}
try: try:
self.setter('filetype', opts[val.lower()]) self.setter("filetype", opts[val.lower()])
except KeyError: except KeyError:
self.logger.warning( self.logger.warning(
f'filetype got: {val} but expected a value in {list(opts.keys())}' f"filetype got: {val} but expected a value in {list(opts.keys())}"
) )
class RecorderMode(IRemote): class RecorderMode(IRemote):
@property @property
def identifier(self): def identifier(self):
return 'recorder.mode' return "recorder.mode"
@property @property
def recbus(self) -> bool: def recbus(self) -> bool:
return self.getter('recbus') == 1 return self.getter("recbus") == 1
@recbus.setter @recbus.setter
def recbus(self, val: bool): def recbus(self, val: bool):
self.setter('recbus', 1 if val else 0) self.setter("recbus", 1 if val else 0)
@property @property
def playonload(self) -> bool: def playonload(self) -> bool:
return self.getter('playonload') == 1 return self.getter("playonload") == 1
@playonload.setter @playonload.setter
def playonload(self, val: bool): def playonload(self, val: bool):
self.setter('playonload', 1 if val else 0) self.setter("playonload", 1 if val else 0)
@property @property
def loop(self) -> bool: def loop(self) -> bool:
return self.getter('loop') == 1 return self.getter("loop") == 1
@loop.setter @loop.setter
def loop(self, val: bool): def loop(self, val: bool):
self.setter('loop', 1 if val else 0) self.setter("loop", 1 if val else 0)
@property @property
def multitrack(self) -> bool: def multitrack(self) -> bool:
return self.getter('multitrack') == 1 return self.getter("multitrack") == 1
@multitrack.setter @multitrack.setter
def multitrack(self, val: bool): def multitrack(self, val: bool):
self.setter('multitrack', 1 if val else 0) self.setter("multitrack", 1 if val else 0)
class RecorderArmChannel(IRemote): class RecorderArmChannel(IRemote):
@ -192,51 +192,51 @@ class RecorderArmChannel(IRemote):
self._i = i self._i = i
def set(self, val: bool): def set(self, val: bool):
self.setter('', 1 if val else 0) self.setter("", 1 if val else 0)
class RecorderArmStrip(RecorderArmChannel): class RecorderArmStrip(RecorderArmChannel):
@property @property
def identifier(self): def identifier(self):
return f'recorder.armstrip[{self._i}]' return f"recorder.armstrip[{self._i}]"
class RecorderArmBus(RecorderArmChannel): class RecorderArmBus(RecorderArmChannel):
@property @property
def identifier(self): def identifier(self):
return f'recorder.armbus[{self._i}]' return f"recorder.armbus[{self._i}]"
def _make_armchannel_mixin(remote, kind): def _make_armchannel_mixin(remote, kind):
"""Creates an armchannel out mixin""" """Creates an armchannel out mixin"""
return type( return type(
f'ArmChannelMixin{kind}', f"ArmChannelMixin{kind}",
(), (),
{ {
'armstrip': tuple( "armstrip": tuple(
RecorderArmStrip(remote, i) for i in range(kind.num_strip) RecorderArmStrip(remote, i) for i in range(kind.num_strip)
), ),
'armbus': tuple(RecorderArmBus(remote, i) for i in range(kind.num_bus)), "armbus": tuple(RecorderArmBus(remote, i) for i in range(kind.num_bus)),
}, },
) )
def _make_armchannel_mixins(remote): def _make_armchannel_mixins(remote):
return {kind.name: _make_armchannel_mixin(remote, kind) for kind in kinds.all} return {kind.name: _make_armchannel_mixin(remote, kind) for kind in kinds_all}
def _make_channelout_mixin(kind): def _make_channelout_mixin(kind):
"""Creates a channel out mixin""" """Creates a channel out mixin"""
return type( return type(
f'ChannelOutMixin{kind}', f"ChannelOutMixin{kind}",
(), (),
{ {
**{f'A{i}': bool_prop(f'A{i}') for i in range(1, kind.phys_out + 1)}, **{f"A{i}": bool_prop(f"A{i}") for i in range(1, kind.phys_out + 1)},
**{f'B{i}': bool_prop(f'B{i}') for i in range(1, kind.virt_out + 1)}, **{f"B{i}": bool_prop(f"B{i}") for i in range(1, kind.virt_out + 1)},
}, },
) )
_make_channelout_mixins = { _make_channelout_mixins = {
kind.name: _make_channelout_mixin(kind) for kind in kinds.all kind.name: _make_channelout_mixin(kind) for kind in kinds_all
} }

View File

@ -30,7 +30,7 @@ class Remote(CBindings):
self.midi = Midi() self.midi = Midi()
self.subject = self.observer = Subject() self.subject = self.observer = Subject()
self.event = Event( self.event = Event(
{k: kwargs.pop(k) for k in ('pdirty', 'mdirty', 'midi', 'ldirty')} {k: kwargs.pop(k) for k in ("pdirty", "mdirty", "midi", "ldirty")}
) )
self.gui = VmGui() self.gui = VmGui()
self.stop_event = None self.stop_event = None
@ -41,7 +41,7 @@ class Remote(CBindings):
if self.bits not in (32, 64): if self.bits not in (32, 64):
self.logger.warning( self.logger.warning(
f'kwarg bits got {self.bits}, expected either 32 or 64, defaulting to 64' f"kwarg bits got {self.bits}, expected either 32 or 64, defaulting to 64"
) )
self.bits = 64 self.bits = 64
@ -61,7 +61,7 @@ class Remote(CBindings):
"""Starts updates thread.""" """Starts updates thread."""
self.event.info() self.event.info()
self.logger.debug('initiating events thread') self.logger.debug("initiating events thread")
self.stop_event = threading.Event() self.stop_event = threading.Event()
self.stop_event.clear() self.stop_event.clear()
queue = Queue() queue = Queue()
@ -79,7 +79,7 @@ class Remote(CBindings):
self.gui.launched = self.call(self.bind_login, ok=(0, 1)) == 0 self.gui.launched = self.call(self.bind_login, ok=(0, 1)) == 0
if not self.gui.launched: if not self.gui.launched:
self.logger.info( self.logger.info(
'Voicemeeter engine running but GUI not launched. Launching the GUI now.' "Voicemeeter engine running but GUI not launched. Launching the GUI now."
) )
self.run_voicemeeter(self.kind.name) self.run_voicemeeter(self.kind.name)
@ -103,7 +103,7 @@ class Remote(CBindings):
"""Returns Voicemeeter's version as a string""" """Returns Voicemeeter's version as a string"""
ver = ct.c_long() ver = ct.c_long()
self.call(self.bind_get_voicemeeter_version, ct.byref(ver)) self.call(self.bind_get_voicemeeter_version, ct.byref(ver))
return '{}.{}.{}.{}'.format( return "{}.{}.{}.{}".format(
(ver.value & 0xFF000000) >> 24, (ver.value & 0xFF000000) >> 24,
(ver.value & 0x00FF0000) >> 16, (ver.value & 0x00FF0000) >> 16,
(ver.value & 0x0000FF00) >> 8, (ver.value & 0x0000FF00) >> 8,
@ -121,16 +121,16 @@ class Remote(CBindings):
try: try:
return self.call(self.bind_macro_button_is_dirty, ok=(0, 1)) == 1 return self.call(self.bind_macro_button_is_dirty, ok=(0, 1)) == 1
except AttributeError as e: except AttributeError as e:
self.logger.exception(f'{type(e).__name__}: {e}') self.logger.exception(f"{type(e).__name__}: {e}")
raise CAPIError('VBVMR_MacroButton_IsDirty', -9) from e raise CAPIError("VBVMR_MacroButton_IsDirty", -9) from e
@property @property
def ldirty(self) -> bool: def ldirty(self) -> bool:
"""True iff levels have been updated.""" """True iff levels have been updated."""
self._strip_buf, self._bus_buf = self._get_levels() self._strip_buf, self._bus_buf = self._get_levels()
return not ( return not (
self.cache.get('strip_level') == self._strip_buf self.cache.get("strip_level") == self._strip_buf
and self.cache.get('bus_level') == self._bus_buf and self.cache.get("bus_level") == self._bus_buf
) )
def clear_dirty(self) -> None: def clear_dirty(self) -> None:
@ -138,9 +138,9 @@ class Remote(CBindings):
while self.pdirty or self.mdirty: while self.pdirty or self.mdirty:
pass pass
except CAPIError as e: except CAPIError as e:
if not (e.fn_name == 'VBVMR_MacroButton_IsDirty' and e.code == -9): if not (e.fn_name == "VBVMR_MacroButton_IsDirty" and e.code == -9):
raise raise
self.logger.error(f'{e} clearing pdirty only.') self.logger.error(f"{e} clearing pdirty only.")
while self.pdirty: while self.pdirty:
pass pass
@ -159,7 +159,7 @@ class Remote(CBindings):
"""Sets a string or float parameter. Caches value""" """Sets a string or float parameter. Caches value"""
if isinstance(val, str): if isinstance(val, str):
if len(val) >= 512: if len(val) >= 512:
raise VMError('String is too long') raise VMError("String is too long")
self.call( self.call(
self.bind_set_parameter_string_w, param.encode(), ct.c_wchar_p(val) self.bind_set_parameter_string_w, param.encode(), ct.c_wchar_p(val)
) )
@ -181,8 +181,8 @@ class Remote(CBindings):
ct.c_long(mode), ct.c_long(mode),
) )
except AttributeError as e: except AttributeError as e:
self.logger.exception(f'{type(e).__name__}: {e}') self.logger.exception(f"{type(e).__name__}: {e}")
raise CAPIError('VBVMR_MacroButton_GetStatus', -9) from e raise CAPIError("VBVMR_MacroButton_GetStatus", -9) from e
return int(c_state.value) return int(c_state.value)
def set_buttonstatus(self, id_: int, val: int, mode: int) -> None: def set_buttonstatus(self, id_: int, val: int, mode: int) -> None:
@ -196,26 +196,26 @@ class Remote(CBindings):
ct.c_long(mode), ct.c_long(mode),
) )
except AttributeError as e: except AttributeError as e:
self.logger.exception(f'{type(e).__name__}: {e}') self.logger.exception(f"{type(e).__name__}: {e}")
raise CAPIError('VBVMR_MacroButton_SetStatus', -9) from e raise CAPIError("VBVMR_MacroButton_SetStatus", -9) from e
self.cache[f'mb_{id_}_{mode}'] = int(c_state.value) self.cache[f"mb_{id_}_{mode}"] = int(c_state.value)
def get_num_devices(self, direction: str = None) -> int: def get_num_devices(self, direction: str = None) -> int:
"""Retrieves number of physical devices connected""" """Retrieves number of physical devices connected"""
if direction not in ('in', 'out'): if direction not in ("in", "out"):
raise VMError('Expected a direction: in or out') raise VMError("Expected a direction: in or out")
func = getattr(self, f'bind_{direction}put_get_device_number') func = getattr(self, f"bind_{direction}put_get_device_number")
res = self.call(func, ok_exp=lambda r: r >= 0) res = self.call(func, ok_exp=lambda r: r >= 0)
return res return res
def get_device_description(self, index: int, direction: str = None) -> tuple: def get_device_description(self, index: int, direction: str = None) -> tuple:
"""Returns a tuple of device parameters""" """Returns a tuple of device parameters"""
if direction not in ('in', 'out'): if direction not in ("in", "out"):
raise VMError('Expected a direction: in or out') raise VMError("Expected a direction: in or out")
type_ = ct.c_long() type_ = ct.c_long()
name = ct.create_unicode_buffer(256) name = ct.create_unicode_buffer(256)
hwid = ct.create_unicode_buffer(256) hwid = ct.create_unicode_buffer(256)
func = getattr(self, f'bind_{direction}put_get_device_desc_w') func = getattr(self, f"bind_{direction}put_get_device_desc_w")
self.call( self.call(
func, func,
ct.c_long(index), ct.c_long(index),
@ -257,7 +257,7 @@ class Remote(CBindings):
) )
if res > 0: if res > 0:
vals = tuple( vals = tuple(
grouper(3, (int.from_bytes(buf[i], 'little') for i in range(res))) grouper(3, (int.from_bytes(buf[i], "little") for i in range(res)))
) )
for msg in vals: for msg in vals:
ch, pitch, vel = msg ch, pitch, vel = msg
@ -271,7 +271,7 @@ class Remote(CBindings):
def sendtext(self, script: str): def sendtext(self, script: str):
"""Sets many parameters from a script""" """Sets many parameters from a script"""
if len(script) > 48000: if len(script) > 48000:
raise ValueError('Script too large, max size 48kB') raise ValueError("Script too large, max size 48kB")
self.call(self.bind_set_parameters, script.encode()) self.call(self.bind_set_parameters, script.encode())
time.sleep(self.DELAY * 5) time.sleep(self.DELAY * 5)
@ -283,15 +283,12 @@ class Remote(CBindings):
""" """
def target(key): def target(key):
match key.split('-'): match key.split("-"):
case ['strip' | 'bus' | 'button' as kls, index] if index.isnumeric(): case ["strip" | "bus" | "button" as kls, index] if index.isnumeric():
target = getattr(self, kls) target = getattr(self, kls)
case [ case [
'vban', "vban",
'in' "in" | "instream" | "out" | "outstream" as direction,
| 'instream'
| 'out'
| 'outstream' as direction,
index, index,
] if index.isnumeric(): ] if index.isnumeric():
target = getattr( target = getattr(
@ -309,20 +306,20 @@ class Remote(CBindings):
"""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 VMError(('\n').join(ERR_MSG)) from e raise VMError(("\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.."
@ -332,7 +329,7 @@ class Remote(CBindings):
def end_thread(self): def end_thread(self):
if not self.stopped(): if not self.stopped():
self.logger.debug('events thread shutdown started') self.logger.debug("events thread shutdown started")
self.stop_event.set() self.stop_event.set()
self.producer.join() # wait for producer thread to complete cycle self.producer.join() # wait for producer thread to complete cycle
@ -340,7 +337,7 @@ class Remote(CBindings):
"""Logout of the API""" """Logout of the API"""
time.sleep(0.1) time.sleep(0.1)
self.call(self.bind_logout) self.call(self.bind_logout)
self.logger.info(f'{type(self).__name__}: Successfully logged out of {self}') self.logger.info(f"{type(self).__name__}: Successfully logged out of {self}")
def __exit__(self, exc_type, exc_value, exc_traceback) -> None: def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
"""teardown procedures""" """teardown procedures"""

View File

@ -3,8 +3,8 @@ from abc import abstractmethod
from math import log from math import log
from typing import Union from typing import Union
from . import kinds
from .iremote import IRemote from .iremote import IRemote
from .kinds import kinds_all
from .meta import bool_prop, device_prop, float_prop from .meta import bool_prop, device_prop, float_prop
@ -21,62 +21,62 @@ 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 mono(self) -> bool: def mono(self) -> bool:
return self.getter('mono') == 1 return self.getter("mono") == 1
@mono.setter @mono.setter
def mono(self, val: bool): def mono(self, val: bool):
self.setter('mono', 1 if val else 0) self.setter("mono", 1 if val else 0)
@property @property
def solo(self) -> bool: def solo(self) -> bool:
return self.getter('solo') == 1 return self.getter("solo") == 1
@solo.setter @solo.setter
def solo(self, val: bool): def solo(self, val: bool):
self.setter('solo', 1 if val else 0) self.setter("solo", 1 if val else 0)
@property @property
def mute(self) -> bool: def mute(self) -> bool:
return self.getter('mute') == 1 return self.getter("mute") == 1
@mute.setter @mute.setter
def mute(self, val: bool): def mute(self, val: bool):
self.setter('mute', 1 if val else 0) self.setter("mute", 1 if val else 0)
@property @property
def limit(self) -> int: def limit(self) -> int:
return int(self.getter('limit')) return int(self.getter("limit"))
@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 label(self) -> str: def label(self) -> str:
return self.getter('Label', is_string=True) return self.getter("Label", is_string=True)
@label.setter @label.setter
def label(self, val: str): def label(self, val: str):
self.setter('Label', str(val)) self.setter("Label", str(val))
@property @property
def gain(self) -> float: def gain(self) -> float:
return round(self.getter('gain'), 1) return round(self.getter("gain"), 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)
@ -90,203 +90,203 @@ class PhysicalStrip(Strip):
""" """
EFFECTS_cls = _make_effects_mixins(is_phys)[remote.kind.name] EFFECTS_cls = _make_effects_mixins(is_phys)[remote.kind.name]
return type( return type(
'PhysicalStrip', "PhysicalStrip",
(cls, EFFECTS_cls), (cls, EFFECTS_cls),
{ {
'comp': StripComp(remote, i), "comp": StripComp(remote, i),
'gate': StripGate(remote, i), "gate": StripGate(remote, i),
'denoiser': StripDenoiser(remote, i), "denoiser": StripDenoiser(remote, i),
'eq': StripEQ(remote, i), "eq": StripEQ(remote, i),
'device': StripDevice.make(remote, i), "device": StripDevice.make(remote, i),
}, },
) )
def __str__(self): def __str__(self):
return f'{type(self).__name__}{self.index}' return f"{type(self).__name__}{self.index}"
@property @property
def audibility(self) -> float: def audibility(self) -> float:
return round(self.getter('audibility'), 1) return round(self.getter("audibility"), 1)
@audibility.setter @audibility.setter
def audibility(self, val: float): def audibility(self, val: float):
self.setter('audibility', val) self.setter("audibility", val)
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:
return round(self.getter(''), 1) return round(self.getter(""), 1)
@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:
return round(self.getter('GainIn'), 1) return round(self.getter("GainIn"), 1)
@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:
return round(self.getter('Ratio'), 1) return round(self.getter("Ratio"), 1)
@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:
return round(self.getter('Threshold'), 1) return round(self.getter("Threshold"), 1)
@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:
return round(self.getter('Attack'), 1) return round(self.getter("Attack"), 1)
@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:
return round(self.getter('Release'), 1) return round(self.getter("Release"), 1)
@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:
return round(self.getter('Knee'), 2) return round(self.getter("Knee"), 2)
@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:
return round(self.getter('GainOut'), 1) return round(self.getter("GainOut"), 1)
@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:
return self.getter('makeup') == 1 return self.getter("makeup") == 1
@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:
return round(self.getter(''), 1) return round(self.getter(""), 1)
@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:
return round(self.getter('Threshold'), 1) return round(self.getter("Threshold"), 1)
@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:
return round(self.getter('Damping'), 1) return round(self.getter("Damping"), 1)
@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:
return int(self.getter('BPSidechain')) return int(self.getter("BPSidechain"))
@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:
return round(self.getter('Attack'), 1) return round(self.getter("Attack"), 1)
@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:
return round(self.getter('Hold'), 1) return round(self.getter("Hold"), 1)
@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:
return round(self.getter('Release'), 1) return round(self.getter("Release"), 1)
@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:
return round(self.getter(''), 1) return round(self.getter(""), 1)
@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) -> bool: def on(self) -> bool:
return self.getter('on') == 1 return self.getter("on") == 1
@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) -> bool: def ab(self) -> bool:
return self.getter('ab') == 1 return self.getter("ab") == 1
@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 StripDevice(IRemote): class StripDevice(IRemote):
@ -298,16 +298,16 @@ class StripDevice(IRemote):
Returns a StripDevice class of a kind. Returns a StripDevice class of a kind.
""" """
DEVICE_cls = type( DEVICE_cls = type(
f'StripDevice{remote.kind}', f"StripDevice{remote.kind}",
(cls,), (cls,),
{ {
**{ **{
param: device_prop(param) param: device_prop(param)
for param in [ for param in [
'wdm', "wdm",
'ks', "ks",
'mme', "mme",
'asio', "asio",
] ]
}, },
}, },
@ -316,15 +316,15 @@ class StripDevice(IRemote):
@property @property
def identifier(self) -> str: def identifier(self) -> str:
return f'Strip[{self.index}].device' return f"Strip[{self.index}].device"
@property @property
def name(self) -> str: def name(self) -> str:
return self.getter('name', is_string=True) return self.getter("name", is_string=True)
@property @property
def sr(self) -> int: def sr(self) -> int:
return int(self.getter('sr')) return int(self.getter("sr"))
class VirtualStrip(Strip): class VirtualStrip(Strip):
@ -337,65 +337,65 @@ class VirtualStrip(Strip):
""" """
EFFECTS_cls = _make_effects_mixins(is_phys)[remote.kind.name] EFFECTS_cls = _make_effects_mixins(is_phys)[remote.kind.name]
return type( return type(
'VirtualStrip', "VirtualStrip",
(cls, EFFECTS_cls), (cls, EFFECTS_cls),
{}, {},
) )
def __str__(self): def __str__(self):
return f'{type(self).__name__}{self.index}' return f"{type(self).__name__}{self.index}"
@property @property
def mc(self) -> bool: def mc(self) -> bool:
return self.getter('mc') == 1 return self.getter("mc") == 1
@mc.setter @mc.setter
def mc(self, val: bool): def mc(self, val: bool):
self.setter('mc', 1 if val else 0) self.setter("mc", 1 if val else 0)
mono = mc mono = mc
@property @property
def k(self) -> int: def k(self) -> int:
return int(self.getter('karaoke')) return int(self.getter("karaoke"))
@k.setter @k.setter
def k(self, val: int): def k(self, val: int):
self.setter('karaoke', val) self.setter("karaoke", val)
@property @property
def bass(self) -> float: def bass(self) -> float:
return round(self.getter('EQGain1'), 1) return round(self.getter("EQGain1"), 1)
@bass.setter @bass.setter
def bass(self, val: float): def bass(self, val: float):
self.setter('EQGain1', val) self.setter("EQGain1", val)
@property @property
def mid(self) -> float: def mid(self) -> float:
return round(self.getter('EQGain2'), 1) return round(self.getter("EQGain2"), 1)
@mid.setter @mid.setter
def mid(self, val: float): def mid(self, val: float):
self.setter('EQGain2', val) self.setter("EQGain2", val)
med = mid med = mid
@property @property
def treble(self) -> float: def treble(self) -> float:
return round(self.getter('EQGain3'), 1) return round(self.getter("EQGain3"), 1)
high = treble high = treble
@treble.setter @treble.setter
def treble(self, val: float): def treble(self, val: float):
self.setter('EQGain3', val) self.setter("EQGain3", 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):
@ -416,7 +416,7 @@ class StripLevel(IRemote):
return round(20 * log(x, 10), 1) if x > 0 else -200.0 return round(20 * log(x, 10), 1) if x > 0 else -200.0
if not self._remote.stopped() and self._remote.event.ldirty: if not self._remote.stopped() and self._remote.event.ldirty:
vals = self._remote.cache['strip_level'][self.range[0] : self.range[-1]] vals = self._remote.cache["strip_level"][self.range[0] : self.range[-1]]
else: else:
vals = [self._remote.get_level(mode, i) for i in range(*self.range)] vals = [self._remote.get_level(mode, i) for i in range(*self.range)]
@ -424,7 +424,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:
@ -467,7 +467,7 @@ def make_strip_level_map(kind):
return phys_map + virt_map return phys_map + virt_map
_make_strip_level_maps = {kind.name: make_strip_level_map(kind) for kind in kinds.all} _make_strip_level_maps = {kind.name: make_strip_level_map(kind) for kind in kinds_all}
class GainLayer(IRemote): class GainLayer(IRemote):
@ -477,24 +477,24 @@ 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): def gain(self):
return self.getter(f'GainLayer[{self._i}]') return self.getter(f"GainLayer[{self._i}]")
@gain.setter @gain.setter
def gain(self, val): def gain(self, val):
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(
'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)
) )
}, },
@ -504,17 +504,17 @@ 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}': bool_prop(f'A{i}') for i in range(1, kind.phys_out + 1)}, **{f"A{i}": bool_prop(f"A{i}") for i in range(1, kind.phys_out + 1)},
**{f'B{i}': bool_prop(f'B{i}') for i in range(1, kind.virt_out + 1)}, **{f"B{i}": bool_prop(f"B{i}") for i in range(1, kind.virt_out + 1)},
}, },
) )
_make_channelout_mixins = { _make_channelout_mixins = {
kind.name: _make_channelout_mixin(kind) for kind in kinds.all kind.name: _make_channelout_mixin(kind) for kind in kinds_all
} }
@ -522,12 +522,12 @@ def _make_effects_mixin(kind, is_phys):
"""creates an effects mixin for a kind""" """creates an effects mixin for a kind"""
def _make_xy_cls(): def _make_xy_cls():
pan = {param: float_prop(param) for param in ['pan_x', 'pan_y']} pan = {param: float_prop(param) for param in ["pan_x", "pan_y"]}
color = {param: float_prop(param) for param in ['color_x', 'color_y']} color = {param: float_prop(param) for param in ["color_x", "color_y"]}
fx = {param: float_prop(param) for param in ['fx_x', 'fx_y']} fx = {param: float_prop(param) for param in ["fx_x", "fx_y"]}
if is_phys: if is_phys:
return type( return type(
'XYPhys', "XYPhys",
(), (),
{ {
**pan, **pan,
@ -536,7 +536,7 @@ def _make_effects_mixin(kind, is_phys):
}, },
) )
return type( return type(
'XYVirt', "XYVirt",
(), (),
{**pan}, {**pan},
) )
@ -544,32 +544,32 @@ def _make_effects_mixin(kind, is_phys):
def _make_fx_cls(): def _make_fx_cls():
if is_phys: if is_phys:
return type( return type(
'FX', "FX",
(), (),
{ {
**{ **{
param: float_prop(param) param: float_prop(param)
for param in ['reverb', 'delay', 'fx1', 'fx2'] for param in ["reverb", "delay", "fx1", "fx2"]
}, },
**{ **{
f'post{param}': bool_prop(f'post{param}') f"post{param}": bool_prop(f"post{param}")
for param in ['reverb', 'delay', 'fx1', 'fx2'] for param in ["reverb", "delay", "fx1", "fx2"]
}, },
}, },
) )
return type('FX', (), {}) return type("FX", (), {})
if kind.name == 'basic': if kind.name == "basic":
steps = (_make_xy_cls,) steps = (_make_xy_cls,)
elif kind.name == 'banana': elif kind.name == "banana":
steps = (_make_xy_cls,) steps = (_make_xy_cls,)
elif kind.name == 'potato': elif kind.name == "potato":
steps = (_make_xy_cls, _make_fx_cls) steps = (_make_xy_cls, _make_fx_cls)
return type(f'Effects{kind}', tuple(step() for step in steps), {}) return type(f"Effects{kind}", tuple(step() for step in steps), {})
def _make_effects_mixins(is_phys): def _make_effects_mixins(is_phys):
return {kind.name: _make_effects_mixin(kind, is_phys) for kind in kinds.all} return {kind.name: _make_effects_mixin(kind, is_phys) for kind in kinds_all}
def strip_factory(is_phys_strip, remote, i) -> Union[PhysicalStrip, VirtualStrip]: def strip_factory(is_phys_strip, remote, i) -> Union[PhysicalStrip, VirtualStrip]:
@ -588,14 +588,14 @@ def strip_factory(is_phys_strip, remote, i) -> Union[PhysicalStrip, VirtualStrip
CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name] CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name]
_kls = (STRIP_cls, CHANNELOUTMIXIN_cls) _kls = (STRIP_cls, CHANNELOUTMIXIN_cls)
if remote.kind.name == 'potato': if remote.kind.name == "potato":
GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i) GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i)
_kls += (GAINLAYERMIXIN_cls,) _kls += (GAINLAYERMIXIN_cls,)
return type( return type(
f'{STRIP_cls.__name__}{remote.kind}', f"{STRIP_cls.__name__}{remote.kind}",
_kls, _kls,
{ {
'levels': StripLevel(remote, i), "levels": StripLevel(remote, i),
}, },
)(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

@ -11,7 +11,7 @@ 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
@ -23,35 +23,35 @@ class Producer(threading.Thread):
def run(self): def run(self):
while not self.stopped(): while not self.stopped():
if self._remote.event.pdirty: if self._remote.event.pdirty:
self.queue.put('pdirty') self.queue.put("pdirty")
if self._remote.event.mdirty: if self._remote.event.mdirty:
self.queue.put('mdirty') self.queue.put("mdirty")
if self._remote.event.midi: if self._remote.event.midi:
self.queue.put('midi') self.queue.put("midi")
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)
class Updater(threading.Thread): 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._remote._strip_comp = [False] * (self._remote.kind.num_strip_levels) self._remote._strip_comp = [False] * (self._remote.kind.num_strip_levels)
self._remote._bus_comp = [False] * (self._remote.kind.num_bus_levels) self._remote._bus_comp = [False] * (self._remote.kind.num_bus_levels)
( (
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._get_levels()
self.logger = logger.getChild(self.__class__.__name__) self.logger = logger.getChild(self.__class__.__name__)
def _update_comps(self, strip_level, bus_level): def _update_comps(self, strip_level, bus_level):
self._remote._strip_comp, self._remote._bus_comp = ( self._remote._strip_comp, self._remote._bus_comp = (
tuple(not x for x in comp(self._remote.cache['strip_level'], strip_level)), tuple(not x for x in comp(self._remote.cache["strip_level"], strip_level)),
tuple(not x for x in comp(self._remote.cache['bus_level'], bus_level)), tuple(not x for x in comp(self._remote.cache["bus_level"], bus_level)),
) )
def run(self): def run(self):
@ -61,15 +61,15 @@ 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 == 'mdirty' and self._remote.mdirty: elif event == "mdirty" and self._remote.mdirty:
self._remote.subject.notify(event) self._remote.subject.notify(event)
elif event == 'midi' and self._remote.get_midi_message(): elif event == "midi" and self._remote.get_midi_message():
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._update_comps(self._remote._strip_buf, self._remote._bus_buf) self._update_comps(self._remote._strip_buf, self._remote._bus_buf)
self._remote.cache['strip_level'] = self._remote._strip_buf self._remote.cache["strip_level"] = self._remote._strip_buf
self._remote.cache['bus_level'] = self._remote._bus_buf self._remote.cache["bus_level"] = self._remote._bus_buf
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")

View File

@ -22,16 +22,16 @@ def timeout(func):
try: try:
time.sleep(0.1) # ensure at least 0.1 delay before clearing dirty time.sleep(0.1) # ensure at least 0.1 delay before clearing dirty
remote.logger.info( remote.logger.info(
f'{type(remote).__name__}: Successfully logged into {remote} version {remote.version}' f"{type(remote).__name__}: Successfully logged into {remote} version {remote.version}"
) )
remote.logger.debug(f'login time: {round(time.time() - start, 2)}') remote.logger.debug(f"login time: {round(time.time() - start, 2)}")
err = None err = None
break break
except CAPIError as e: except CAPIError as e:
err = e err = e
continue continue
if err: if err:
raise VMError('Timeout logging into the api') raise VMError("Timeout logging into the api")
remote.clear_dirty() remote.clear_dirty()
return wrapper return wrapper
@ -48,15 +48,15 @@ def polling(func):
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
get = func.__name__ == 'get' get = func.__name__ == "get"
mb_get = func.__name__ == 'get_buttonstatus' mb_get = func.__name__ == "get_buttonstatus"
remote, *remaining = args remote, *remaining = args
if get: if get:
param, *rem = remaining param, *rem = remaining
elif mb_get: elif mb_get:
id, mode, *rem = remaining id, mode, *rem = remaining
param = f'mb_{id}_{mode}' param = f"mb_{id}_{mode}"
if param in remote.cache: if param in remote.cache:
return remote.cache.pop(param) return remote.cache.pop(param)
@ -73,15 +73,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)

View File

@ -1,7 +1,7 @@
from abc import abstractmethod from abc import abstractmethod
from . import kinds
from .iremote import IRemote from .iremote import IRemote
from .kinds import kinds_all
class VbanStream(IRemote): class VbanStream(IRemote):
@ -17,94 +17,94 @@ 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:
return self.getter('on') == 1 return self.getter("on") == 1
@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:
return self.getter('name', is_string=True) return self.getter("name", is_string=True)
@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:
return self.getter('ip', is_string=True) return self.getter("ip", is_string=True)
@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:
return int(self.getter('port')) return int(self.getter("port"))
@port.setter @port.setter
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:
return int(self.getter('sr')) return int(self.getter("sr"))
@sr.setter @sr.setter
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:
return int(self.getter('channel')) return int(self.getter("channel"))
@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:
return 16 if (int(self.getter('bit') == 1)) else 24 return 16 if (int(self.getter("bit") == 1)) else 24
@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:
return int(self.getter('quality')) return int(self.getter("quality"))
@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:
return int(self.getter('route')) return int(self.getter("route"))
@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,27 +174,27 @@ 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)),
) )
def _make_stream_pairs(remote): def _make_stream_pairs(remote):
return {kind.name: _make_stream_pair(remote, kind) for kind in kinds.all} return {kind.name: _make_stream_pair(remote, kind) for kind in kinds_all}
class Vban: class Vban:
@ -209,10 +209,10 @@ class Vban:
self.instream, self.outstream = _make_stream_pairs(remote)[remote.kind.name] self.instream, self.outstream = _make_stream_pairs(remote)[remote.kind.name]
def enable(self): def enable(self):
self.remote.set('vban.Enable', 1) self.remote.set("vban.Enable", 1)
def disable(self): def disable(self):
self.remote.set('vban.Enable', 0) self.remote.set("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: