mirror of
https://github.com/onyx-and-iris/vban-cmd-python.git
synced 2025-05-21 17:50:22 +01:00
Compare commits
28 Commits
f6218d2032
...
8b912a2d08
Author | SHA1 | Date | |
---|---|---|---|
8b912a2d08 | |||
d2a5fe197e | |||
0970bfe0b5 | |||
54041503c9 | |||
9d015755eb | |||
ca9a31c94a | |||
7a3abfc372 | |||
37a9c88867 | |||
df7996a846 | |||
3f5dc7c376 | |||
05cbc432b2 | |||
174d95d08d | |||
fc324fecc4 | |||
449cb9b3c1 | |||
cdccc603d1 | |||
a8bb9711af | |||
5bb0c2731e | |||
372dba0b6b | |||
226fc5ead7 | |||
9196a4e267 | |||
8485992495 | |||
91e49cbb55 | |||
3c85903554 | |||
a730edc2c2 | |||
90acafe95b | |||
5f4fdcb0eb | |||
d5219d66f7 | |||
c74d827154 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -157,3 +157,5 @@ quick.py
|
|||||||
#config
|
#config
|
||||||
config.toml
|
config.toml
|
||||||
vban.toml
|
vban.toml
|
||||||
|
|
||||||
|
.vscode/
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -11,6 +11,36 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
|
|||||||
|
|
||||||
- [x]
|
- [x]
|
||||||
|
|
||||||
|
## [2.0.0] - 2023-06-25
|
||||||
|
|
||||||
|
This update introduces some breaking changes:
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `strip[i].comp` now references StripComp class
|
||||||
|
- To change the comp knob you should now use the property `strip[i].comp.knob`
|
||||||
|
- `strip[i].gate` now references StripGate class
|
||||||
|
|
||||||
|
- To change the gate knob you should now use the property `strip[i].gate.knob`
|
||||||
|
|
||||||
|
- `bus[i].eq` now references BusEQ class
|
||||||
|
|
||||||
|
- To set bus[i].{eq,eq_ab} as before you should now use bus[i].eq.on and bus[i].eq.ab
|
||||||
|
|
||||||
|
- new error class `VBANCMDConnectionError` raised when a connection fails or times out.
|
||||||
|
|
||||||
|
There are other non-breaking changes:
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- now using a producer thread to send events to the updater thread.
|
||||||
|
- factory.request_vbancmd_obj simply raises a `VBANCMDError` if passed an incorrect kind.
|
||||||
|
- module level loggers implemented (with class loggers as child loggers)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `strip[i].eq` added to PhysicalStrip
|
||||||
|
|
||||||
## [1.8.0]
|
## [1.8.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
106
README.md
106
README.md
@ -18,9 +18,9 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
|
|||||||
|
|
||||||
## Tested against
|
## Tested against
|
||||||
|
|
||||||
- Basic 1.0.8.4
|
- Basic 1.0.8.8
|
||||||
- Banana 2.0.6.4
|
- Banana 2.0.6.8
|
||||||
- Potato 3.0.2.4
|
- Potato 3.0.2.8
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@ -71,19 +71,19 @@ class ManyThings:
|
|||||||
|
|
||||||
def other_things(self):
|
def other_things(self):
|
||||||
self.vban.bus[3].gain = -6.3
|
self.vban.bus[3].gain = -6.3
|
||||||
self.vban.bus[4].eq = True
|
self.vban.bus[4].eq.on = True
|
||||||
info = (
|
info = (
|
||||||
f"bus 3 gain has been set to {self.vban.bus[3].gain}",
|
f"bus 3 gain has been set to {self.vban.bus[3].gain}",
|
||||||
f"bus 4 eq has been set to {self.vban.bus[4].eq}",
|
f"bus 4 eq has been set to {self.vban.bus[4].eq.on}",
|
||||||
)
|
)
|
||||||
print("\n".join(info))
|
print("\n".join(info))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
kind_id = "banana"
|
KIND_ID = "banana"
|
||||||
|
|
||||||
with vban_cmd.api(
|
with vban_cmd.api(
|
||||||
kind_id, ip="gamepc.local", port=6980, streamname="Command1"
|
KIND_ID, ip="gamepc.local", port=6980, streamname="Command1"
|
||||||
) as vban:
|
) as vban:
|
||||||
do = ManyThings(vban)
|
do = ManyThings(vban)
|
||||||
do.things()
|
do.things()
|
||||||
@ -93,7 +93,7 @@ def main():
|
|||||||
vban.apply(
|
vban.apply(
|
||||||
{
|
{
|
||||||
"strip-2": {"A1": True, "B1": True, "gain": -6.0},
|
"strip-2": {"A1": True, "B1": True, "gain": -6.0},
|
||||||
"bus-2": {"mute": True},
|
"bus-2": {"mute": True, "eq": {"on": True}},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -104,9 +104,9 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
Otherwise you must remember to call `vban.login()`, `vban.logout()` at the start/end of your code.
|
Otherwise you must remember to call `vban.login()`, `vban.logout()` at the start/end of your code.
|
||||||
|
|
||||||
## `kind_id`
|
## `KIND_ID`
|
||||||
|
|
||||||
Pass the kind of Voicemeeter as an argument. kind_id may be:
|
Pass the kind of Voicemeeter as an argument. KIND_ID may be:
|
||||||
|
|
||||||
- `basic`
|
- `basic`
|
||||||
- `banana`
|
- `banana`
|
||||||
@ -124,8 +124,6 @@ The following properties are available.
|
|||||||
- `label`: string
|
- `label`: string
|
||||||
- `gain`: float, -60 to 12
|
- `gain`: float, -60 to 12
|
||||||
- `A1 - A5`, `B1 - B3`: boolean
|
- `A1 - A5`, `B1 - B3`: boolean
|
||||||
- `comp`: float, from 0.0 to 10.0
|
|
||||||
- `gate`: float, from 0.0 to 10.0
|
|
||||||
- `limit`: int, from -40 to 12
|
- `limit`: int, from -40 to 12
|
||||||
|
|
||||||
example:
|
example:
|
||||||
@ -152,6 +150,69 @@ vban.strip[5].appmute("Spotify", True)
|
|||||||
vban.strip[5].appgain("Spotify", 0.5)
|
vban.strip[5].appgain("Spotify", 0.5)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Strip.Comp
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `knob`: float, from 0.0 to 10.0
|
||||||
|
- `gainin`: float, from -24.0 to 24.0
|
||||||
|
- `ratio`: float, from 1.0 to 8.0
|
||||||
|
- `threshold`: float, from -40.0 to -3.0
|
||||||
|
- `attack`: float, from 0.0 to 200.0
|
||||||
|
- `release`: float, from 0.0 to 5000.0
|
||||||
|
- `knee`: float, from 0.0 to 1.0
|
||||||
|
- `gainout`: float, from -24.0 to 24.0
|
||||||
|
- `makeup`: boolean
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print(vm.strip[4].comp.knob)
|
||||||
|
```
|
||||||
|
|
||||||
|
Strip Comp properties are defined as write only.
|
||||||
|
|
||||||
|
`knob` defined for all versions, all other parameters potato only.
|
||||||
|
|
||||||
|
##### Strip.Gate
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `knob`: float, from 0.0 to 10.0
|
||||||
|
- `threshold`: float, from -60.0 to -10.0
|
||||||
|
- `damping`: float, from -60.0 to -10.0
|
||||||
|
- `bpsidechain`: int, from 100 to 4000
|
||||||
|
- `attack`: float, from 0.0 to 1000.0
|
||||||
|
- `hold`: float, from 0.0 to 5000.0
|
||||||
|
- `release`: float, from 0.0 to 5000.0
|
||||||
|
|
||||||
|
example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
vm.strip[2].gate.attack = 300.8
|
||||||
|
```
|
||||||
|
|
||||||
|
Strip Gate properties are defined as write only, potato version only.
|
||||||
|
|
||||||
|
`knob` defined for all versions, all other parameters potato only.
|
||||||
|
|
||||||
|
##### Strip.Denoiser
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `knob`: float, from 0.0 to 10.0
|
||||||
|
|
||||||
|
strip.denoiser properties are defined as write only, potato version only.
|
||||||
|
|
||||||
|
##### Strip.EQ
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `on`: boolean
|
||||||
|
- `ab`: boolean
|
||||||
|
|
||||||
|
Strip EQ properties are defined as write only, potato version only.
|
||||||
|
|
||||||
##### Gainlayers
|
##### Gainlayers
|
||||||
|
|
||||||
- `gain`: float, from -60.0 to 12.0
|
- `gain`: float, from -60.0 to 12.0
|
||||||
@ -183,8 +244,6 @@ Level properties will return -200.0 if no audio detected.
|
|||||||
The following properties are available.
|
The following properties are available.
|
||||||
|
|
||||||
- `mono`: boolean
|
- `mono`: boolean
|
||||||
- `eq`: boolean
|
|
||||||
- `eq_ab`: boolean
|
|
||||||
- `mute`: boolean
|
- `mute`: boolean
|
||||||
- `label`: string
|
- `label`: string
|
||||||
- `gain`: float, -60 to 12
|
- `gain`: float, -60 to 12
|
||||||
@ -196,6 +255,13 @@ vban.bus[4].eq = true
|
|||||||
print(vban.bus[0].label)
|
print(vban.bus[0].label)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Bus.EQ
|
||||||
|
|
||||||
|
The following properties are available.
|
||||||
|
|
||||||
|
- `on`: boolean
|
||||||
|
- `ab`: boolean
|
||||||
|
|
||||||
##### Modes
|
##### Modes
|
||||||
|
|
||||||
The following properties are available.
|
The following properties are available.
|
||||||
@ -325,9 +391,8 @@ opts = {
|
|||||||
"ip": "<ip address>",
|
"ip": "<ip address>",
|
||||||
"streamname": "Command1",
|
"streamname": "Command1",
|
||||||
"port": 6980,
|
"port": 6980,
|
||||||
"subs": {"ldirty": True},
|
|
||||||
}
|
}
|
||||||
with vban_cmd.api('banana', **opts) as vban:
|
with vban_cmd.api('banana', ldirty=True, **opts) as vban:
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -386,16 +451,15 @@ print(vban.event.get())
|
|||||||
|
|
||||||
## VbanCmd class
|
## VbanCmd class
|
||||||
|
|
||||||
`vban_cmd.api(kind_id: str, **opts: dict)`
|
`vban_cmd.api(kind_id: str, **opts)`
|
||||||
|
|
||||||
You may pass the following optional keyword arguments:
|
You may pass the following optional keyword arguments:
|
||||||
|
|
||||||
- `ip`: str, ip or hostname of remote machine
|
- `ip`: str, ip or hostname of remote machine
|
||||||
- `streamname`: str, name of the stream to connect to.
|
- `streamname`: str, name of the stream to connect to.
|
||||||
- `port`: int=6980, vban udp port of remote machine.
|
- `port`: int=6980, vban udp port of remote machine.
|
||||||
- `subs`: dict={"pdirty": True, "ldirty": False}, controls which updates to listen for.
|
- `pdirty`: parameter updates
|
||||||
- `pdirty`: parameter updates
|
- `ldirty`: level updates
|
||||||
- `ldirty`: level updates
|
|
||||||
|
|
||||||
#### `vban.pdirty`
|
#### `vban.pdirty`
|
||||||
|
|
||||||
@ -415,7 +479,7 @@ vban.sendtext("Strip[0].Mute=1;Bus[0].Mono=1")
|
|||||||
|
|
||||||
#### `vban.public_packet`
|
#### `vban.public_packet`
|
||||||
|
|
||||||
Returns a Voicemeeter rt data packet object. Designed to be used internally by the interface but available for parsing through this read only property object. States not guaranteed to be current (requires use of dirty parameters to confirm).
|
Returns a `VbanRtPacket`. Designed to be used internally by the interface but available for parsing through this read only property object. States not guaranteed to be current (requires use of dirty parameters to confirm).
|
||||||
|
|
||||||
### `Errors`
|
### `Errors`
|
||||||
|
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
label = "PhysStrip0"
|
label = "PhysStrip0"
|
||||||
A1 = true
|
A1 = true
|
||||||
gain = -8.8
|
gain = -8.8
|
||||||
comp = 3.2
|
comp.knob = 3.2
|
||||||
|
|
||||||
[strip-1]
|
[strip-1]
|
||||||
label = "PhysStrip1"
|
label = "PhysStrip1"
|
||||||
B1 = true
|
B1 = true
|
||||||
gate = 4.1
|
gate.knob = 4.1
|
||||||
|
|
||||||
[strip-2]
|
[strip-2]
|
||||||
label = "PhysStrip2"
|
label = "PhysStrip2"
|
||||||
@ -31,12 +31,12 @@ mono = true
|
|||||||
|
|
||||||
[bus-2]
|
[bus-2]
|
||||||
label = "PhysBus2"
|
label = "PhysBus2"
|
||||||
eq = true
|
eq.on = true
|
||||||
mode = "composite"
|
mode = "composite"
|
||||||
|
|
||||||
[bus-3]
|
[bus-3]
|
||||||
label = "VirtBus0"
|
label = "VirtBus0"
|
||||||
eq_ab = true
|
eq.ab = true
|
||||||
mode = "upmix61"
|
mode = "upmix61"
|
||||||
|
|
||||||
[bus-4]
|
[bus-4]
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
label = "PhysStrip0"
|
label = "PhysStrip0"
|
||||||
A1 = true
|
A1 = true
|
||||||
gain = -8.8
|
gain = -8.8
|
||||||
comp = 3.2
|
comp.knob = 3.2
|
||||||
|
|
||||||
[strip-1]
|
[strip-1]
|
||||||
label = "PhysStrip1"
|
label = "PhysStrip1"
|
||||||
B1 = true
|
B1 = true
|
||||||
gate = 4.1
|
gate.knob = 4.1
|
||||||
|
|
||||||
[strip-2]
|
[strip-2]
|
||||||
label = "PhysStrip2"
|
label = "PhysStrip2"
|
||||||
@ -47,7 +47,7 @@ mono = true
|
|||||||
|
|
||||||
[bus-2]
|
[bus-2]
|
||||||
label = "PhysBus2"
|
label = "PhysBus2"
|
||||||
eq = true
|
eq.on = true
|
||||||
|
|
||||||
[bus-3]
|
[bus-3]
|
||||||
label = "PhysBus3"
|
label = "PhysBus3"
|
||||||
@ -59,7 +59,7 @@ mode = "composite"
|
|||||||
|
|
||||||
[bus-5]
|
[bus-5]
|
||||||
label = "VirtBus0"
|
label = "VirtBus0"
|
||||||
eq_ab = true
|
eq.ab = true
|
||||||
|
|
||||||
[bus-6]
|
[bus-6]
|
||||||
label = "VirtBus1"
|
label = "VirtBus1"
|
||||||
|
13
examples/gui/README.md
Normal file
13
examples/gui/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## About
|
||||||
|
|
||||||
|
A single channel GUI demonstrating controls for the first virtual strip if Voicemeeter Banana.
|
||||||
|
|
||||||
|
This example demonstrates (to an extent) two way communication.
|
||||||
|
- Sending parameters values to the Voicemeeter driver.
|
||||||
|
- Receiving level updates
|
||||||
|
|
||||||
|
Parameter updates (pdirty) events are not being received so changing a UI element on the main Voicemeeter app will not be reflected in the example GUI.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
Simply run the script and try the controls.
|
100
examples/gui/__main__.py
Normal file
100
examples/gui/__main__.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import vban_cmd
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
|
||||||
|
|
||||||
|
class App(tk.Tk):
|
||||||
|
def __init__(self, vban):
|
||||||
|
super().__init__()
|
||||||
|
self.vban = vban
|
||||||
|
self.title(f"{vban} - version {vban.version}")
|
||||||
|
self.vban.observer.add(self.on_ldirty)
|
||||||
|
|
||||||
|
# create widget variables
|
||||||
|
self.button_var = tk.BooleanVar(value=vban.strip[3].mute)
|
||||||
|
self.slider_var = tk.DoubleVar(value=vban.strip[3].gain)
|
||||||
|
self.meter_var = tk.DoubleVar(value=self._get_level())
|
||||||
|
self.gainlabel_var = tk.StringVar(value=self.slider_var.get())
|
||||||
|
|
||||||
|
# initialize style table
|
||||||
|
self.style = ttk.Style()
|
||||||
|
self.style.theme_use("clam")
|
||||||
|
self.style.configure(
|
||||||
|
"Mute.TButton", foreground="#cd5c5c" if vban.strip[3].mute else "#5a5a5a"
|
||||||
|
)
|
||||||
|
|
||||||
|
# create labelframe and grid it onto the mainframe
|
||||||
|
self.labelframe = tk.LabelFrame(text=self.vban.strip[3].label)
|
||||||
|
self.labelframe.grid(padx=1)
|
||||||
|
|
||||||
|
# create slider and grid it onto the labelframe
|
||||||
|
slider = ttk.Scale(
|
||||||
|
self.labelframe,
|
||||||
|
from_=12,
|
||||||
|
to_=-60,
|
||||||
|
orient="vertical",
|
||||||
|
variable=self.slider_var,
|
||||||
|
command=lambda arg: self.on_slider_move(arg),
|
||||||
|
)
|
||||||
|
slider.grid(
|
||||||
|
column=0,
|
||||||
|
row=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# create level meter and grid it onto the labelframe
|
||||||
|
level_meter = ttk.Progressbar(
|
||||||
|
self.labelframe,
|
||||||
|
orient="vertical",
|
||||||
|
variable=self.meter_var,
|
||||||
|
maximum=72,
|
||||||
|
mode="determinate",
|
||||||
|
)
|
||||||
|
level_meter.grid(column=1, row=0)
|
||||||
|
|
||||||
|
# create gainlabel and grid it onto the labelframe
|
||||||
|
gainlabel = ttk.Label(self.labelframe, textvariable=self.gainlabel_var)
|
||||||
|
gainlabel.grid(column=0, row=1, columnspan=2)
|
||||||
|
|
||||||
|
# create button and grid it onto the labelframe
|
||||||
|
button = ttk.Button(
|
||||||
|
self.labelframe,
|
||||||
|
text="Mute",
|
||||||
|
style="Mute.TButton",
|
||||||
|
command=lambda: self.on_button_press(),
|
||||||
|
)
|
||||||
|
button.grid(column=0, row=2, columnspan=2, padx=1, pady=2)
|
||||||
|
|
||||||
|
# define callbacks
|
||||||
|
|
||||||
|
def on_slider_move(self, *args):
|
||||||
|
val = round(self.slider_var.get(), 1)
|
||||||
|
self.vban.strip[3].gain = val
|
||||||
|
self.gainlabel_var.set(val)
|
||||||
|
|
||||||
|
def on_button_press(self):
|
||||||
|
self.button_var.set(not self.button_var.get())
|
||||||
|
self.vban.strip[3].mute = self.button_var.get()
|
||||||
|
self.style.configure(
|
||||||
|
"Mute.TButton", foreground="#cd5c5c" if self.button_var.get() else "#5a5a5a"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_level(self):
|
||||||
|
val = max(self.vban.strip[3].levels.prefader)
|
||||||
|
return 0 if self.button_var.get() else 72 + val - 12 + self.slider_var.get()
|
||||||
|
|
||||||
|
def on_ldirty(self):
|
||||||
|
self.meter_var.set(self._get_level())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with vban_cmd.api("banana", ldirty=True) as vban:
|
||||||
|
app = App(vban)
|
||||||
|
app.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -40,10 +40,12 @@ Make sure you have established a working connection to OBS and the remote Voicem
|
|||||||
|
|
||||||
Run the script, change OBS scenes and watch Voicemeeter parameters change.
|
Run the script, change OBS scenes and watch Voicemeeter parameters change.
|
||||||
|
|
||||||
Pressing `<Enter>` will exit.
|
Closing OBS will end the script.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
All but `vban_cmd.iremote` logs are filtered out. Log in DEBUG mode.
|
||||||
|
|
||||||
This script can be run from a Linux host since the vban-cmd interface relies on UDP packets and obsws-python runs over websockets.
|
This script can be run from a Linux host since the vban-cmd interface relies on UDP packets and obsws-python runs over websockets.
|
||||||
|
|
||||||
You could for example, set this up to run in the background on a home server such as a Raspberry Pi.
|
You could for example, set this up to run in the background on a home server such as a Raspberry Pi.
|
||||||
|
@ -1,16 +1,41 @@
|
|||||||
import logging
|
import time
|
||||||
|
from logging import config
|
||||||
|
|
||||||
|
import obsws_python as obsws
|
||||||
|
|
||||||
import obsws_python as obs
|
|
||||||
import vban_cmd
|
import vban_cmd
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
config.dictConfig(
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"formatters": {
|
||||||
|
"standard": {
|
||||||
|
"format": "%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"stream": {
|
||||||
|
"level": "DEBUG",
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "standard",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loggers": {"vban_cmd.iremote": {"handlers": ["stream"], "level": "DEBUG"}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Observer:
|
class Observer:
|
||||||
def __init__(self, vban):
|
def __init__(self, vban):
|
||||||
self.vban = vban
|
self.vban = vban
|
||||||
self.client = obs.EventClient()
|
self.client = obsws.EventClient()
|
||||||
self.client.callback.register(self.on_current_program_scene_changed)
|
self.client.callback.register(
|
||||||
|
(
|
||||||
|
self.on_current_program_scene_changed,
|
||||||
|
self.on_exit_started,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.is_running = True
|
||||||
|
|
||||||
def on_start(self):
|
def on_start(self):
|
||||||
self.vban.strip[0].mute = True
|
self.vban.strip[0].mute = True
|
||||||
@ -50,13 +75,16 @@ class Observer:
|
|||||||
if fn := fget(scene):
|
if fn := fget(scene):
|
||||||
fn()
|
fn()
|
||||||
|
|
||||||
|
def on_exit_started(self, _):
|
||||||
|
self.client.unsubscribe()
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with vban_cmd.api("potato", sync=True) as vban:
|
with vban_cmd.api("potato") as vban:
|
||||||
obs = Observer(vban)
|
observer = Observer(vban)
|
||||||
while cmd := input("<Enter> to exit\n"):
|
while observer.is_running:
|
||||||
if not cmd:
|
time.sleep(0.1)
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
7
examples/obs/setup.py
Normal file
7
examples/obs/setup.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="obs",
|
||||||
|
description="OBS Example",
|
||||||
|
install_requires=["obsws-python"],
|
||||||
|
)
|
@ -5,33 +5,30 @@ import vban_cmd
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
class Observer:
|
class App:
|
||||||
def __init__(self, vban):
|
def __init__(self, vban):
|
||||||
self.vban = vban
|
self.vban = vban
|
||||||
# register your app as event observer
|
# register your app as event observer
|
||||||
self.vban.subject.add(self)
|
self.vban.observer.add(self)
|
||||||
# enable level updates, since they are disabled by default.
|
|
||||||
self.vban.event.ldirty = True
|
|
||||||
|
|
||||||
# define an 'on_update' callback function to receive event updates
|
# define an 'on_update' callback function to receive event updates
|
||||||
def on_update(self, subject):
|
def on_update(self, event):
|
||||||
if subject == "pdirty":
|
if event == "pdirty":
|
||||||
print("pdirty!")
|
print("pdirty!")
|
||||||
elif subject == "ldirty":
|
elif event == "ldirty":
|
||||||
for bus in self.vban.bus:
|
for bus in self.vban.bus:
|
||||||
if bus.levels.isdirty:
|
if bus.levels.isdirty:
|
||||||
print(bus, bus.levels.all)
|
print(bus, bus.levels.all)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
kind_id = "potato"
|
KIND_ID = "banana"
|
||||||
|
|
||||||
with vban_cmd.api(kind_id) as vban:
|
with vban_cmd.api(KIND_ID, pdirty=True, ldirty=True) as vban:
|
||||||
Observer(vban)
|
App(vban)
|
||||||
|
|
||||||
while cmd := input("Press <Enter> to exit\n"):
|
while cmd := input("Press <Enter> to exit\n"):
|
||||||
if not cmd:
|
pass
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
125
poetry.lock
generated
125
poetry.lock
generated
@ -33,6 +33,22 @@ d = ["aiohttp (>=3.7.4)"]
|
|||||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cachetools"
|
||||||
|
version = "5.3.1"
|
||||||
|
description = "Extensible memoizing collections and decorators"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chardet"
|
||||||
|
version = "5.1.0"
|
||||||
|
description = "Universal encoding detector for Python 3"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.1.3"
|
version = "8.1.3"
|
||||||
@ -46,11 +62,31 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.5"
|
version = "0.4.6"
|
||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "distlib"
|
||||||
|
version = "0.3.6"
|
||||||
|
description = "Distribution utilities"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filelock"
|
||||||
|
version = "3.12.2"
|
||||||
|
description = "A platform independent file lock."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2023.5.20)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"]
|
||||||
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)", "pytest (>=7.3.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
@ -84,14 +120,11 @@ python-versions = "*"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "21.3"
|
version = "23.1"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
@ -103,15 +136,15 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "2.5.2"
|
version = "3.7.0"
|
||||||
description = "A small Python module 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\"."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"]
|
||||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest (>=7.3.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
@ -122,8 +155,8 @@ optional = false
|
|||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["pytest-benchmark", "pytest"]
|
dev = ["pre-commit", "tox"]
|
||||||
dev = ["tox", "pre-commit"]
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "py"
|
name = "py"
|
||||||
@ -134,15 +167,20 @@ optional = false
|
|||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyparsing"
|
name = "pyproject-api"
|
||||||
version = "3.0.9"
|
version = "1.5.2"
|
||||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
description = "API to interact with the python pyproject.toml based projects"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.8"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
packaging = ">=23.1"
|
||||||
|
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
diagrams = ["railroad-diagrams", "jinja2"]
|
docs = ["furo (>=2023.5.20)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"]
|
||||||
|
testing = ["covdefaults (>=2.3)", "importlib-metadata (>=6.6)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest (>=7.3.1)", "setuptools (>=67.8)", "wheel (>=0.40)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
@ -194,16 +232,61 @@ category = "main"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tox"
|
||||||
|
version = "4.6.3"
|
||||||
|
description = "tox is a generic virtualenv management and test command line tool"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cachetools = ">=5.3.1"
|
||||||
|
chardet = ">=5.1"
|
||||||
|
colorama = ">=0.4.6"
|
||||||
|
filelock = ">=3.12.2"
|
||||||
|
packaging = ">=23.1"
|
||||||
|
platformdirs = ">=3.5.3"
|
||||||
|
pluggy = ">=1"
|
||||||
|
pyproject-api = ">=1.5.2"
|
||||||
|
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
|
||||||
|
virtualenv = ">=20.23.1"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2023.5.20)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.2,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinx (>=7.0.1)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||||
|
testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "pytest (>=7.3.2)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "virtualenv"
|
||||||
|
version = "20.23.1"
|
||||||
|
description = "Virtual Python Environment builder"
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
distlib = ">=0.3.6,<1"
|
||||||
|
filelock = ">=3.12,<4"
|
||||||
|
platformdirs = ">=3.5.1,<4"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx-argparse (>=0.4)", "sphinx (>=7.0.1)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||||
|
test = ["covdefaults (>=2.3)", "coverage-enable-subprocess (>=1)", "coverage (>=7.2.7)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "pytest (>=7.3.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "9f887ae517ade09119bf1f2cf77261d2445ae95857b69470ce1707f9791ce080"
|
content-hash = "5d0edd070ea010edb4e2ade88dc37324b8b4b04f22db78e49db161185365849b"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
attrs = []
|
attrs = []
|
||||||
black = []
|
black = []
|
||||||
|
cachetools = []
|
||||||
|
chardet = []
|
||||||
click = []
|
click = []
|
||||||
colorama = []
|
colorama = []
|
||||||
|
distlib = []
|
||||||
|
filelock = []
|
||||||
iniconfig = []
|
iniconfig = []
|
||||||
isort = []
|
isort = []
|
||||||
mypy-extensions = []
|
mypy-extensions = []
|
||||||
@ -212,8 +295,10 @@ pathspec = []
|
|||||||
platformdirs = []
|
platformdirs = []
|
||||||
pluggy = []
|
pluggy = []
|
||||||
py = []
|
py = []
|
||||||
pyparsing = []
|
pyproject-api = []
|
||||||
pytest = []
|
pytest = []
|
||||||
pytest-randomly = []
|
pytest-randomly = []
|
||||||
pytest-repeat = []
|
pytest-repeat = []
|
||||||
tomli = []
|
tomli = []
|
||||||
|
tox = []
|
||||||
|
virtualenv = []
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "vban-cmd"
|
name = "vban-cmd"
|
||||||
version = "1.8.1"
|
version = "2.0.0"
|
||||||
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
||||||
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@ -18,11 +18,26 @@ pytest-randomly = "^3.12.0"
|
|||||||
pytest-repeat = "^0.9.1"
|
pytest-repeat = "^0.9.1"
|
||||||
black = "^22.3.0"
|
black = "^22.3.0"
|
||||||
isort = "^5.10.1"
|
isort = "^5.10.1"
|
||||||
|
tox = "^4.6.3"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
|
gui = "scripts:ex_gui"
|
||||||
obs = "scripts:ex_obs"
|
obs = "scripts:ex_obs"
|
||||||
observer = "scripts:ex_observer"
|
observer = "scripts:ex_observer"
|
||||||
|
test = "scripts:test"
|
||||||
|
|
||||||
|
[tool.tox]
|
||||||
|
legacy_tox_ini = """
|
||||||
|
[tox]
|
||||||
|
envlist = py310,py311
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
allowlist_externals = poetry
|
||||||
|
commands =
|
||||||
|
poetry install -v
|
||||||
|
poetry run pytest tests/
|
||||||
|
"""
|
||||||
|
@ -2,6 +2,11 @@ import subprocess
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def ex_gui():
|
||||||
|
path = Path.cwd() / "examples" / "gui" / "."
|
||||||
|
subprocess.run(["py", str(path)])
|
||||||
|
|
||||||
|
|
||||||
def ex_obs():
|
def ex_obs():
|
||||||
path = Path.cwd() / "examples" / "obs" / "."
|
path = Path.cwd() / "examples" / "obs" / "."
|
||||||
subprocess.run(["py", str(path)])
|
subprocess.run(["py", str(path)])
|
||||||
@ -10,3 +15,7 @@ def ex_obs():
|
|||||||
def ex_observer():
|
def ex_observer():
|
||||||
path = Path.cwd() / "examples" / "observer" / "."
|
path = Path.cwd() / "examples" / "observer" / "."
|
||||||
subprocess.run(["py", str(path)])
|
subprocess.run(["py", str(path)])
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
subprocess.run(["tox"])
|
||||||
|
@ -3,23 +3,21 @@ import sys
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import vban_cmd
|
import vban_cmd
|
||||||
from vban_cmd.kinds import KindId, kinds_all
|
from vban_cmd.kinds import KindId
|
||||||
from vban_cmd.kinds import request_kind_map as kindmap
|
from vban_cmd.kinds import request_kind_map as kindmap
|
||||||
|
|
||||||
# let's keep things random
|
# let's keep things random
|
||||||
kind_id = random.choice(tuple(kind_id.name.lower() for kind_id in KindId))
|
KIND_ID = random.choice(tuple(kind_id.name.lower() for kind_id in KindId))
|
||||||
|
|
||||||
opts = {
|
opts = {
|
||||||
"ip": "ws.local",
|
"ip": "testing.local",
|
||||||
"streamname": "workstation",
|
"streamname": "testing",
|
||||||
"port": 6990,
|
"port": 6990,
|
||||||
"bps": 0,
|
"bps": 0,
|
||||||
"sync": True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vbans = {kind.name: vban_cmd.api(kind.name, **opts) for kind in kinds_all}
|
vban = vban_cmd.api(KIND_ID, **opts)
|
||||||
tests = vbans[kind_id]
|
kind = kindmap(KIND_ID)
|
||||||
kind = kindmap(kind_id)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -42,9 +40,9 @@ data = Data()
|
|||||||
|
|
||||||
def setup_module():
|
def setup_module():
|
||||||
print(f"\nRunning tests for kind [{data.name}]\n", file=sys.stdout)
|
print(f"\nRunning tests for kind [{data.name}]\n", file=sys.stdout)
|
||||||
tests.login()
|
vban.login()
|
||||||
tests.command.reset()
|
vban.command.reset()
|
||||||
|
|
||||||
|
|
||||||
def teardown_module():
|
def teardown_module():
|
||||||
tests.logout()
|
vban.logout()
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tests import data, tests
|
from tests import data, vban
|
||||||
|
|
||||||
|
|
||||||
class TestSetAndGetBoolHigher:
|
class TestSetAndGetBoolHigher:
|
||||||
@ -12,18 +10,18 @@ class TestSetAndGetBoolHigher:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
tests.apply_config("example")
|
vban.apply_config("example")
|
||||||
|
|
||||||
def test_it_tests_config_string(self):
|
def test_it_tests_config_string(self):
|
||||||
assert "PhysStrip" in tests.strip[data.phys_in].label
|
assert "PhysStrip" in vban.strip[data.phys_in].label
|
||||||
assert "VirtStrip" in tests.strip[data.virt_in].label
|
assert "VirtStrip" in vban.strip[data.virt_in].label
|
||||||
|
|
||||||
def test_it_tests_config_bool(self):
|
def test_it_tests_config_bool(self):
|
||||||
assert tests.strip[0].A1 == True
|
assert vban.strip[0].A1 == True
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
"not config.getoption('--run-slow')",
|
"not config.getoption('--run-slow')",
|
||||||
reason="Only run when --run-slow is given",
|
reason="Only run when --run-slow is given",
|
||||||
)
|
)
|
||||||
def test_it_tests_config_busmode(self):
|
def test_it_tests_config_busmode(self):
|
||||||
assert tests.bus[data.phys_out].mode.get() == "composite"
|
assert vban.bus[data.phys_out].mode.get() == "composite"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tests import data, tests
|
from tests import data, vban
|
||||||
|
|
||||||
|
|
||||||
class TestRemoteFactories:
|
class TestRemoteFactories:
|
||||||
@ -11,33 +11,33 @@ class TestRemoteFactories:
|
|||||||
reason="Skip test if kind is not basic",
|
reason="Skip test if kind is not basic",
|
||||||
)
|
)
|
||||||
def test_it_tests_remote_attrs_for_basic(self):
|
def test_it_tests_remote_attrs_for_basic(self):
|
||||||
assert hasattr(tests, "strip")
|
assert hasattr(vban, "strip")
|
||||||
assert hasattr(tests, "bus")
|
assert hasattr(vban, "bus")
|
||||||
assert hasattr(tests, "command")
|
assert hasattr(vban, "command")
|
||||||
|
|
||||||
assert len(tests.strip) == 3
|
assert len(vban.strip) == 3
|
||||||
assert len(tests.bus) == 2
|
assert len(vban.bus) == 2
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
data.name != "banana",
|
data.name != "banana",
|
||||||
reason="Skip test if kind is not basic",
|
reason="Skip test if kind is not basic",
|
||||||
)
|
)
|
||||||
def test_it_tests_remote_attrs_for_banana(self):
|
def test_it_tests_remote_attrs_for_banana(self):
|
||||||
assert hasattr(tests, "strip")
|
assert hasattr(vban, "strip")
|
||||||
assert hasattr(tests, "bus")
|
assert hasattr(vban, "bus")
|
||||||
assert hasattr(tests, "command")
|
assert hasattr(vban, "command")
|
||||||
|
|
||||||
assert len(tests.strip) == 5
|
assert len(vban.strip) == 5
|
||||||
assert len(tests.bus) == 5
|
assert len(vban.bus) == 5
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
data.name != "potato",
|
data.name != "potato",
|
||||||
reason="Skip test if kind is not basic",
|
reason="Skip test if kind is not basic",
|
||||||
)
|
)
|
||||||
def test_it_tests_remote_attrs_for_potato(self):
|
def test_it_tests_remote_attrs_for_potato(self):
|
||||||
assert hasattr(tests, "strip")
|
assert hasattr(vban, "strip")
|
||||||
assert hasattr(tests, "bus")
|
assert hasattr(vban, "bus")
|
||||||
assert hasattr(tests, "command")
|
assert hasattr(vban, "command")
|
||||||
|
|
||||||
assert len(tests.strip) == 8
|
assert len(vban.strip) == 8
|
||||||
assert len(tests.bus) == 8
|
assert len(vban.bus) == 8
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from tests import data, tests
|
from tests import data, vban
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("value", [False, True])
|
@pytest.mark.parametrize("value", [False, True])
|
||||||
@ -17,8 +17,8 @@ class TestSetAndGetBoolHigher:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_bool_params(self, index, param, value):
|
def test_it_sets_and_gets_strip_bool_params(self, index, param, value):
|
||||||
setattr(tests.strip[index], param, value)
|
setattr(vban.strip[index], param, value)
|
||||||
assert getattr(tests.strip[index], param) == value
|
assert getattr(vban.strip[index], param) == value
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
data.name == "banana",
|
data.name == "banana",
|
||||||
@ -31,23 +31,22 @@ class TestSetAndGetBoolHigher:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_bool_params_mc(self, index, param, value):
|
def test_it_sets_and_gets_strip_bool_params_mc(self, index, param, value):
|
||||||
setattr(tests.strip[index], param, value)
|
setattr(vban.strip[index], param, value)
|
||||||
assert getattr(tests.strip[index], param) == value
|
assert getattr(vban.strip[index], param) == value
|
||||||
|
|
||||||
""" bus tests, physical and virtual """
|
""" bus tests, physical and virtual """
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"index,param",
|
"index,param",
|
||||||
[
|
[
|
||||||
(data.phys_out, "eq"),
|
|
||||||
(data.phys_out, "mute"),
|
(data.phys_out, "mute"),
|
||||||
(data.virt_out, "eq_ab"),
|
|
||||||
(data.virt_out, "sel"),
|
(data.virt_out, "sel"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_bus_bool_params(self, index, param, value):
|
def test_it_sets_and_gets_bus_bool_params(self, index, param, value):
|
||||||
setattr(tests.bus[index], param, value)
|
assert hasattr(vban.bus[index], param)
|
||||||
assert getattr(tests.bus[index], param) == value
|
setattr(vban.bus[index], param, value)
|
||||||
|
assert getattr(vban.bus[index], param) == value
|
||||||
|
|
||||||
""" bus modes tests, physical and virtual """
|
""" bus modes tests, physical and virtual """
|
||||||
|
|
||||||
@ -66,8 +65,8 @@ class TestSetAndGetBoolHigher:
|
|||||||
# here it only makes sense to set/get bus modes as True
|
# here it only makes sense to set/get bus modes as True
|
||||||
if not value:
|
if not value:
|
||||||
value = True
|
value = True
|
||||||
setattr(tests.bus[index].mode, param, value)
|
setattr(vban.bus[index].mode, param, value)
|
||||||
assert getattr(tests.bus[index].mode, param) == value
|
assert getattr(vban.bus[index].mode, param) == value
|
||||||
|
|
||||||
""" command tests """
|
""" command tests """
|
||||||
|
|
||||||
@ -76,7 +75,7 @@ class TestSetAndGetBoolHigher:
|
|||||||
[("lock")],
|
[("lock")],
|
||||||
)
|
)
|
||||||
def test_it_sets_command_bool_params(self, param, value):
|
def test_it_sets_command_bool_params(self, param, value):
|
||||||
setattr(tests.command, param, value)
|
setattr(vban.command, param, value)
|
||||||
|
|
||||||
|
|
||||||
class TestSetAndGetIntHigher:
|
class TestSetAndGetIntHigher:
|
||||||
@ -94,8 +93,8 @@ class TestSetAndGetIntHigher:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_bool_params(self, index, param, value):
|
def test_it_sets_and_gets_strip_bool_params(self, index, param, value):
|
||||||
setattr(tests.strip[index], param, value)
|
setattr(vban.strip[index], param, value)
|
||||||
assert getattr(tests.strip[index], param) == value
|
assert getattr(vban.strip[index], param) == value
|
||||||
|
|
||||||
|
|
||||||
class TestSetAndGetFloatHigher:
|
class TestSetAndGetFloatHigher:
|
||||||
@ -113,15 +112,15 @@ class TestSetAndGetFloatHigher:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_float_params(self, index, param, value):
|
def test_it_sets_and_gets_strip_float_params(self, index, param, value):
|
||||||
setattr(tests.strip[index], param, value)
|
setattr(vban.strip[index], param, value)
|
||||||
assert getattr(tests.strip[index], param) == value
|
assert getattr(vban.strip[index], param) == value
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"index,value",
|
"index,value",
|
||||||
[(data.phys_in, 2), (data.phys_in, 2), (data.virt_in, 8), (data.virt_in, 8)],
|
[(data.phys_in, 2), (data.phys_in, 2), (data.virt_in, 8), (data.virt_in, 8)],
|
||||||
)
|
)
|
||||||
def test_it_gets_prefader_levels_and_compares_length_of_array(self, index, value):
|
def test_it_gets_prefader_levels_and_compares_length_of_array(self, index, value):
|
||||||
assert len(tests.strip[index].levels.prefader) == value
|
assert len(vban.strip[index].levels.prefader) == value
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
data.name != "potato",
|
data.name != "potato",
|
||||||
@ -137,8 +136,42 @@ class TestSetAndGetFloatHigher:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_gainlayer_values(self, index, j, value):
|
def test_it_sets_and_gets_strip_gainlayer_values(self, index, j, value):
|
||||||
tests.strip[index].gainlayer[j].gain = value
|
vban.strip[index].gainlayer[j].gain = value
|
||||||
assert tests.strip[index].gainlayer[j].gain == value
|
assert vban.strip[index].gainlayer[j].gain == value
|
||||||
|
|
||||||
|
""" strip tests, physical """
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
data.name != "potato",
|
||||||
|
reason="Only test if logged into Potato version",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"index, param, value",
|
||||||
|
[
|
||||||
|
(data.phys_in, "gainin", -8.6),
|
||||||
|
(data.phys_in, "knee", 0.24),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_it_sets_strip_comp_params(self, index, param, value):
|
||||||
|
assert hasattr(vban.strip[index].comp, param)
|
||||||
|
setattr(vban.strip[index].comp, param, value)
|
||||||
|
# we can set but not get this value. Not in RT Packet.
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
data.name != "potato",
|
||||||
|
reason="Only test if logged into Potato version",
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"index, param, value",
|
||||||
|
[
|
||||||
|
(data.phys_in, "bpsidechain", 120),
|
||||||
|
(data.phys_in, "hold", 3000),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_it_sets_and_gets_strip_gate_params(self, index, param, value):
|
||||||
|
assert hasattr(vban.strip[index].gate, param)
|
||||||
|
setattr(vban.strip[index].gate, param, value)
|
||||||
|
# we can set but not get this value. Not in RT Packet.
|
||||||
|
|
||||||
""" strip tests, virtual """
|
""" strip tests, virtual """
|
||||||
|
|
||||||
@ -151,8 +184,8 @@ class TestSetAndGetFloatHigher:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_eq_params(self, index, param, value):
|
def test_it_sets_and_gets_strip_eq_params(self, index, param, value):
|
||||||
setattr(tests.strip[index], param, value)
|
setattr(vban.strip[index], param, value)
|
||||||
assert getattr(tests.strip[index], param) == value
|
assert getattr(vban.strip[index], param) == value
|
||||||
|
|
||||||
""" bus tests, physical and virtual """
|
""" bus tests, physical and virtual """
|
||||||
|
|
||||||
@ -161,15 +194,15 @@ class TestSetAndGetFloatHigher:
|
|||||||
[(data.phys_out, "gain", -3.6), (data.virt_out, "gain", 5.8)],
|
[(data.phys_out, "gain", -3.6), (data.virt_out, "gain", 5.8)],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_bus_float_params(self, index, param, value):
|
def test_it_sets_and_gets_bus_float_params(self, index, param, value):
|
||||||
setattr(tests.bus[index], param, value)
|
setattr(vban.bus[index], param, value)
|
||||||
assert getattr(tests.bus[index], param) == value
|
assert getattr(vban.bus[index], param) == value
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"index,value",
|
"index,value",
|
||||||
[(data.phys_out, 8), (data.virt_out, 8)],
|
[(data.phys_out, 8), (data.virt_out, 8)],
|
||||||
)
|
)
|
||||||
def test_it_gets_prefader_levels_and_compares_length_of_array(self, index, value):
|
def test_it_gets_prefader_levels_and_compares_length_of_array(self, index, value):
|
||||||
assert len(tests.bus[index].levels.all) == value
|
assert len(vban.bus[index].levels.all) == value
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("value", ["test0", "test1"])
|
@pytest.mark.parametrize("value", ["test0", "test1"])
|
||||||
@ -183,8 +216,8 @@ class TestSetAndGetStringHigher:
|
|||||||
[(data.phys_in, "label"), (data.virt_in, "label")],
|
[(data.phys_in, "label"), (data.virt_in, "label")],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_strip_string_params(self, index, param, value):
|
def test_it_sets_and_gets_strip_string_params(self, index, param, value):
|
||||||
setattr(tests.strip[index], param, value)
|
setattr(vban.strip[index], param, value)
|
||||||
assert getattr(tests.strip[index], param) == value
|
assert getattr(vban.strip[index], param) == value
|
||||||
|
|
||||||
""" bus tests, physical and virtual """
|
""" bus tests, physical and virtual """
|
||||||
|
|
||||||
@ -193,5 +226,5 @@ class TestSetAndGetStringHigher:
|
|||||||
[(data.phys_out, "label"), (data.virt_out, "label")],
|
[(data.phys_out, "label"), (data.virt_out, "label")],
|
||||||
)
|
)
|
||||||
def test_it_sets_and_gets_bus_string_params(self, index, param, value):
|
def test_it_sets_and_gets_bus_string_params(self, index, param, value):
|
||||||
setattr(tests.bus[index], param, value)
|
setattr(vban.bus[index], param, value)
|
||||||
assert getattr(tests.bus[index], param) == value
|
assert getattr(vban.bus[index], param) == value
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from vban_cmd import kinds
|
|
||||||
|
|
||||||
from tests import data, tests
|
from tests import data, vban
|
||||||
|
from vban_cmd import kinds
|
||||||
|
|
||||||
|
|
||||||
class TestPublicPacketLower:
|
class TestPublicPacketLower:
|
||||||
@ -12,7 +12,7 @@ class TestPublicPacketLower:
|
|||||||
"""Tests for a valid rt data packet"""
|
"""Tests for a valid rt data packet"""
|
||||||
|
|
||||||
def test_it_gets_an_rt_data_packet(self):
|
def test_it_gets_an_rt_data_packet(self):
|
||||||
assert tests.public_packet.voicemeetertype in (
|
assert vban.public_packet.voicemeetertype in (
|
||||||
kind.name for kind in kinds.kinds_all
|
kind.name for kind in kinds.kinds_all
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ class TestSetRT:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_it_sends_a_text_request(self, kls, index, param, value):
|
def test_it_sends_a_text_request(self, kls, index, param, value):
|
||||||
tests._set_rt(f"{kls}[{index}]", param, value)
|
vban._set_rt(f"{kls}[{index}]", param, value)
|
||||||
time.sleep(0.02)
|
time.sleep(0.02)
|
||||||
target = getattr(tests, kls)[index]
|
target = getattr(vban, kls)[index]
|
||||||
assert getattr(target, param) == bool(value)
|
assert getattr(target, param) == bool(value)
|
||||||
|
@ -52,6 +52,23 @@ class Bus(IRemote):
|
|||||||
time.sleep(self._remote.DELAY)
|
time.sleep(self._remote.DELAY)
|
||||||
|
|
||||||
|
|
||||||
|
class BusEQ(IRemote):
|
||||||
|
@classmethod
|
||||||
|
def make(cls, remote, index):
|
||||||
|
BUSEQ_cls = type(
|
||||||
|
f"BusEQ{remote.kind}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
**{param: channel_bool_prop(param) for param in ["on", "ab"]},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return BUSEQ_cls(remote, index)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return f"Bus[{self.index}].eq"
|
||||||
|
|
||||||
|
|
||||||
class PhysicalBus(Bus):
|
class PhysicalBus(Bus):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{type(self).__name__}{self.index}"
|
return f"{type(self).__name__}{self.index}"
|
||||||
@ -167,11 +184,10 @@ def bus_factory(phys_bus, remote, i) -> Union[PhysicalBus, VirtualBus]:
|
|||||||
f"{BUS_cls.__name__}{remote.kind}",
|
f"{BUS_cls.__name__}{remote.kind}",
|
||||||
(BUS_cls,),
|
(BUS_cls,),
|
||||||
{
|
{
|
||||||
|
"eq": BusEQ.make(remote, i),
|
||||||
"levels": BusLevel(remote, i),
|
"levels": BusLevel(remote, i),
|
||||||
"mode": BUSMODEMIXIN_cls(remote, i),
|
"mode": BUSMODEMIXIN_cls(remote, i),
|
||||||
**{param: channel_bool_prop(param) for param in ["mute", "mono"]},
|
**{param: channel_bool_prop(param) for param in ["mute", "mono"]},
|
||||||
"eq": channel_bool_prop("eq.On"),
|
|
||||||
"eq_ab": channel_bool_prop("eq.ab"),
|
|
||||||
"label": channel_label_prop(),
|
"label": channel_label_prop(),
|
||||||
},
|
},
|
||||||
)(remote, i)
|
)(remote, i)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from .iremote import IRemote
|
from .iremote import IRemote
|
||||||
from .meta import action_prop
|
from .meta import action_fn
|
||||||
|
|
||||||
|
|
||||||
class Command(IRemote):
|
class Command(IRemote):
|
||||||
@ -21,10 +21,9 @@ class Command(IRemote):
|
|||||||
(cls,),
|
(cls,),
|
||||||
{
|
{
|
||||||
**{
|
**{
|
||||||
param: action_prop(param)
|
param: action_fn(param) for param in ["show", "shutdown", "restart"]
|
||||||
for param in ["show", "shutdown", "restart"]
|
|
||||||
},
|
},
|
||||||
"hide": action_prop("show", val=0),
|
"hide": action_fn("show", val=0),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return CMD_cls(remote)
|
return CMD_cls(remote)
|
||||||
|
@ -2,6 +2,8 @@ import itertools
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from .error import VBANCMDError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tomllib
|
import tomllib
|
||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
@ -9,6 +11,8 @@ except ModuleNotFoundError:
|
|||||||
|
|
||||||
from .kinds import request_kind_map as kindmap
|
from .kinds import request_kind_map as kindmap
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TOMLStrBuilder:
|
class TOMLStrBuilder:
|
||||||
"""builds a config profile, as a string, for the toml parser"""
|
"""builds a config profile, as a string, for the toml parser"""
|
||||||
@ -32,10 +36,18 @@ class TOMLStrBuilder:
|
|||||||
+ [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 = 0.0",
|
"comp.knob = 0.0",
|
||||||
"gate = 0.0",
|
"gate.knob = 0.0",
|
||||||
|
"denoiser.knob = 0.0",
|
||||||
|
"eq.on = false",
|
||||||
|
]
|
||||||
|
self.bus_float = ["gain = 0.0"]
|
||||||
|
self.bus_params = [
|
||||||
|
"mono = false",
|
||||||
|
"eq.on = false",
|
||||||
|
"mute = false",
|
||||||
|
"gain = 0.0",
|
||||||
]
|
]
|
||||||
self.bus_bool = ["mono = false", "eq = false", "mute = false"]
|
|
||||||
|
|
||||||
if profile == "reset":
|
if profile == "reset":
|
||||||
self.reset_config()
|
self.reset_config()
|
||||||
@ -66,7 +78,7 @@ class TOMLStrBuilder:
|
|||||||
else self.virt_strip_params
|
else self.virt_strip_params
|
||||||
)
|
)
|
||||||
case "bus":
|
case "bus":
|
||||||
toml_str += ("\n").join(self.bus_bool)
|
toml_str += ("\n").join(self.bus_params)
|
||||||
case _:
|
case _:
|
||||||
pass
|
pass
|
||||||
return toml_str + "\n"
|
return toml_str + "\n"
|
||||||
@ -119,10 +131,9 @@ class Loader(metaclass=SingletonType):
|
|||||||
loads data into memory if not found
|
loads data into memory if not found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger = logging.getLogger("config.Loader")
|
|
||||||
|
|
||||||
def __init__(self, kind):
|
def __init__(self, kind):
|
||||||
self._kind = kind
|
self._kind = kind
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self._configs = dict()
|
self._configs = dict()
|
||||||
self.defaults(kind)
|
self.defaults(kind)
|
||||||
self.parser = None
|
self.parser = None
|
||||||
@ -166,16 +177,16 @@ def loader(kind):
|
|||||||
|
|
||||||
returns configs loaded into memory
|
returns configs loaded into memory
|
||||||
"""
|
"""
|
||||||
logger = logging.getLogger("config.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(__file__).parent / "configs" / kind.name,
|
Path.home() / ".config" / "vban-cmd" / kind.name,
|
||||||
Path.home() / "Documents/Voicemeeter" / "configs" / kind.name,
|
Path.home() / "Documents" / "Voicemeeter" / "configs" / kind.name,
|
||||||
):
|
):
|
||||||
if path.is_dir():
|
if path.is_dir():
|
||||||
logger.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):
|
||||||
@ -191,6 +202,6 @@ def request_config(kind_id: str):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
configs = loader(kindmap(kind_id))
|
configs = loader(kindmap(kind_id))
|
||||||
except KeyError as e:
|
except KeyError:
|
||||||
print(f"Unknown Voicemeeter kind '{kind_id}'")
|
raise VBANCMDError(f"Unknown Voicemeeter kind {kind_id}")
|
||||||
return configs
|
return configs
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
class VBANCMDError(Exception):
|
class VBANCMDError(Exception):
|
||||||
"""general errors"""
|
"""Exception raised when general errors occur"""
|
||||||
|
|
||||||
pass
|
|
||||||
|
class VBANCMDConnectionError(Exception):
|
||||||
|
"""Exception raised when connection/timeout errors occur"""
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Iterable, Union
|
from typing import Iterable, Union
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class Event:
|
||||||
"""Keeps track of event subscriptions"""
|
"""Keeps track of event subscriptions"""
|
||||||
|
|
||||||
logger = logging.getLogger("event.event")
|
|
||||||
|
|
||||||
def __init__(self, subs: dict):
|
def __init__(self, subs: dict):
|
||||||
self.subs = subs
|
self.subs = subs
|
||||||
|
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 ()
|
||||||
|
@ -7,11 +7,14 @@ from typing import Iterable, NoReturn
|
|||||||
from .bus import request_bus_obj as bus
|
from .bus import request_bus_obj as bus
|
||||||
from .command import Command
|
from .command import Command
|
||||||
from .config import request_config as configs
|
from .config import request_config as configs
|
||||||
|
from .error import VBANCMDError
|
||||||
from .kinds import KindMapClass
|
from .kinds import KindMapClass
|
||||||
from .kinds import request_kind_map as kindmap
|
from .kinds import request_kind_map as kindmap
|
||||||
from .strip import request_strip_obj as strip
|
from .strip import request_strip_obj as strip
|
||||||
from .vbancmd import VbanCmd
|
from .vbancmd import VbanCmd
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FactoryBuilder:
|
class FactoryBuilder:
|
||||||
"""
|
"""
|
||||||
@ -20,7 +23,6 @@ class FactoryBuilder:
|
|||||||
Separates construction from representation.
|
Separates construction from representation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger = logging.getLogger("vbancmd.factorybuilder")
|
|
||||||
BuilderProgress = IntEnum("BuilderProgress", "strip bus command", start=0)
|
BuilderProgress = IntEnum("BuilderProgress", "strip bus command", start=0)
|
||||||
|
|
||||||
def __init__(self, factory, kind: KindMapClass):
|
def __init__(self, factory, kind: KindMapClass):
|
||||||
@ -31,6 +33,7 @@ class FactoryBuilder:
|
|||||||
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}",
|
||||||
)
|
)
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
|
||||||
def _pinfo(self, name: str) -> NoReturn:
|
def _pinfo(self, name: str) -> NoReturn:
|
||||||
"""prints progress status for each step"""
|
"""prints progress status for each step"""
|
||||||
@ -60,9 +63,6 @@ class FactoryBase(VbanCmd):
|
|||||||
"""Base class for factories, subclasses VbanCmd."""
|
"""Base class for factories, subclasses VbanCmd."""
|
||||||
|
|
||||||
def __init__(self, kind_id: str, **kwargs):
|
def __init__(self, kind_id: str, **kwargs):
|
||||||
defaultsubs = {"pdirty": True, "ldirty": False}
|
|
||||||
if "subs" in kwargs:
|
|
||||||
defaultsubs = defaultsubs | kwargs.pop("subs")
|
|
||||||
defaultkwargs = {
|
defaultkwargs = {
|
||||||
"ip": None,
|
"ip": None,
|
||||||
"port": 6980,
|
"port": 6980,
|
||||||
@ -70,9 +70,13 @@ class FactoryBase(VbanCmd):
|
|||||||
"bps": 0,
|
"bps": 0,
|
||||||
"channel": 0,
|
"channel": 0,
|
||||||
"ratelimit": 0.01,
|
"ratelimit": 0.01,
|
||||||
|
"timeout": 5,
|
||||||
"sync": False,
|
"sync": False,
|
||||||
"subs": defaultsubs,
|
"pdirty": False,
|
||||||
|
"ldirty": False,
|
||||||
}
|
}
|
||||||
|
if "subs" in kwargs:
|
||||||
|
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)
|
||||||
@ -188,9 +192,12 @@ def request_vbancmd_obj(kind_id: str, **kwargs) -> VbanCmd:
|
|||||||
|
|
||||||
Returns a reference to a VbanCmd class of a kind
|
Returns a reference to a VbanCmd class of a kind
|
||||||
"""
|
"""
|
||||||
|
logger_entry = logger.getChild("factory.request_vbancmd_obj")
|
||||||
|
|
||||||
VBANCMD_obj = None
|
VBANCMD_obj = None
|
||||||
try:
|
try:
|
||||||
VBANCMD_obj = vbancmd_factory(kind_id, **kwargs)
|
VBANCMD_obj = vbancmd_factory(kind_id, **kwargs)
|
||||||
except (ValueError, TypeError) as e:
|
except (ValueError, TypeError) as e:
|
||||||
raise SystemExit(e)
|
logger_entry.exception(f"{type(e).__name__}: {e}")
|
||||||
|
raise VBANCMDError(str(e)) from e
|
||||||
return VBANCMD_obj
|
return VBANCMD_obj
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Modes:
|
class Modes:
|
||||||
@ -26,9 +29,9 @@ class Modes:
|
|||||||
|
|
||||||
_mask: hex = 0x000000F0
|
_mask: hex = 0x000000F0
|
||||||
|
|
||||||
_eq_on: hex = 0x00000100
|
_on: hex = 0x00000100 # eq.on
|
||||||
_cross: hex = 0x00000200
|
_cross: hex = 0x00000200
|
||||||
_eq_ab: hex = 0x00000800
|
_ab: hex = 0x00000800 # eq.ab
|
||||||
|
|
||||||
_busa: hex = 0x00001000
|
_busa: hex = 0x00001000
|
||||||
_busa1: hex = 0x00001000
|
_busa1: hex = 0x00001000
|
||||||
@ -85,10 +88,12 @@ class IRemote(metaclass=ABCMeta):
|
|||||||
def __init__(self, remote, index=None):
|
def __init__(self, remote, index=None):
|
||||||
self._remote = remote
|
self._remote = remote
|
||||||
self.index = index
|
self.index = index
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self._modes = Modes()
|
self._modes = Modes()
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
cmd = f"{self.identifier}.{param}"
|
cmd = self._cmd(param)
|
||||||
|
self.logger.debug(f"getter: {cmd}")
|
||||||
if cmd in self._remote.cache:
|
if cmd in self._remote.cache:
|
||||||
return self._remote.cache.pop(cmd)
|
return self._remote.cache.pop(cmd)
|
||||||
if self._remote.sync:
|
if self._remote.sync:
|
||||||
@ -96,7 +101,14 @@ class IRemote(metaclass=ABCMeta):
|
|||||||
|
|
||||||
def setter(self, param, val):
|
def setter(self, param, val):
|
||||||
"""Sends a string request RT packet."""
|
"""Sends a string request RT packet."""
|
||||||
self._remote._set_rt(f"{self.identifier}", param, val)
|
self.logger.debug(f"setter: {self._cmd(param)}={val}")
|
||||||
|
self._remote._set_rt(self.identifier, param, val)
|
||||||
|
|
||||||
|
def _cmd(self, param):
|
||||||
|
cmd = (self.identifier,)
|
||||||
|
if param:
|
||||||
|
cmd += (f".{param}",)
|
||||||
|
return "".join(cmd)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
@ -113,20 +125,26 @@ class IRemote(metaclass=ABCMeta):
|
|||||||
def fget(attr, val):
|
def fget(attr, val):
|
||||||
if attr == "mode":
|
if attr == "mode":
|
||||||
return (f"mode.{val}", 1)
|
return (f"mode.{val}", 1)
|
||||||
|
elif attr == "knob":
|
||||||
|
return ("", val)
|
||||||
return (attr, val)
|
return (attr, val)
|
||||||
|
|
||||||
script = str()
|
|
||||||
for attr, val in data.items():
|
for attr, val in data.items():
|
||||||
if hasattr(self, attr):
|
if not isinstance(val, dict):
|
||||||
attr, val = fget(attr, val)
|
if attr in dir(self): # avoid calling getattr (with hasattr)
|
||||||
if isinstance(val, bool):
|
attr, val = fget(attr, val)
|
||||||
val = 1 if val else 0
|
if isinstance(val, bool):
|
||||||
|
val = 1 if val else 0
|
||||||
|
|
||||||
self._remote.cache[f"{self.identifier}.{attr}"] = val
|
self._remote.cache[self._cmd(attr)] = val
|
||||||
script += f"{self.identifier}.{attr}={val};"
|
self._remote._script += f"{self._cmd(attr)}={val};"
|
||||||
|
else:
|
||||||
self._remote.sendtext(script)
|
target = getattr(self, attr)
|
||||||
|
target.apply(val)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def then_wait(self):
|
def then_wait(self):
|
||||||
|
self.logger.debug(self._remote._script)
|
||||||
|
self._remote.sendtext(self._remote._script)
|
||||||
|
self._remote._script = str()
|
||||||
time.sleep(self._remote.DELAY)
|
time.sleep(self._remote.DELAY)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
|
|
||||||
|
from .error import VBANCMDError
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class KindId(Enum):
|
class KindId(Enum):
|
||||||
@ -97,7 +99,7 @@ def request_kind_map(kind_id):
|
|||||||
try:
|
try:
|
||||||
KIND_obj = kind_factory(kind_id)
|
KIND_obj = kind_factory(kind_id)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(e)
|
raise VBANCMDError(str(e)) from e
|
||||||
return KIND_obj
|
return KIND_obj
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ def channel_bool_prop(param):
|
|||||||
)[self.index],
|
)[self.index],
|
||||||
"little",
|
"little",
|
||||||
)
|
)
|
||||||
& getattr(self._modes, f'_{param.replace(".", "_").lower()}')
|
& getattr(self._modes, f"_{param.lower()}")
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,8 +91,8 @@ def bus_mode_prop(param):
|
|||||||
return property(fget, fset)
|
return property(fget, fset)
|
||||||
|
|
||||||
|
|
||||||
def action_prop(param, val=1):
|
def action_fn(param, val=1):
|
||||||
"""A param that performs an action"""
|
"""A function that performs an action"""
|
||||||
|
|
||||||
def fdo(self):
|
def fdo(self):
|
||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
|
from .kinds import KindMapClass
|
||||||
from .util import comp
|
from .util import comp
|
||||||
|
|
||||||
VBAN_SERVICE_RTPACKETREGISTER = 32
|
VBAN_SERVICE_RTPACKETREGISTER = 32
|
||||||
@ -13,11 +13,29 @@ HEADER_SIZE = 4 + 1 + 1 + 1 + 1 + 16 + 4
|
|||||||
class VbanRtPacket:
|
class VbanRtPacket:
|
||||||
"""Represents the body of a VBAN RT data packet"""
|
"""Represents the body of a VBAN RT data packet"""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
_kind: KindMapClass
|
||||||
for k, v in kwargs.items():
|
_voicemeeterType: bytes
|
||||||
setattr(self, k, v)
|
_reserved: bytes
|
||||||
self._strip_level = self._generate_levels(self._inputLeveldB100)
|
_buffersize: bytes
|
||||||
self._bus_level = self._generate_levels(self._outputLeveldB100)
|
_voicemeeterVersion: bytes
|
||||||
|
_optionBits: bytes
|
||||||
|
_samplerate: bytes
|
||||||
|
_inputLeveldB100: bytes
|
||||||
|
_outputLeveldB100: bytes
|
||||||
|
_TransportBit: bytes
|
||||||
|
_stripState: bytes
|
||||||
|
_busState: bytes
|
||||||
|
_stripGaindB100Layer1: bytes
|
||||||
|
_stripGaindB100Layer2: bytes
|
||||||
|
_stripGaindB100Layer3: bytes
|
||||||
|
_stripGaindB100Layer4: bytes
|
||||||
|
_stripGaindB100Layer5: bytes
|
||||||
|
_stripGaindB100Layer6: bytes
|
||||||
|
_stripGaindB100Layer7: bytes
|
||||||
|
_stripGaindB100Layer8: bytes
|
||||||
|
_busGaindB100: bytes
|
||||||
|
_stripLabelUTF8c60: bytes
|
||||||
|
_busLabelUTF8c60: bytes
|
||||||
|
|
||||||
def _generate_levels(self, levelarray) -> tuple:
|
def _generate_levels(self, levelarray) -> tuple:
|
||||||
return tuple(
|
return tuple(
|
||||||
@ -25,6 +43,14 @@ class VbanRtPacket:
|
|||||||
for i in range(0, len(levelarray), 2)
|
for i in range(0, len(levelarray), 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def strip_levels(self):
|
||||||
|
return self._generate_levels(self._inputLeveldB100)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bus_levels(self):
|
||||||
|
return self._generate_levels(self._outputLeveldB100)
|
||||||
|
|
||||||
def pdirty(self, other) -> bool:
|
def pdirty(self, other) -> bool:
|
||||||
"""True iff any defined parameter has changed"""
|
"""True iff any defined parameter has changed"""
|
||||||
|
|
||||||
@ -46,8 +72,8 @@ class VbanRtPacket:
|
|||||||
|
|
||||||
def ldirty(self, strip_cache, bus_cache) -> bool:
|
def ldirty(self, strip_cache, bus_cache) -> bool:
|
||||||
self._strip_comp, self._bus_comp = (
|
self._strip_comp, self._bus_comp = (
|
||||||
tuple(not val for val in comp(strip_cache, self._strip_level)),
|
tuple(not val for val in comp(strip_cache, self.strip_levels)),
|
||||||
tuple(not val for val in comp(bus_cache, self._bus_level)),
|
tuple(not val for val in comp(bus_cache, self.bus_levels)),
|
||||||
)
|
)
|
||||||
return any(any(l) for l in (self._strip_comp, self._bus_comp))
|
return any(any(l) for l in (self._strip_comp, self._bus_comp))
|
||||||
|
|
||||||
@ -77,12 +103,12 @@ class VbanRtPacket:
|
|||||||
@property
|
@property
|
||||||
def inputlevels(self) -> tuple:
|
def inputlevels(self) -> tuple:
|
||||||
"""returns the entire level array across all inputs for a kind"""
|
"""returns the entire level array across all inputs for a kind"""
|
||||||
return self._strip_level[0 : (2 * self._kind.phys_in + 8 * self._kind.virt_in)]
|
return self.strip_levels[0 : (2 * self._kind.phys_in + 8 * self._kind.virt_in)]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def outputlevels(self) -> tuple:
|
def outputlevels(self) -> tuple:
|
||||||
"""returns the entire level array across all outputs for a kind"""
|
"""returns the entire level array across all outputs for a kind"""
|
||||||
return self._bus_level[0 : 8 * self._kind.num_bus]
|
return self.bus_levels[0 : 8 * self._kind.num_bus]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stripstate(self) -> tuple:
|
def stripstate(self) -> tuple:
|
||||||
|
@ -51,25 +51,22 @@ class Strip(IRemote):
|
|||||||
|
|
||||||
|
|
||||||
class PhysicalStrip(Strip):
|
class PhysicalStrip(Strip):
|
||||||
|
@classmethod
|
||||||
|
def make(cls, remote, index):
|
||||||
|
return type(
|
||||||
|
f"PhysicalStrip{remote.kind}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
"comp": StripComp(remote, index),
|
||||||
|
"gate": StripGate(remote, index),
|
||||||
|
"denoiser": StripDenoiser(remote, index),
|
||||||
|
"eq": StripEQ(remote, index),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{type(self).__name__}{self.index}"
|
return f"{type(self).__name__}{self.index}"
|
||||||
|
|
||||||
@property
|
|
||||||
def comp(self) -> float:
|
|
||||||
return
|
|
||||||
|
|
||||||
@comp.setter
|
|
||||||
def comp(self, val: float):
|
|
||||||
self.setter("Comp", val)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def gate(self) -> float:
|
|
||||||
return
|
|
||||||
|
|
||||||
@gate.setter
|
|
||||||
def gate(self, val: float):
|
|
||||||
self.setter("gate", val)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device(self):
|
def device(self):
|
||||||
return
|
return
|
||||||
@ -79,6 +76,182 @@ class PhysicalStrip(Strip):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
class StripComp(IRemote):
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return f"Strip[{self.index}].comp"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def knob(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@knob.setter
|
||||||
|
def knob(self, val: float):
|
||||||
|
self.setter("", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gainin(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@gainin.setter
|
||||||
|
def gainin(self, val: float):
|
||||||
|
self.setter("GainIn", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ratio(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@ratio.setter
|
||||||
|
def ratio(self, val: float):
|
||||||
|
self.setter("Ratio", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def threshold(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@threshold.setter
|
||||||
|
def threshold(self, val: float):
|
||||||
|
self.setter("Threshold", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@attack.setter
|
||||||
|
def attack(self, val: float):
|
||||||
|
self.setter("Attack", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def release(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@release.setter
|
||||||
|
def release(self, val: float):
|
||||||
|
self.setter("Release", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def knee(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@knee.setter
|
||||||
|
def knee(self, val: float):
|
||||||
|
self.setter("Knee", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gainout(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@gainout.setter
|
||||||
|
def gainout(self, val: float):
|
||||||
|
self.setter("GainOut", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def makeup(self) -> bool:
|
||||||
|
return
|
||||||
|
|
||||||
|
@makeup.setter
|
||||||
|
def makeup(self, val: bool):
|
||||||
|
self.setter("makeup", 1 if val else 0)
|
||||||
|
|
||||||
|
|
||||||
|
class StripGate(IRemote):
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return f"Strip[{self.index}].gate"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def knob(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@knob.setter
|
||||||
|
def knob(self, val: float):
|
||||||
|
self.setter("", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def threshold(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@threshold.setter
|
||||||
|
def threshold(self, val: float):
|
||||||
|
self.setter("Threshold", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def damping(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@damping.setter
|
||||||
|
def damping(self, val: float):
|
||||||
|
self.setter("Damping", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bpsidechain(self) -> int:
|
||||||
|
return
|
||||||
|
|
||||||
|
@bpsidechain.setter
|
||||||
|
def bpsidechain(self, val: int):
|
||||||
|
self.setter("BPSidechain", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def attack(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@attack.setter
|
||||||
|
def attack(self, val: float):
|
||||||
|
self.setter("Attack", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hold(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@hold.setter
|
||||||
|
def hold(self, val: float):
|
||||||
|
self.setter("Hold", val)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def release(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@release.setter
|
||||||
|
def release(self, val: float):
|
||||||
|
self.setter("Release", val)
|
||||||
|
|
||||||
|
|
||||||
|
class StripDenoiser(IRemote):
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return f"Strip[{self.index}].denoiser"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def knob(self) -> float:
|
||||||
|
return
|
||||||
|
|
||||||
|
@knob.setter
|
||||||
|
def knob(self, val: float):
|
||||||
|
self.setter("", val)
|
||||||
|
|
||||||
|
|
||||||
|
class StripEQ(IRemote):
|
||||||
|
@property
|
||||||
|
def identifier(self) -> str:
|
||||||
|
return f"Strip[{self.index}].eq"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@on.setter
|
||||||
|
def on(self, val: bool):
|
||||||
|
self.setter("on", 1 if val else 0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ab(self):
|
||||||
|
return
|
||||||
|
|
||||||
|
@ab.setter
|
||||||
|
def ab(self, val: bool):
|
||||||
|
self.setter("ab", 1 if val else 0)
|
||||||
|
|
||||||
|
|
||||||
class VirtualStrip(Strip):
|
class VirtualStrip(Strip):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{type(self).__name__}{self.index}"
|
return f"{type(self).__name__}{self.index}"
|
||||||
@ -232,7 +405,7 @@ def strip_factory(is_phys_strip, remote, i) -> Union[PhysicalStrip, VirtualStrip
|
|||||||
|
|
||||||
Returns a physical or virtual strip subclass
|
Returns a physical or virtual strip subclass
|
||||||
"""
|
"""
|
||||||
STRIP_cls = PhysicalStrip if is_phys_strip else VirtualStrip
|
STRIP_cls = PhysicalStrip.make(remote, i) if is_phys_strip else VirtualStrip
|
||||||
CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name]
|
CHANNELOUTMIXIN_cls = _make_channelout_mixins[remote.kind.name]
|
||||||
GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i)
|
GAINLAYERMIXIN_cls = _make_gainlayer_mixin(remote, i)
|
||||||
|
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Subject:
|
class Subject:
|
||||||
"""Adds support for observers"""
|
|
||||||
|
|
||||||
logger = logging.getLogger("subject.subject")
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""list of current observers"""
|
"""Adds support for observers and callbacks"""
|
||||||
|
|
||||||
self._observers = list()
|
self._observers = list()
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def observers(self) -> list:
|
def observers(self) -> list:
|
||||||
@ -17,38 +16,57 @@ class Subject:
|
|||||||
|
|
||||||
return self._observers
|
return self._observers
|
||||||
|
|
||||||
def notify(self, modifier=None):
|
def notify(self, event):
|
||||||
"""run callbacks on update"""
|
"""run callbacks on update"""
|
||||||
|
|
||||||
[o.on_update(modifier) for o in self._observers]
|
for o in self._observers:
|
||||||
|
if hasattr(o, "on_update"):
|
||||||
|
o.on_update(event)
|
||||||
|
else:
|
||||||
|
if o.__name__ == f"on_{event}":
|
||||||
|
o()
|
||||||
|
|
||||||
def add(self, observer):
|
def add(self, observer):
|
||||||
"""adds an observer to _observers"""
|
"""adds an observer to observers"""
|
||||||
|
|
||||||
if observer not in self._observers:
|
try:
|
||||||
self._observers.append(observer)
|
iterator = iter(observer)
|
||||||
self.logger.info(f"{type(observer).__name__} added to event observers")
|
for o in iterator:
|
||||||
else:
|
if o not in self._observers:
|
||||||
self.logger.error(
|
self._observers.append(o)
|
||||||
f"Failed to add {type(observer).__name__} to event observers"
|
self.logger.info(f"{o} added to event observers")
|
||||||
)
|
else:
|
||||||
|
self.logger.error(f"Failed to add {o} to event observers")
|
||||||
|
except TypeError:
|
||||||
|
if observer not in self._observers:
|
||||||
|
self._observers.append(observer)
|
||||||
|
self.logger.info(f"{observer} added to event observers")
|
||||||
|
else:
|
||||||
|
self.logger.error(f"Failed to add {observer} to event observers")
|
||||||
|
|
||||||
register = add
|
register = add
|
||||||
|
|
||||||
def remove(self, observer):
|
def remove(self, observer):
|
||||||
"""removes an observer from _observers"""
|
"""removes an observer from observers"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._observers.remove(observer)
|
iterator = iter(observer)
|
||||||
self.logger.info(f"{type(observer).__name__} removed from event observers")
|
for o in iterator:
|
||||||
except ValueError:
|
try:
|
||||||
self.logger.error(
|
self._observers.remove(o)
|
||||||
f"Failed to remove {type(observer).__name__} from event observers"
|
self.logger.info(f"{o} removed from event observers")
|
||||||
)
|
except ValueError:
|
||||||
|
self.logger.error(f"Failed to remove {o} from event observers")
|
||||||
|
except TypeError:
|
||||||
|
try:
|
||||||
|
self._observers.remove(observer)
|
||||||
|
self.logger.info(f"{observer} removed from event observers")
|
||||||
|
except ValueError:
|
||||||
|
self.logger.error(f"Failed to remove {observer} from event observers")
|
||||||
|
|
||||||
deregister = remove
|
deregister = remove
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""clears the _observers list"""
|
"""clears the observers list"""
|
||||||
|
|
||||||
self._observers.clear()
|
self._observers.clear()
|
||||||
|
@ -3,18 +3,17 @@ import socket
|
|||||||
import time
|
import time
|
||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from queue import Queue
|
||||||
from typing import Iterable, Optional, Union
|
from typing import Iterable, Optional, Union
|
||||||
|
|
||||||
try:
|
from .error import VBANCMDError
|
||||||
import tomllib
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
import tomli as tomllib
|
|
||||||
|
|
||||||
from .event import Event
|
from .event import Event
|
||||||
from .packet import RequestHeader
|
from .packet import RequestHeader
|
||||||
from .subject import Subject
|
from .subject import Subject
|
||||||
from .util import Socket, script
|
from .util import Socket, script
|
||||||
from .worker import Subscriber, Updater
|
from .worker import Producer, Subscriber, Updater
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class VbanCmd(metaclass=ABCMeta):
|
class VbanCmd(metaclass=ABCMeta):
|
||||||
@ -28,15 +27,14 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
1000000, 1500000, 2000000, 3000000,
|
1000000, 1500000, 2000000, 3000000,
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
logger = logging.getLogger("vbancmd.vbancmd")
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
self.event = Event({k: kwargs.pop(k) for k in ("pdirty", "ldirty")})
|
||||||
|
if not kwargs["ip"]:
|
||||||
|
kwargs |= self._conn_from_toml()
|
||||||
for attr, val in kwargs.items():
|
for attr, val in kwargs.items():
|
||||||
setattr(self, attr, val)
|
setattr(self, attr, val)
|
||||||
if self.ip is None:
|
|
||||||
conn = self._conn_from_toml()
|
|
||||||
for attr, val in conn.items():
|
|
||||||
setattr(self, attr, val)
|
|
||||||
|
|
||||||
self.packet_request = RequestHeader(
|
self.packet_request = RequestHeader(
|
||||||
name=self.streamname,
|
name=self.streamname,
|
||||||
@ -46,9 +44,8 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
self.socks = tuple(
|
self.socks = tuple(
|
||||||
socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for _ in Socket
|
socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for _ in Socket
|
||||||
)
|
)
|
||||||
self.subject = Subject()
|
self.subject = self.observer = Subject()
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
self.event = Event(self.subs)
|
|
||||||
self._pdirty = False
|
self._pdirty = False
|
||||||
self._ldirty = False
|
self._ldirty = False
|
||||||
|
|
||||||
@ -57,11 +54,31 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
"""Ensure subclasses override str magic method"""
|
"""Ensure subclasses override str magic method"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _conn_from_toml(self) -> str:
|
def _conn_from_toml(self) -> dict:
|
||||||
filepath = Path.cwd() / "vban.toml"
|
try:
|
||||||
with open(filepath, "rb") as f:
|
import tomllib
|
||||||
conn = tomllib.load(f)
|
except ModuleNotFoundError:
|
||||||
return conn["connection"]
|
import tomli as tomllib
|
||||||
|
|
||||||
|
def get_filepath():
|
||||||
|
filepaths = [
|
||||||
|
Path.cwd() / "vban.toml",
|
||||||
|
Path.home() / ".config" / "vban-cmd" / "vban.toml",
|
||||||
|
Path.home() / "Documents" / "Voicemeeter" / "vban.toml",
|
||||||
|
]
|
||||||
|
for filepath in filepaths:
|
||||||
|
if filepath.exists():
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
if filepath := get_filepath():
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
conn = tomllib.load(f)
|
||||||
|
assert (
|
||||||
|
"ip" in conn["connection"]
|
||||||
|
), "please provide ip, by kwarg or config"
|
||||||
|
return conn["connection"]
|
||||||
|
else:
|
||||||
|
raise VBANCMDError("no ip provided and no vban.toml located.")
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.login()
|
self.login()
|
||||||
@ -75,8 +92,11 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
self.subscriber = Subscriber(self)
|
self.subscriber = Subscriber(self)
|
||||||
self.subscriber.start()
|
self.subscriber.start()
|
||||||
|
|
||||||
self.updater = Updater(self)
|
queue = Queue()
|
||||||
|
self.updater = Updater(self, queue)
|
||||||
self.updater.start()
|
self.updater.start()
|
||||||
|
self.producer = Producer(self, queue)
|
||||||
|
self.producer.start()
|
||||||
|
|
||||||
self.logger.info(f"{type(self).__name__}: Successfully logged into {self}")
|
self.logger.info(f"{type(self).__name__}: Successfully logged into {self}")
|
||||||
|
|
||||||
@ -87,20 +107,27 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
val: Optional[Union[int, float]] = None,
|
val: Optional[Union[int, float]] = None,
|
||||||
):
|
):
|
||||||
"""Sends a string request command over a network."""
|
"""Sends a string request command over a network."""
|
||||||
cmd = id_ if not param else f"{id_}.{param}={val};"
|
cmd = f"{id_}={val};" if not param else f"{id_}.{param}={val};"
|
||||||
self.socks[Socket.request].sendto(
|
self.socks[Socket.request].sendto(
|
||||||
self.packet_request.header + cmd.encode(),
|
self.packet_request.header + cmd.encode(),
|
||||||
(socket.gethostbyname(self.ip), self.port),
|
(socket.gethostbyname(self.ip), self.port),
|
||||||
)
|
)
|
||||||
count = int.from_bytes(self.packet_request.framecounter, "little") + 1
|
self.packet_request.framecounter = (
|
||||||
self.packet_request.framecounter = count.to_bytes(4, "little")
|
int.from_bytes(self.packet_request.framecounter, "little") + 1
|
||||||
|
).to_bytes(4, "little")
|
||||||
if param:
|
if param:
|
||||||
self.cache[f"{id_}.{param}"] = val
|
self.cache[f"{id_}.{param}"] = val
|
||||||
|
|
||||||
@script
|
@script
|
||||||
def sendtext(self, cmd):
|
def sendtext(self, cmd):
|
||||||
"""Sends a multiple parameter string over a network."""
|
"""Sends a multiple parameter string over a network."""
|
||||||
self._set_rt(cmd)
|
self.socks[Socket.request].sendto(
|
||||||
|
self.packet_request.header + cmd.encode(),
|
||||||
|
(socket.gethostbyname(self.ip), self.port),
|
||||||
|
)
|
||||||
|
self.packet_request.framecounter = (
|
||||||
|
int.from_bytes(self.packet_request.framecounter, "little") + 1
|
||||||
|
).to_bytes(4, "little")
|
||||||
time.sleep(self.DELAY)
|
time.sleep(self.DELAY)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -157,6 +184,7 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
else:
|
else:
|
||||||
raise ValueError(obj)
|
raise ValueError(obj)
|
||||||
|
|
||||||
|
self._script = str()
|
||||||
[param(key).apply(datum).then_wait() for key, datum in data.items()]
|
[param(key).apply(datum).then_wait() for key, datum in data.items()]
|
||||||
|
|
||||||
def apply_config(self, name):
|
def apply_config(self, name):
|
||||||
@ -168,7 +196,7 @@ class VbanCmd(metaclass=ABCMeta):
|
|||||||
try:
|
try:
|
||||||
self.apply(self.configs[name])
|
self.apply(self.configs[name])
|
||||||
self.logger.info(f"Profile '{name}' applied!")
|
self.logger.info(f"Profile '{name}' applied!")
|
||||||
except KeyError as e:
|
except KeyError:
|
||||||
self.logger.error(("\n").join(error_msg))
|
self.logger.error(("\n").join(error_msg))
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
|
@ -4,60 +4,66 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .error import VBANCMDError
|
from .error import VBANCMDConnectionError
|
||||||
from .packet import HEADER_SIZE, SubscribeHeader, VbanRtPacket, VbanRtPacketHeader
|
from .packet import HEADER_SIZE, SubscribeHeader, VbanRtPacket, VbanRtPacketHeader
|
||||||
from .util import Socket, comp
|
from .util import Socket
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Subscriber(threading.Thread):
|
class Subscriber(threading.Thread):
|
||||||
"""fire a subscription packet every 10 seconds"""
|
"""fire a subscription packet every 10 seconds"""
|
||||||
|
|
||||||
def __init__(self, remote):
|
def __init__(self, remote):
|
||||||
super().__init__(name="subscriber", target=self.subscribe, daemon=True)
|
super().__init__(name="subscriber", daemon=True)
|
||||||
self._remote = remote
|
self._remote = remote
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.packet = SubscribeHeader()
|
self.packet = SubscribeHeader()
|
||||||
|
|
||||||
def subscribe(self):
|
def run(self):
|
||||||
while self._remote.running:
|
while self._remote.running:
|
||||||
try:
|
try:
|
||||||
self._remote.socks[Socket.register].sendto(
|
self._remote.socks[Socket.register].sendto(
|
||||||
self.packet.header,
|
self.packet.header,
|
||||||
(socket.gethostbyname(self._remote.ip), self._remote.port),
|
(socket.gethostbyname(self._remote.ip), self._remote.port),
|
||||||
)
|
)
|
||||||
count = int.from_bytes(self.packet.framecounter, "little") + 1
|
self.packet.framecounter = (
|
||||||
self.packet.framecounter = count.to_bytes(4, "little")
|
int.from_bytes(self.packet.framecounter, "little") + 1
|
||||||
|
).to_bytes(4, "little")
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
except socket.gaierror:
|
except socket.gaierror as e:
|
||||||
err_msg = f"Unable to resolve hostname {self._remote.ip}"
|
self.logger.exception(f"{type(e).__name__}: {e}")
|
||||||
print(err_msg)
|
raise VBANCMDConnectionError(
|
||||||
raise VBANCMDError(err_msg)
|
f"unable to resolve hostname {self._remote.ip}"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
|
||||||
class Updater(threading.Thread):
|
class Producer(threading.Thread):
|
||||||
"""
|
"""Continously send job queue to the Updater thread at a rate of self._remote.ratelimit."""
|
||||||
continously updates the public packet
|
|
||||||
|
|
||||||
notifies observers of event updates
|
def __init__(self, remote, queue):
|
||||||
"""
|
super().__init__(name="producer", daemon=True)
|
||||||
|
|
||||||
logger = logging.getLogger("worker.updater")
|
|
||||||
|
|
||||||
def __init__(self, remote):
|
|
||||||
super().__init__(name="updater", target=self.update, daemon=True)
|
|
||||||
self._remote = remote
|
self._remote = remote
|
||||||
self._remote.socks[Socket.response].settimeout(5)
|
self.queue = queue
|
||||||
self._remote.socks[Socket.response].bind(
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
(socket.gethostbyname(socket.gethostname()), self._remote.port)
|
|
||||||
)
|
|
||||||
self.packet_expected = VbanRtPacketHeader()
|
self.packet_expected = VbanRtPacketHeader()
|
||||||
self._remote._public_packet = self._get_rt()
|
self._remote._public_packet = self._get_rt()
|
||||||
(
|
(
|
||||||
self._remote.cache["strip_level"],
|
self._remote.cache["strip_level"],
|
||||||
self._remote.cache["bus_level"],
|
self._remote.cache["bus_level"],
|
||||||
) = self._remote._get_levels(self._remote.public_packet)
|
) = self._remote._get_levels(self._remote.public_packet)
|
||||||
p_in, v_in = self._remote.kind.ins
|
|
||||||
self._remote._strip_comp = [False] * (2 * p_in + 8 * v_in)
|
def _get_rt(self) -> VbanRtPacket:
|
||||||
self._remote._bus_comp = [False] * (self._remote.kind.num_bus * 8)
|
"""Attempt to fetch data packet until a valid one found"""
|
||||||
|
|
||||||
|
def fget():
|
||||||
|
data = None
|
||||||
|
while not data:
|
||||||
|
data = self._fetch_rt_packet()
|
||||||
|
time.sleep(self._remote.DELAY)
|
||||||
|
return data
|
||||||
|
|
||||||
|
return fget()
|
||||||
|
|
||||||
def _fetch_rt_packet(self) -> Optional[VbanRtPacket]:
|
def _fetch_rt_packet(self) -> Optional[VbanRtPacket]:
|
||||||
try:
|
try:
|
||||||
@ -66,7 +72,6 @@ class Updater(threading.Thread):
|
|||||||
if len(data) > HEADER_SIZE:
|
if len(data) > HEADER_SIZE:
|
||||||
# check if packet is of type rt packet response
|
# check if packet is of type rt packet response
|
||||||
if self.packet_expected.header == data[: HEADER_SIZE - 4]:
|
if self.packet_expected.header == data[: HEADER_SIZE - 4]:
|
||||||
self.logger.debug("valid packet received")
|
|
||||||
return VbanRtPacket(
|
return VbanRtPacket(
|
||||||
_kind=self._remote.kind,
|
_kind=self._remote.kind,
|
||||||
_voicemeeterType=data[28:29],
|
_voicemeeterType=data[28:29],
|
||||||
@ -92,26 +97,14 @@ class Updater(threading.Thread):
|
|||||||
_stripLabelUTF8c60=data[452:932],
|
_stripLabelUTF8c60=data[452:932],
|
||||||
_busLabelUTF8c60=data[932:1412],
|
_busLabelUTF8c60=data[932:1412],
|
||||||
)
|
)
|
||||||
except TimeoutError:
|
except TimeoutError as e:
|
||||||
err_msg = f"Unable to establish connection with {self._remote.ip}"
|
self.logger.exception(f"{type(e).__name__}: {e}")
|
||||||
print(err_msg)
|
raise VBANCMDConnectionError(
|
||||||
raise VBANCMDError(err_msg)
|
f"timeout waiting for RtPacket from {self._remote.ip}"
|
||||||
|
) from e
|
||||||
|
|
||||||
def _get_rt(self) -> VbanRtPacket:
|
def run(self):
|
||||||
"""Attempt to fetch data packet until a valid one found"""
|
|
||||||
|
|
||||||
def fget():
|
|
||||||
data = None
|
|
||||||
while not data:
|
|
||||||
data = self._fetch_rt_packet()
|
|
||||||
time.sleep(self._remote.DELAY)
|
|
||||||
return data
|
|
||||||
|
|
||||||
return fget()
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
while self._remote.running:
|
while self._remote.running:
|
||||||
start = time.time()
|
|
||||||
_pp = self._get_rt()
|
_pp = self._get_rt()
|
||||||
pdirty = _pp.pdirty(self._remote.public_packet)
|
pdirty = _pp.pdirty(self._remote.public_packet)
|
||||||
ldirty = _pp.ldirty(
|
ldirty = _pp.ldirty(
|
||||||
@ -119,24 +112,63 @@ class Updater(threading.Thread):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if pdirty or ldirty:
|
if pdirty or ldirty:
|
||||||
self.logger.debug("dirty state, updating public packet")
|
|
||||||
self._remote._public_packet = _pp
|
self._remote._public_packet = _pp
|
||||||
self._remote._pdirty = pdirty
|
self._remote._pdirty = pdirty
|
||||||
self._remote._ldirty = ldirty
|
self._remote._ldirty = ldirty
|
||||||
|
|
||||||
if self._remote.event.pdirty and self._remote.pdirty:
|
if self._remote.event.pdirty:
|
||||||
self._remote.subject.notify("pdirty")
|
self.queue.put("pdirty")
|
||||||
if self._remote.event.ldirty and self._remote.ldirty:
|
if self._remote.event.ldirty:
|
||||||
|
self.queue.put("ldirty")
|
||||||
|
time.sleep(self._remote.ratelimit)
|
||||||
|
self.logger.debug(f"terminating {self.name} thread")
|
||||||
|
self.queue.put(None)
|
||||||
|
|
||||||
|
|
||||||
|
class Updater(threading.Thread):
|
||||||
|
"""
|
||||||
|
continously updates the public packet
|
||||||
|
|
||||||
|
notifies observers of event updates
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, remote, queue):
|
||||||
|
super().__init__(name="updater", daemon=True)
|
||||||
|
self._remote = remote
|
||||||
|
self.queue = queue
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
self._remote.socks[Socket.response].settimeout(self._remote.timeout)
|
||||||
|
self._remote.socks[Socket.response].bind(
|
||||||
|
(socket.gethostbyname(socket.gethostname()), self._remote.port)
|
||||||
|
)
|
||||||
|
p_in, v_in = self._remote.kind.ins
|
||||||
|
self._remote._strip_comp = [False] * (2 * p_in + 8 * v_in)
|
||||||
|
self._remote._bus_comp = [False] * (self._remote.kind.num_bus * 8)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Continously update observers of dirty states.
|
||||||
|
|
||||||
|
Generate _strip_comp, _bus_comp and update level cache if ldirty.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
event = self.queue.get()
|
||||||
|
if event is None:
|
||||||
|
self.logger.debug(f"terminating {self.name} thread")
|
||||||
|
break
|
||||||
|
|
||||||
|
if event == "pdirty" and self._remote.pdirty:
|
||||||
|
self._remote.subject.notify(event)
|
||||||
|
elif event == "ldirty" and self._remote.ldirty:
|
||||||
self._remote._strip_comp, self._remote._bus_comp = (
|
self._remote._strip_comp, self._remote._bus_comp = (
|
||||||
_pp._strip_comp,
|
self._remote._public_packet._strip_comp,
|
||||||
_pp._bus_comp,
|
self._remote._public_packet._bus_comp,
|
||||||
)
|
)
|
||||||
self._remote.cache["strip_level"], self._remote.cache["bus_level"] = (
|
(
|
||||||
_pp.inputlevels,
|
self._remote.cache["strip_level"],
|
||||||
_pp.outputlevels,
|
self._remote.cache["bus_level"],
|
||||||
|
) = (
|
||||||
|
self._remote._public_packet.inputlevels,
|
||||||
|
self._remote._public_packet.outputlevels,
|
||||||
)
|
)
|
||||||
self._remote.subject.notify("ldirty")
|
self._remote.subject.notify(event)
|
||||||
|
|
||||||
elapsed = time.time() - start
|
|
||||||
if self._remote.ratelimit - elapsed > 0:
|
|
||||||
time.sleep(self._remote.ratelimit - elapsed)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user