mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2024-11-15 17:40:52 +00:00
gui rewritten with builder classes and observables
gui rewritten with builder classes and observables
This commit is contained in:
parent
69eed318c9
commit
92aead8754
172
README.md
172
README.md
@ -1,171 +1 @@
|
|||||||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/voicemeeter-compact/blob/main/LICENSE)
|
# Not Ready Yet
|
||||||
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
|
|
||||||
![OS: Windows](https://img.shields.io/badge/os-windows-red)
|
|
||||||
|
|
||||||
![Image of app/potato size comparison](./doc_imgs/potatocomparisonsmaller.png)
|
|
||||||
|
|
||||||
# Voicemeeter Compact
|
|
||||||
|
|
||||||
A compact Voicemeeter remote app, works locally and over LAN.
|
|
||||||
|
|
||||||
For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- [Voicemeeter](https://voicemeeter.com/) (Basic v1.0.8.2), (Banana v2.0.6.2) or (Potato v3.0.2.2)
|
|
||||||
- [Git for Windows](https://gitforwindows.org/)
|
|
||||||
- Python 3.9+
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
For a step-by-step guide [click here](INSTALLATION.md)
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://github.com/onyx-and-iris/voicemeeter-compact
|
|
||||||
cd voicemeeter-compact
|
|
||||||
pip install .
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Example `__main__.py` file:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import voicemeeter
|
|
||||||
import vmcompact
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# pass the kind_id and the vmr object to the app
|
|
||||||
with voicemeeter.remote(kind_id) as vmr:
|
|
||||||
app = vmcompact.connect(kind_id, vmr)
|
|
||||||
app.mainloop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# choose the kind of Voicemeeter (Local connection)
|
|
||||||
kind_id = "banana"
|
|
||||||
|
|
||||||
voicemeeter.launch(kind_id, hide=False)
|
|
||||||
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
It's important to know that only labelled strips and buses will appear in the Channel frames. Removing a Channels label will cause the GUI to grow/shrink in real time.
|
|
||||||
|
|
||||||
![Image of unlabelled app](./doc_imgs/nolabels.png)
|
|
||||||
|
|
||||||
If the GUI looks like the above when you first load it, then no channels are labelled. From the menu, `Profiles->Load Profile` you may load an example config. Save your current Voicemeeter settings first :).
|
|
||||||
|
|
||||||
### kind_id
|
|
||||||
|
|
||||||
A _kind_id_ specifies a major Voicemeeter version. This may be one of:
|
|
||||||
|
|
||||||
- `basic`
|
|
||||||
- `banana`
|
|
||||||
- `potato`
|
|
||||||
|
|
||||||
## TOML Files
|
|
||||||
|
|
||||||
This is how your files should be organised. Wherever your `__main__.py` file is located (after install this can be any location), `config` and `profiles` directories
|
|
||||||
should be in the same location.
|
|
||||||
Regarding profiles, a directory for each kind should hold the profile files and in each there can be any number of config files. Example, a config for streaming, a config for general listening/movie watching or any other type of config.
|
|
||||||
|
|
||||||
.
|
|
||||||
|
|
||||||
├── `__main__.py`
|
|
||||||
|
|
||||||
├── configs
|
|
||||||
|
|
||||||
├── app.toml
|
|
||||||
|
|
||||||
├── vban.toml
|
|
||||||
|
|
||||||
├── profiles
|
|
||||||
|
|
||||||
├── basic
|
|
||||||
|
|
||||||
├── example.toml
|
|
||||||
|
|
||||||
├── other_config.toml
|
|
||||||
|
|
||||||
├── streaming_config.toml
|
|
||||||
|
|
||||||
├── banana
|
|
||||||
|
|
||||||
├── example.toml
|
|
||||||
|
|
||||||
├── other.toml
|
|
||||||
|
|
||||||
├── ...
|
|
||||||
|
|
||||||
├── potato
|
|
||||||
|
|
||||||
├── example.toml
|
|
||||||
|
|
||||||
├── ...
|
|
||||||
|
|
||||||
## Configs
|
|
||||||
|
|
||||||
### app.toml
|
|
||||||
|
|
||||||
Configure certain startup states for the app.
|
|
||||||
|
|
||||||
- `profiles`
|
|
||||||
Configure a profile to load on app startup. Don't include the .toml extension in the profile name.
|
|
||||||
|
|
||||||
- `theme`
|
|
||||||
By default the app loads up the [Sun Valley light or dark theme](https://github.com/rdbende/Sun-Valley-ttk-theme) by @rdbende. You have the option to load up the app without any theme loaded. Simply set `enabled` to false and `mode` will take no effect.
|
|
||||||
|
|
||||||
- `extends`
|
|
||||||
Extending the app will show both strips and buses. In reduced mode only one or the other. This app will extend both horizontally and vertically, simply set `extends_horizontal` true or false accordingly.
|
|
||||||
|
|
||||||
- `channel`
|
|
||||||
For each channel labelframe the width and height may be adjusted which effects the spacing between widgets and the length of the scales and progressbars respectively.
|
|
||||||
|
|
||||||
- `mwscroll_step`
|
|
||||||
Sets the amount (in db) the gain slider moves with a single mousewheel step. Default 3.
|
|
||||||
|
|
||||||
- `submixes`
|
|
||||||
Select the default submix bus when Submix frame is shown. For example, a dedicated bus for OBS.
|
|
||||||
|
|
||||||
### vban.toml
|
|
||||||
|
|
||||||
Configure as many vban connections as you wish. This allows the app to work over a LAN connection as well as with a local Voicemeeter installation.
|
|
||||||
|
|
||||||
For vban connections to work correctly VBAN TEXT incoming stream MUST be configured correctly on the remote machine. Both pcs ought to be connected to a local private network and should be able to ping one another.
|
|
||||||
|
|
||||||
A valid `vban.toml` might look like this:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[connection-1]
|
|
||||||
kind = 'banana'
|
|
||||||
ip = '192.168.1.2'
|
|
||||||
streamname = 'streampc'
|
|
||||||
port = 6990
|
|
||||||
|
|
||||||
[connection-2]
|
|
||||||
kind = 'potato'
|
|
||||||
ip = '192.168.1.3'
|
|
||||||
streamname = 'worklaptop'
|
|
||||||
port = 6990
|
|
||||||
```
|
|
||||||
|
|
||||||
## Profiles
|
|
||||||
|
|
||||||
Three example profiles are included with the package, one for each kind of Voicemeeter. Use these to configure parameter startup states. Any parameter supported by the underlying interfaces may be used. For a detailed description of parameter coverage see:
|
|
||||||
|
|
||||||
[Voicemeeter Remote API Python](https://github.com/onyx-and-iris/voicemeeter-api-python)
|
|
||||||
|
|
||||||
[VBAN CMD API Python](https://github.com/onyx-and-iris/vban-cmd-python)
|
|
||||||
|
|
||||||
Profiles may be loaded at any time via the menu.
|
|
||||||
|
|
||||||
## Special Thanks
|
|
||||||
|
|
||||||
[Vincent Burel](https://github.com/vburel2018) for creating Voicemeeter, its SDK, the C Remote API, the RT Packet service and Streamer View app!
|
|
||||||
|
|
||||||
[Christian Volkmann](https://github.com/chvolkmann) for the detailed work that went into creating the underlying Remote API Python Interface.
|
|
||||||
Unfortunately, the Remote API Python Interface has `NOT` been open source licensed. I have [raised an issue](https://github.com/chvolkmann/voicemeeter-remote-python/issues/13) and asked directly and politely but so far no response. If a license is added in future I will update this section. Without an open source license there is no guarantee that in future this package may not be pulled down, without any notice.
|
|
||||||
|
|
||||||
[Rdbende](https://github.com/rdbende) for creating the beautiful Sun Valley Tkinter theme and adding it to Pypi!
|
|
||||||
|
220
vmcompact/app.py
220
vmcompact/app.py
@ -2,27 +2,27 @@ import tkinter as tk
|
|||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sv_ttk
|
|
||||||
|
|
||||||
from .errors import VMCompactErrors
|
from .errors import VMCompactErrors
|
||||||
from .data import _base_vals, _kinds_all
|
from .data import _kinds_all, _configuration, _base_values
|
||||||
from .channels import ChannelFrame
|
from .subject import Subject
|
||||||
from .navigation import Navigation
|
from .builders import MainFrameBuilder
|
||||||
from .menu import Menus
|
from .menu import Menus
|
||||||
from .banner import Banner
|
|
||||||
from .configurations import configuration
|
|
||||||
|
|
||||||
|
|
||||||
class App(tk.Tk):
|
class App(tk.Tk):
|
||||||
"""Topmost Level of App"""
|
"""App mainframe"""
|
||||||
|
|
||||||
|
_instances = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def make(cls, kind: NamedTuple):
|
def make(cls, kind: NamedTuple):
|
||||||
"""
|
"""
|
||||||
Factory function for App
|
Factory function for App.
|
||||||
|
|
||||||
Returns an App class of a kind
|
Returns an App class of a kind.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
APP_cls = type(
|
APP_cls = type(
|
||||||
f"Voicemeeter{kind.name}.Compact",
|
f"Voicemeeter{kind.name}.Compact",
|
||||||
(cls,),
|
(cls,),
|
||||||
@ -34,106 +34,34 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
def __init__(self, vmr):
|
def __init__(self, vmr):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
defaults = {
|
self._vmr = vmr
|
||||||
"profiles": {
|
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
|
||||||
"profile": None,
|
if icon_path.is_file():
|
||||||
},
|
self.iconbitmap(str(icon_path))
|
||||||
"theme": {
|
self.minsize(275, False)
|
||||||
"enabled": True,
|
self.subject_pdirty = Subject()
|
||||||
"mode": "light",
|
self.subject_ldirty = Subject()
|
||||||
},
|
|
||||||
"extends": {
|
|
||||||
"extended": True,
|
|
||||||
"extends_horizontal": True,
|
|
||||||
},
|
|
||||||
"channel": {
|
|
||||||
"width": 80,
|
|
||||||
"height": 130,
|
|
||||||
},
|
|
||||||
"mwscroll_step": {
|
|
||||||
"size": 3,
|
|
||||||
},
|
|
||||||
"submixes": {
|
|
||||||
"default": 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if configuration:
|
|
||||||
self.configuration = defaults | self.configuration
|
|
||||||
else:
|
|
||||||
configuration["app"] = defaults
|
|
||||||
_base_vals.themes_enabled = self.configuration["theme"]["enabled"]
|
|
||||||
_base_vals.extends_horizontal = self.configuration["extends"][
|
|
||||||
"extends_horizontal"
|
|
||||||
]
|
|
||||||
_base_vals.submixes = self.configuration["submixes"]["default"]
|
|
||||||
_base_vals.mwscroll_step = self.configuration["mwscroll_step"]["size"]
|
|
||||||
self.bus_modes_cache = {
|
|
||||||
"vmr": list(tk.StringVar(value="normal") for _ in range(8)),
|
|
||||||
"vban": list(tk.StringVar(value="normal") for _ in range(8)),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
"profiles" in self.configuration
|
|
||||||
and self.configuration["profiles"]["profile"]
|
|
||||||
):
|
|
||||||
vmr.apply_profile(self.configuration["profiles"]["profile"])
|
|
||||||
|
|
||||||
# create menus
|
|
||||||
self["menu"] = Menus(self, vmr)
|
self["menu"] = Menus(self, vmr)
|
||||||
self.styletable = ttk.Style()
|
self.styletable = ttk.Style()
|
||||||
self._vmr = vmr
|
if _configuration.profile:
|
||||||
|
vmr.apply_profile(_configuration.profile)
|
||||||
|
|
||||||
# start watchers, initialize level arrays
|
self.build_app()
|
||||||
self.upd_pdirty()
|
|
||||||
self.strip_levels = self.target.strip_levels
|
|
||||||
self.bus_levels = self.target.bus_levels
|
|
||||||
self.watch_levels()
|
|
||||||
|
|
||||||
self.resizable(False, False)
|
|
||||||
if _base_vals.themes_enabled:
|
|
||||||
self.apply_theme()
|
|
||||||
self._make_app(self.kind)
|
|
||||||
|
|
||||||
self.drag_id = ""
|
self.drag_id = ""
|
||||||
self.bind("<Configure>", self.dragging)
|
self.bind("<Configure>", self.dragging)
|
||||||
|
|
||||||
self.iconbitmap(Path(__file__).parent.resolve() / "img" / "cat.ico")
|
|
||||||
|
|
||||||
self.minsize(275, False)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the current interface"""
|
||||||
return self._vban if _base_vals.vban_connected else self._vmr
|
|
||||||
|
|
||||||
@property
|
return self._vban if _base_values.vban_connected else self._vmr
|
||||||
def pdirty(self):
|
|
||||||
return self._pdirty
|
|
||||||
|
|
||||||
@pdirty.setter
|
|
||||||
def pdirty(self, val):
|
|
||||||
self._pdirty = val
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ldirty(self):
|
|
||||||
return self._ldirty
|
|
||||||
|
|
||||||
@ldirty.setter
|
|
||||||
def ldirty(self, val):
|
|
||||||
self._ldirty = val
|
|
||||||
|
|
||||||
@property
|
|
||||||
def configuration(self):
|
|
||||||
return configuration["app"]
|
|
||||||
|
|
||||||
@configuration.setter
|
|
||||||
def configuration(self, val):
|
|
||||||
self.configuration["app"] = val
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def configframes(self):
|
def configframes(self):
|
||||||
"""returns a tuple of current config frame addresses"""
|
"""returns the current configframes"""
|
||||||
return tuple(
|
|
||||||
|
return (
|
||||||
frame
|
frame
|
||||||
for frame in self.winfo_children()
|
for frame in self.winfo_children()
|
||||||
if isinstance(frame, ttk.Frame)
|
if isinstance(frame, ttk.Frame)
|
||||||
@ -141,86 +69,73 @@ class App(tk.Tk):
|
|||||||
or "!busconfig" in str(frame)
|
or "!busconfig" in str(frame)
|
||||||
)
|
)
|
||||||
|
|
||||||
def apply_theme(self):
|
def build_app(self, kind=None, vban=None):
|
||||||
_base_vals.using_theme = True
|
"""builds the app frames according to a kind"""
|
||||||
sv_ttk.set_theme(self.configuration["theme"]["mode"])
|
|
||||||
|
|
||||||
def _make_app(self, kind, vban=None):
|
|
||||||
self.title(
|
|
||||||
f'Voicemeeter{kind.name}.Compact [{"Local" if not vban else "Network"} Connection]'
|
|
||||||
)
|
|
||||||
self._vban = vban
|
self._vban = vban
|
||||||
|
if kind:
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
self.strip_levels = self.target.strip_levels
|
# register as observer
|
||||||
self.bus_levels = self.target.bus_levels
|
self.target.subject.add(self)
|
||||||
|
|
||||||
self._make_top_level_frames()
|
|
||||||
|
|
||||||
def _make_top_level_frames(self):
|
|
||||||
# initialize bus frame variable
|
|
||||||
self.bus_frame = None
|
self.bus_frame = None
|
||||||
# channel_frame, left aligned
|
self.submix_frame = None
|
||||||
self.channel_frame = ChannelFrame.make_strips(self)
|
self.builder = MainFrameBuilder(self)
|
||||||
self.channel_frame.grid(row=0, column=0, sticky=(tk.W))
|
self.builder.setup()
|
||||||
# separator
|
self.builder.create_channelframe("strip")
|
||||||
self.sep = ttk.Separator(self, orient="vertical")
|
self.builder.create_separator()
|
||||||
self.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
self.builder.create_navframe()
|
||||||
self.columnconfigure(1, minsize=15)
|
if _configuration.extended:
|
||||||
|
|
||||||
# navigation frame
|
|
||||||
self.nav_frame = Navigation(self)
|
|
||||||
self.nav_frame.grid(row=0, column=3, sticky=(tk.E))
|
|
||||||
|
|
||||||
if self.configuration["extends"]["extended"]:
|
|
||||||
self.nav_frame.extend.set(True)
|
self.nav_frame.extend.set(True)
|
||||||
self.nav_frame.extend_frame()
|
self.nav_frame.extend_frame()
|
||||||
|
|
||||||
if self.kind.name == "Potato":
|
if self.kind.name == "Potato":
|
||||||
self.banner = Banner(self)
|
self.builder.create_banner()
|
||||||
self.banner.grid(row=4, column=0, columnspan=3)
|
|
||||||
|
def update(self, subject):
|
||||||
|
"""
|
||||||
|
called whenever notified of update
|
||||||
|
|
||||||
|
after 1 to prevent vmr,vban interface waiting.
|
||||||
|
"""
|
||||||
|
if subject == "pdirty" and not _base_values.in_scale_button_1:
|
||||||
|
self.after(1, self.notify_pdirty)
|
||||||
|
elif subject == "ldirty" and not _base_values.dragging:
|
||||||
|
self.after(1, self.notify_ldirty)
|
||||||
|
|
||||||
|
def notify_pdirty(self):
|
||||||
|
self.subject_pdirty.notify()
|
||||||
|
|
||||||
|
def notify_ldirty(self):
|
||||||
|
self.subject_ldirty.notify()
|
||||||
|
|
||||||
def _destroy_top_level_frames(self):
|
def _destroy_top_level_frames(self):
|
||||||
|
"""
|
||||||
|
Clear observables.
|
||||||
|
|
||||||
|
Unregister app as observer.
|
||||||
|
|
||||||
|
Destroy all top level frames.
|
||||||
|
"""
|
||||||
|
self.subject_pdirty.clear()
|
||||||
|
self.subject_ldirty.clear()
|
||||||
|
self.target.subject.remove(self)
|
||||||
[
|
[
|
||||||
frame.destroy()
|
frame.destroy()
|
||||||
for frame in self.winfo_children()
|
for frame in self.winfo_children()
|
||||||
if isinstance(frame, ttk.Frame)
|
if isinstance(frame, ttk.Frame)
|
||||||
]
|
]
|
||||||
|
|
||||||
def upd_pdirty(self):
|
|
||||||
self.after(1, self.upd_pdirty_step)
|
|
||||||
|
|
||||||
def upd_pdirty_step(self):
|
|
||||||
self.pdirty = self.target.pdirty
|
|
||||||
self.after(_base_vals.pdelay, self.upd_pdirty_step)
|
|
||||||
|
|
||||||
def watch_levels(self):
|
|
||||||
self.after(1, self.watch_levels_step)
|
|
||||||
|
|
||||||
def watch_levels_step(self):
|
|
||||||
"""Continuously fetch level arrays, only update if ldirty"""
|
|
||||||
_strip_levels = self.target.strip_levels
|
|
||||||
_bus_levels = self.target.bus_levels
|
|
||||||
self.comp_strip = [not a == b for a, b in zip(self.strip_levels, _strip_levels)]
|
|
||||||
self.comp_bus = [not a == b for a, b in zip(self.bus_levels, _bus_levels)]
|
|
||||||
|
|
||||||
self.ldirty = any(any(l) for l in (self.comp_strip, self.comp_bus))
|
|
||||||
if self.ldirty:
|
|
||||||
self.strip_levels = _strip_levels
|
|
||||||
self.bus_levels = _bus_levels
|
|
||||||
self.after(_base_vals.ldelay, self.watch_levels_step)
|
|
||||||
|
|
||||||
def dragging(self, event, *args):
|
def dragging(self, event, *args):
|
||||||
if event.widget is self:
|
if event.widget is self:
|
||||||
if self.drag_id == "":
|
if self.drag_id == "":
|
||||||
_base_vals.in_scale_button_1 = True
|
_base_values.in_scale_button_1 = True
|
||||||
_base_vals.dragging = True
|
_base_values.dragging = True
|
||||||
else:
|
else:
|
||||||
self.after_cancel(self.drag_id)
|
self.after_cancel(self.drag_id)
|
||||||
self.drag_id = self.after(100, self.stop_drag)
|
self.drag_id = self.after(100, self.stop_drag)
|
||||||
|
|
||||||
def stop_drag(self):
|
def stop_drag(self):
|
||||||
_base_vals.dragging = False
|
_base_values.dragging = False
|
||||||
_base_vals.in_scale_button_1 = False
|
_base_values.in_scale_button_1 = False
|
||||||
self.drag_id = ""
|
self.drag_id = ""
|
||||||
|
|
||||||
|
|
||||||
@ -229,6 +144,7 @@ _apps = {kind.id: App.make(kind) for kind in _kinds_all}
|
|||||||
|
|
||||||
def connect(kind_id: str, vmr) -> App:
|
def connect(kind_id: str, vmr) -> App:
|
||||||
"""return App of the kind requested"""
|
"""return App of the kind requested"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
VMMIN_cls = _apps[kind_id]
|
VMMIN_cls = _apps[kind_id]
|
||||||
return VMMIN_cls(vmr)
|
return VMMIN_cls(vmr)
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from .data import _base_vals
|
from .data import _base_values
|
||||||
|
|
||||||
|
|
||||||
class Banner(ttk.Frame):
|
class Banner(ttk.Frame):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self.submix = tk.StringVar()
|
self.submix = tk.StringVar()
|
||||||
self.submix.set(self.target.bus[_base_vals.submixes].label)
|
self.submix.set(self.target.bus[_base_values.submixes].label)
|
||||||
|
|
||||||
self.label = ttk.Label(
|
self.label = ttk.Label(
|
||||||
self,
|
self,
|
||||||
@ -22,13 +22,13 @@ class Banner(ttk.Frame):
|
|||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""use the correct interface"""
|
"""use the correct interface"""
|
||||||
return self._parent.target
|
return self.parent.target
|
||||||
|
|
||||||
def upd_submix(self):
|
def upd_submix(self):
|
||||||
self.after(1, self.upd_submix_step)
|
self.after(1, self.upd_submix_step)
|
||||||
|
|
||||||
def upd_submix_step(self):
|
def upd_submix_step(self):
|
||||||
if not _base_vals.dragging:
|
if not _base_values.dragging:
|
||||||
self.submix.set(self.target.bus[_base_vals.submixes].label)
|
self.submix.set(self.target.bus[_base_values.submixes].label)
|
||||||
self.label["text"] = f"SUBMIX: {self.submix.get().upper()}"
|
self.label["text"] = f"SUBMIX: {self.submix.get().upper()}"
|
||||||
self.after(100, self.upd_submix_step)
|
self.after(100, self.upd_submix_step)
|
||||||
|
555
vmcompact/builders.py
Normal file
555
vmcompact/builders.py
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
from functools import partial
|
||||||
|
import abc
|
||||||
|
import sv_ttk
|
||||||
|
|
||||||
|
|
||||||
|
from .data import _base_values, _configuration
|
||||||
|
from .channels import _make_channelframe
|
||||||
|
from .navigation import Navigation
|
||||||
|
from .config import StripConfig, BusConfig
|
||||||
|
from .banner import Banner
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractBuilder(abc.ABC):
|
||||||
|
@abc.abstractmethod
|
||||||
|
def setup(self):
|
||||||
|
"""register as observable"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def teardown(self):
|
||||||
|
"""unregister as observable"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MainFrameBuilder(AbstractBuilder):
|
||||||
|
"""Responsible for building the frames that sit directly on the mainframe"""
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
self.kind = app.kind
|
||||||
|
self.app = app
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self.app.title(
|
||||||
|
f'Voicemeeter{self.kind.name}.Compact [{"Local" if not _base_values.vban_connected else "Network"} Connection]'
|
||||||
|
)
|
||||||
|
self.app.resizable(False, False)
|
||||||
|
if _configuration.themes_enabled:
|
||||||
|
sv_ttk.set_theme(_configuration.theme_mode)
|
||||||
|
|
||||||
|
def create_channelframe(self, type_):
|
||||||
|
if type_ == "strip":
|
||||||
|
self.app.strip_frame = _make_channelframe(self.app, type_)
|
||||||
|
else:
|
||||||
|
self.app.bus_frame = _make_channelframe(self.app, type_)
|
||||||
|
|
||||||
|
def create_separator(self):
|
||||||
|
self.app.sep = ttk.Separator(self.app, orient="vertical")
|
||||||
|
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
||||||
|
self.app.columnconfigure(1, minsize=15)
|
||||||
|
|
||||||
|
def create_navframe(self):
|
||||||
|
self.app.nav_frame = Navigation(self.app)
|
||||||
|
|
||||||
|
def create_configframe(self, type_, index, id):
|
||||||
|
if type_ == "strip":
|
||||||
|
self.app.config_frame = StripConfig(self.app, index, id)
|
||||||
|
[
|
||||||
|
frame.conf.set(False)
|
||||||
|
for i, frame in enumerate(self.app.strip_frame.labelframes)
|
||||||
|
if i != index
|
||||||
|
]
|
||||||
|
if self.app.bus_frame:
|
||||||
|
[
|
||||||
|
frame.conf.set(False)
|
||||||
|
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self.app.config_frame = BusConfig(self.app, index, id)
|
||||||
|
[
|
||||||
|
frame.conf.set(False)
|
||||||
|
for i, frame in enumerate(self.app.bus_frame.labelframes)
|
||||||
|
if i != index
|
||||||
|
]
|
||||||
|
if self.app.strip_frame:
|
||||||
|
[
|
||||||
|
frame.conf.set(False)
|
||||||
|
for _, frame in enumerate(self.app.strip_frame.labelframes)
|
||||||
|
]
|
||||||
|
if not _configuration.themes_enabled:
|
||||||
|
if self.app.strip_frame:
|
||||||
|
[
|
||||||
|
frame.styletable.configure(
|
||||||
|
f"{frame.identifier}Conf{frame.index}.TButton",
|
||||||
|
background=f"{'white' if not frame.conf.get() else 'yellow'}",
|
||||||
|
)
|
||||||
|
for _, frame in enumerate(self.app.strip_frame.labelframes)
|
||||||
|
]
|
||||||
|
if self.app.bus_frame:
|
||||||
|
[
|
||||||
|
frame.styletable.configure(
|
||||||
|
f"{frame.identifier}Conf{frame.index}.TButton",
|
||||||
|
background=f"{'white' if not frame.conf.get() else 'yellow'}",
|
||||||
|
)
|
||||||
|
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
||||||
|
]
|
||||||
|
self.app.after(5, self.reset_config_frames)
|
||||||
|
|
||||||
|
def reset_config_frames(self):
|
||||||
|
[
|
||||||
|
frame.teardown()
|
||||||
|
for frame in self.app.configframes
|
||||||
|
if frame != self.app.config_frame
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_banner(self):
|
||||||
|
self.app.banner = Banner(self.app)
|
||||||
|
self.app.banner.grid(row=4, column=0, columnspan=3)
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NavigationFrameBuilder(AbstractBuilder):
|
||||||
|
"""Responsible for building navigation frame widgets"""
|
||||||
|
|
||||||
|
def __init__(self, navframe):
|
||||||
|
self.navframe = navframe
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self.navframe.submix = tk.BooleanVar()
|
||||||
|
self.navframe.channel = tk.BooleanVar()
|
||||||
|
self.navframe.extend = tk.BooleanVar(value=_configuration.extended)
|
||||||
|
self.navframe.info = tk.BooleanVar()
|
||||||
|
|
||||||
|
self.navframe.channel_text = tk.StringVar(
|
||||||
|
value=f"{self.navframe.parent.strip_frame.identifier.upper()}"
|
||||||
|
)
|
||||||
|
self.navframe.extend_text = tk.StringVar(
|
||||||
|
value=f"{'REDUCE' if self.navframe.extend.get() else 'EXTEND'}"
|
||||||
|
)
|
||||||
|
self.navframe.info_text = tk.StringVar()
|
||||||
|
|
||||||
|
def create_submix_button(self):
|
||||||
|
self.navframe.submix_button = ttk.Checkbutton(
|
||||||
|
self.navframe,
|
||||||
|
text="SUBMIX",
|
||||||
|
command=self.navframe.show_submix,
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Submix.TButton'}",
|
||||||
|
variable=self.navframe.submix,
|
||||||
|
)
|
||||||
|
self.navframe.submix_button.grid(column=0, row=0)
|
||||||
|
if self.navframe.parent.kind.name != "Potato":
|
||||||
|
self.navframe.submix_button["state"] = "disabled"
|
||||||
|
|
||||||
|
def create_channel_button(self):
|
||||||
|
self.navframe.channel_button = ttk.Checkbutton(
|
||||||
|
self.navframe,
|
||||||
|
textvariable=self.navframe.channel_text,
|
||||||
|
command=self.navframe.switch_channel,
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Channel.TButton'}",
|
||||||
|
variable=self.navframe.channel,
|
||||||
|
)
|
||||||
|
self.navframe.channel_button.grid(column=0, row=1, rowspan=1)
|
||||||
|
|
||||||
|
def create_extend_button(self):
|
||||||
|
self.navframe.extend_button = ttk.Checkbutton(
|
||||||
|
self.navframe,
|
||||||
|
textvariable=self.navframe.extend_text,
|
||||||
|
command=self.navframe.extend_frame,
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Extend.TButton'}",
|
||||||
|
variable=self.navframe.extend,
|
||||||
|
)
|
||||||
|
self.navframe.extend_button.grid(column=0, row=2)
|
||||||
|
|
||||||
|
def create_info_button(self):
|
||||||
|
self.navframe.info_button = ttk.Checkbutton(
|
||||||
|
self.navframe,
|
||||||
|
textvariable=self.navframe.info_text,
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Rec.TButton'}",
|
||||||
|
variable=self.navframe.info,
|
||||||
|
)
|
||||||
|
self.navframe.info_button.grid(column=0, row=3)
|
||||||
|
|
||||||
|
def grid_configure(self):
|
||||||
|
[
|
||||||
|
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||||
|
for child in self.navframe.winfo_children()
|
||||||
|
if isinstance(child, ttk.Checkbutton)
|
||||||
|
]
|
||||||
|
if _configuration.themes_enabled:
|
||||||
|
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 25)
|
||||||
|
else:
|
||||||
|
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 5)
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||||
|
"""Responsible for building channel labelframe widgets"""
|
||||||
|
|
||||||
|
def __init__(self, labelframe, index, id):
|
||||||
|
self.labelframe = labelframe
|
||||||
|
self.index = index
|
||||||
|
self.identifier = id
|
||||||
|
self.using_theme = False
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""Create class variables for widgets"""
|
||||||
|
self.labelframe.gain = tk.DoubleVar()
|
||||||
|
self.labelframe.level = tk.DoubleVar()
|
||||||
|
self.labelframe.mute = tk.BooleanVar()
|
||||||
|
self.labelframe.conf = tk.BooleanVar()
|
||||||
|
|
||||||
|
"""for gainlayers"""
|
||||||
|
self.labelframe.on = tk.BooleanVar()
|
||||||
|
|
||||||
|
def add_progressbar(self):
|
||||||
|
"""Adds a progress bar widget to a single label frame"""
|
||||||
|
self.labelframe.pb = ttk.Progressbar(
|
||||||
|
self.labelframe,
|
||||||
|
maximum=100,
|
||||||
|
orient="vertical",
|
||||||
|
mode="determinate",
|
||||||
|
variable=self.labelframe.level,
|
||||||
|
)
|
||||||
|
self.labelframe.pb.grid(column=0, row=0)
|
||||||
|
|
||||||
|
def add_scale(self):
|
||||||
|
"""Adds a scale widget to a single label frame"""
|
||||||
|
self.scale = ttk.Scale(
|
||||||
|
self.labelframe,
|
||||||
|
from_=12.0,
|
||||||
|
to=-60.0,
|
||||||
|
orient="vertical",
|
||||||
|
variable=self.labelframe.gain,
|
||||||
|
command=self.labelframe.scale_callback,
|
||||||
|
length=100,
|
||||||
|
)
|
||||||
|
self.scale.grid(column=1, row=0)
|
||||||
|
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
||||||
|
self.scale.bind("<Button-1>", self.labelframe.scale_press)
|
||||||
|
self.scale.bind("<Enter>", self.labelframe.scale_enter)
|
||||||
|
self.scale.bind("<ButtonRelease-1>", self.labelframe.scale_release)
|
||||||
|
self.scale.bind("<Leave>", self.labelframe.scale_leave)
|
||||||
|
self.scale.bind("<MouseWheel>", self.labelframe._on_mousewheel)
|
||||||
|
|
||||||
|
def add_mute_button(self):
|
||||||
|
"""Adds a mute button widget to a single label frame"""
|
||||||
|
self.button_mute = ttk.Checkbutton(
|
||||||
|
self.labelframe,
|
||||||
|
text="MUTE",
|
||||||
|
command=partial(self.labelframe.toggle_mute, "mute"),
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Mute{self.index}.TButton'}",
|
||||||
|
variable=self.labelframe.mute,
|
||||||
|
)
|
||||||
|
self.button_mute.grid(column=0, row=1, columnspan=2)
|
||||||
|
|
||||||
|
def add_conf_button(self):
|
||||||
|
self.button_conf = ttk.Checkbutton(
|
||||||
|
self.labelframe,
|
||||||
|
text="CONFIG",
|
||||||
|
command=self.labelframe.open_config,
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Conf{self.index}.TButton'}",
|
||||||
|
variable=self.labelframe.conf,
|
||||||
|
)
|
||||||
|
self.button_conf.grid(column=0, row=2, columnspan=2)
|
||||||
|
|
||||||
|
def add_on_button(self):
|
||||||
|
self.button_on = ttk.Checkbutton(
|
||||||
|
self.labelframe,
|
||||||
|
text="ON",
|
||||||
|
command=self.labelframe.set_on,
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else 'On.TButton'}",
|
||||||
|
variable=self.labelframe.on,
|
||||||
|
)
|
||||||
|
self.button_on.grid(column=0, row=1, columnspan=2)
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
self.labelframe.grid_remove()
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelConfigFrameBuilder(AbstractBuilder):
|
||||||
|
"""Responsible for building channel configframe widgets"""
|
||||||
|
|
||||||
|
def __init__(self, configframe):
|
||||||
|
self.configframe = configframe
|
||||||
|
(
|
||||||
|
self.configframe.phys_in,
|
||||||
|
self.configframe.virt_in,
|
||||||
|
) = self.configframe.parent.kind.ins
|
||||||
|
(
|
||||||
|
self.configframe.phys_out,
|
||||||
|
self.configframe.virt_out,
|
||||||
|
) = self.configframe.parent.kind.outs
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"register configframe as observable"
|
||||||
|
pass
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
"""Unregister as observable, then destroy frame"""
|
||||||
|
self.configframe.parent.subject_pdirty.remove(self.configframe)
|
||||||
|
self.configframe.destroy()
|
||||||
|
|
||||||
|
def grid_configure(self):
|
||||||
|
[
|
||||||
|
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||||
|
for child in self.configframe.winfo_children()
|
||||||
|
if isinstance(child, ttk.Checkbutton)
|
||||||
|
]
|
||||||
|
self.configframe.grid(sticky=(tk.W))
|
||||||
|
[
|
||||||
|
self.configframe.columnconfigure(i, minsize=_configuration.level_width)
|
||||||
|
for i in range(self.configframe.phys_out + self.configframe.virt_out)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||||
|
"""Responsible for building channel configframe widgets"""
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
if self.configframe.parent.kind.ins == "Basic":
|
||||||
|
self.configframe.slider_params = ("audibility",)
|
||||||
|
self.configframe.slider_vars = (tk.DoubleVar(),)
|
||||||
|
else:
|
||||||
|
self.configframe.slider_params = ("comp", "gate", "limit")
|
||||||
|
self.configframe.slider_vars = [
|
||||||
|
tk.DoubleVar() for _ in self.configframe.slider_params
|
||||||
|
]
|
||||||
|
|
||||||
|
self.configframe.phys_out_params = [
|
||||||
|
f"A{i+1}" for i in range(self.configframe.phys_out)
|
||||||
|
]
|
||||||
|
self.configframe.phys_out_params_vars = [
|
||||||
|
tk.BooleanVar() for _ in self.configframe.phys_out_params
|
||||||
|
]
|
||||||
|
|
||||||
|
self.configframe.virt_out_params = [
|
||||||
|
f"B{i+1}" for i in range(self.configframe.virt_out)
|
||||||
|
]
|
||||||
|
self.configframe.virt_out_params_vars = [
|
||||||
|
tk.BooleanVar() for _ in self.configframe.virt_out_params
|
||||||
|
]
|
||||||
|
|
||||||
|
self.configframe.params = ("mono", "solo")
|
||||||
|
self.configframe.param_vars = list(
|
||||||
|
tk.BooleanVar() for _ in self.configframe.params
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_comp_slider(self):
|
||||||
|
comp_label = ttk.Label(self.configframe, text="Comp")
|
||||||
|
comp_scale = ttk.Scale(
|
||||||
|
self.configframe,
|
||||||
|
from_=0.0,
|
||||||
|
to=10.0,
|
||||||
|
orient="horizontal",
|
||||||
|
length=_configuration.level_width,
|
||||||
|
variable=self.configframe.slider_vars[
|
||||||
|
self.configframe.slider_params.index("comp")
|
||||||
|
],
|
||||||
|
command=partial(self.configframe.scale_callback, "comp"),
|
||||||
|
)
|
||||||
|
comp_scale.bind(
|
||||||
|
"<Double-Button-1>", partial(self.configframe.reset_scale, "comp", 0)
|
||||||
|
)
|
||||||
|
comp_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||||
|
comp_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||||
|
|
||||||
|
comp_label.grid(column=0, row=0)
|
||||||
|
comp_scale.grid(column=1, row=0)
|
||||||
|
|
||||||
|
def create_gate_slider(self):
|
||||||
|
gate_label = ttk.Label(self.configframe, text="Gate")
|
||||||
|
gate_scale = ttk.Scale(
|
||||||
|
self.configframe,
|
||||||
|
from_=0.0,
|
||||||
|
to=10.0,
|
||||||
|
orient="horizontal",
|
||||||
|
length=_configuration.level_width,
|
||||||
|
variable=self.configframe.slider_vars[
|
||||||
|
self.configframe.slider_params.index("gate")
|
||||||
|
],
|
||||||
|
command=partial(self.configframe.scale_callback, "gate"),
|
||||||
|
)
|
||||||
|
gate_scale.bind(
|
||||||
|
"<Double-Button-1>", partial(self.configframe.reset_scale, "gate", 0)
|
||||||
|
)
|
||||||
|
gate_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||||
|
gate_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||||
|
|
||||||
|
gate_label.grid(column=2, row=0)
|
||||||
|
gate_scale.grid(column=3, row=0)
|
||||||
|
|
||||||
|
def create_limit_slider(self):
|
||||||
|
limit_label = ttk.Label(self.configframe, text="Limit")
|
||||||
|
limit_scale = ttk.Scale(
|
||||||
|
self.configframe,
|
||||||
|
from_=-40,
|
||||||
|
to=12,
|
||||||
|
orient="horizontal",
|
||||||
|
length=_configuration.level_width,
|
||||||
|
variable=self.configframe.slider_vars[
|
||||||
|
self.configframe.slider_params.index("limit")
|
||||||
|
],
|
||||||
|
command=partial(self.configframe.scale_callback, "limit"),
|
||||||
|
)
|
||||||
|
limit_scale.bind(
|
||||||
|
"<Double-Button-1>", partial(self.configframe.reset_scale, "limit", 12)
|
||||||
|
)
|
||||||
|
limit_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||||
|
limit_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||||
|
|
||||||
|
limit_label.grid(column=4, row=0)
|
||||||
|
limit_scale.grid(column=5, row=0)
|
||||||
|
|
||||||
|
def create_audibility_slider(self):
|
||||||
|
aud_label = ttk.Label(self.configframe, text="Audibility")
|
||||||
|
aud_scale = ttk.Scale(
|
||||||
|
self,
|
||||||
|
from_=0.0,
|
||||||
|
to=10.0,
|
||||||
|
orient="horizontal",
|
||||||
|
length=_base_values.level_width,
|
||||||
|
variable=self.configframe.slider_vars[
|
||||||
|
self.configframe.slider_params.index("audibility")
|
||||||
|
],
|
||||||
|
command=partial(self.configframe.scale_callback, "audibility"),
|
||||||
|
)
|
||||||
|
aud_scale.bind(
|
||||||
|
"<Double-Button-1>", partial(self.configframe.reset_scale, "audibility", 0)
|
||||||
|
)
|
||||||
|
aud_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||||
|
aud_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||||
|
|
||||||
|
aud_label.grid(column=0, row=0)
|
||||||
|
aud_scale.grid(column=1, row=0)
|
||||||
|
|
||||||
|
def create_a_buttons(self):
|
||||||
|
self.configframe.a_buttons = [
|
||||||
|
ttk.Checkbutton(
|
||||||
|
self.configframe,
|
||||||
|
text=param,
|
||||||
|
command=partial(self.configframe.toggle_a, param),
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||||
|
variable=self.configframe.phys_out_params_vars[
|
||||||
|
self.configframe.phys_out_params.index(param)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for param in self.configframe.phys_out_params
|
||||||
|
]
|
||||||
|
[
|
||||||
|
button.grid(
|
||||||
|
column=i,
|
||||||
|
row=1,
|
||||||
|
)
|
||||||
|
for i, button in enumerate(self.configframe.a_buttons)
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_b_buttons(self):
|
||||||
|
self.configframe.b_buttons = [
|
||||||
|
ttk.Checkbutton(
|
||||||
|
self.configframe,
|
||||||
|
text=param,
|
||||||
|
command=partial(self.configframe.toggle_b, param),
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||||
|
variable=self.configframe.virt_out_params_vars[
|
||||||
|
self.configframe.virt_out_params.index(param)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for param in self.configframe.virt_out_params
|
||||||
|
]
|
||||||
|
[
|
||||||
|
button.grid(
|
||||||
|
column=len(self.configframe.a_buttons) + i,
|
||||||
|
row=1,
|
||||||
|
)
|
||||||
|
for i, button in enumerate(self.configframe.b_buttons)
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_param_buttons(self):
|
||||||
|
param_buttons = [
|
||||||
|
ttk.Checkbutton(
|
||||||
|
self.configframe,
|
||||||
|
text=param,
|
||||||
|
command=partial(self.configframe.toggle_p, param),
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||||
|
variable=self.configframe.param_vars[
|
||||||
|
self.configframe.params.index(param)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for param in self.configframe.params
|
||||||
|
]
|
||||||
|
[
|
||||||
|
button.grid(
|
||||||
|
column=i,
|
||||||
|
row=2,
|
||||||
|
)
|
||||||
|
for i, button in enumerate(param_buttons)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||||
|
"""Responsible for building channel configframe widgets"""
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
# fmt: off
|
||||||
|
self.configframe.bus_mode_map = {
|
||||||
|
"normal": "Normal",
|
||||||
|
"amix": "Mix Down A",
|
||||||
|
"bmix": "Mix Down B",
|
||||||
|
"repeat": "Stereo Repeat",
|
||||||
|
"composite": "Composite",
|
||||||
|
"tvmix": "Up Mix TV",
|
||||||
|
"upmix21": "Up Mix 2.1",
|
||||||
|
"upmix41": "Up Mix 4.1",
|
||||||
|
"upmix61": "Up Mix 6.1",
|
||||||
|
"centeronly": "Center Only",
|
||||||
|
"lfeonly": "LFE Only",
|
||||||
|
"rearonly": "Rear Only",
|
||||||
|
}
|
||||||
|
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
||||||
|
# fmt: on
|
||||||
|
self.configframe.params = ("mono", "eq", "eq_ab")
|
||||||
|
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params]
|
||||||
|
self.configframe.bus_mode_label_text = tk.StringVar(
|
||||||
|
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_bus_mode_button(self):
|
||||||
|
self.configframe.busmode_button = ttk.Button(
|
||||||
|
self.configframe, textvariable=self.configframe.bus_mode_label_text
|
||||||
|
)
|
||||||
|
self.configframe.busmode_button.grid(
|
||||||
|
column=0, row=0, columnspan=2, sticky=(tk.W)
|
||||||
|
)
|
||||||
|
self.configframe.busmode_button.bind(
|
||||||
|
"<Button-1>", self.configframe.rotate_bus_modes_right
|
||||||
|
)
|
||||||
|
self.configframe.busmode_button.bind(
|
||||||
|
"<Button-3>", self.configframe.rotate_bus_modes_left
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_param_buttons(self):
|
||||||
|
param_buttons = [
|
||||||
|
ttk.Checkbutton(
|
||||||
|
self.configframe,
|
||||||
|
text=param,
|
||||||
|
command=partial(self.configframe.toggle_p, param),
|
||||||
|
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||||
|
variable=self.configframe.param_vars[
|
||||||
|
self.configframe.params.index(param)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
for param in self.configframe.params
|
||||||
|
]
|
||||||
|
[
|
||||||
|
button.grid(
|
||||||
|
column=i,
|
||||||
|
row=1,
|
||||||
|
)
|
||||||
|
for i, button in enumerate(param_buttons)
|
||||||
|
]
|
@ -1,34 +1,31 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from functools import partial
|
|
||||||
from math import log
|
from math import log
|
||||||
|
|
||||||
from .data import _base_vals
|
from . import builders
|
||||||
from .config import StripConfig, BusConfig
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
|
||||||
class Channel(ttk.LabelFrame):
|
class ChannelLabelFrame(ttk.LabelFrame):
|
||||||
"""Base class for a single channel"""
|
"""Base class for a single channel"""
|
||||||
|
|
||||||
def __init__(self, parent, index, id):
|
def __init__(self, parent, index, id):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.id = id
|
self.id = id
|
||||||
self.s = self._parent._parent.styletable
|
self.styletable = self.parent.parent.styletable
|
||||||
self.config_frame = None
|
|
||||||
|
|
||||||
self.gain = tk.DoubleVar()
|
|
||||||
self.level = tk.DoubleVar()
|
|
||||||
self.mute = tk.BooleanVar()
|
|
||||||
self.conf = tk.BooleanVar()
|
|
||||||
|
|
||||||
|
self.builder = builders.ChannelLabelFrameBuilder(self, index, id)
|
||||||
|
self.builder.setup()
|
||||||
|
self.builder.add_progressbar()
|
||||||
|
self.builder.add_scale()
|
||||||
|
self.builder.add_mute_button()
|
||||||
|
self.builder.add_conf_button()
|
||||||
self.sync()
|
self.sync()
|
||||||
self._make_widgets()
|
self.grid_configure()
|
||||||
|
|
||||||
self.col_row_configure()
|
self.configbuilder = builders.MainFrameBuilder(self.parent.parent)
|
||||||
self.watch_pdirty()
|
|
||||||
self.watch_levels()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
@ -36,8 +33,9 @@ class Channel(ttk.LabelFrame):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""use the correct interface"""
|
"""returns the current interface"""
|
||||||
return self._parent.target
|
|
||||||
|
return self.parent.target
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if param in dir(self.target):
|
if param in dir(self.target):
|
||||||
@ -47,10 +45,16 @@ class Channel(ttk.LabelFrame):
|
|||||||
if param in dir(self.target):
|
if param in dir(self.target):
|
||||||
setattr(self.target, param, value)
|
setattr(self.target, param, value)
|
||||||
|
|
||||||
|
def scale_callback(self, *args):
|
||||||
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
|
self.setter("gain", self.gain.get())
|
||||||
|
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
def toggle_mute(self, *args):
|
def toggle_mute(self, *args):
|
||||||
self.target.mute = self.mute.get()
|
self.target.mute = self.mute.get()
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}Mute{self.index}.TButton",
|
f"{self.identifier}Mute{self.index}.TButton",
|
||||||
background=f'{"red" if self.mute.get() else "white"}',
|
background=f'{"red" if self.mute.get() else "white"}',
|
||||||
)
|
)
|
||||||
@ -58,27 +62,27 @@ class Channel(ttk.LabelFrame):
|
|||||||
def reset_gain(self, *args):
|
def reset_gain(self, *args):
|
||||||
self.setter("gain", 0)
|
self.setter("gain", 0)
|
||||||
self.gain.set(0)
|
self.gain.set(0)
|
||||||
self._parent._parent.nav_frame.info_text.set(0)
|
self.parent.parent.nav_frame.info_text.set(0)
|
||||||
|
|
||||||
def scale_enter(self, *args):
|
def scale_enter(self, *args):
|
||||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
def scale_leave(self, *args):
|
def scale_leave(self, *args):
|
||||||
self._parent._parent.nav_frame.info_text.set("")
|
self.parent.parent.nav_frame.info_text.set("")
|
||||||
|
|
||||||
def scale_press(self, *args):
|
def scale_press(self, *args):
|
||||||
_base_vals.in_scale_button_1 = True
|
_base_values.in_scale_button_1 = True
|
||||||
|
|
||||||
def scale_release(self, *args):
|
def scale_release(self, *args):
|
||||||
_base_vals.in_scale_button_1 = False
|
_base_values.in_scale_button_1 = False
|
||||||
|
|
||||||
def _on_mousewheel(self, event):
|
def _on_mousewheel(self, event):
|
||||||
self.gain.set(
|
self.gain.set(
|
||||||
self.gain.get()
|
self.gain.get()
|
||||||
+ (
|
+ (
|
||||||
_base_vals.mwscroll_step
|
_configuration.mwscroll_step
|
||||||
if event.delta > 0
|
if event.delta > 0
|
||||||
else -_base_vals.mwscroll_step
|
else -_configuration.mwscroll_step
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.gain.get() > 12:
|
if self.gain.get() > 12:
|
||||||
@ -86,94 +90,46 @@ class Channel(ttk.LabelFrame):
|
|||||||
elif self.gain.get() < -60:
|
elif self.gain.get() < -60:
|
||||||
self.gain.set(-60)
|
self.gain.set(-60)
|
||||||
self.setter("gain", self.gain.get())
|
self.setter("gain", self.gain.get())
|
||||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
def scale_callback(self, *args):
|
def open_config(self):
|
||||||
"""callback function for scale widget"""
|
if self.conf.get():
|
||||||
self.setter("gain", self.gain.get())
|
self.configbuilder.create_configframe(self.identifier, self.index, self.id)
|
||||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
else:
|
||||||
|
self.parent.parent.config_frame.teardown()
|
||||||
def convert_level(self, val):
|
if not _configuration.themes_enabled:
|
||||||
if _base_vals.vban_connected:
|
self.styletable.configure(
|
||||||
return round(-val * 0.01, 1)
|
f"{self.identifier}Conf{self.index}.TButton",
|
||||||
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||||
|
|
||||||
def _make_widgets(self):
|
|
||||||
"""Creates a progressbar, scale, mute button and config button for a single channel"""
|
|
||||||
# Progress bar
|
|
||||||
self.pb = ttk.Progressbar(
|
|
||||||
self,
|
|
||||||
maximum=100,
|
|
||||||
orient="vertical",
|
|
||||||
mode="determinate",
|
|
||||||
variable=self.level,
|
|
||||||
)
|
)
|
||||||
self.pb.grid(column=0, row=0)
|
|
||||||
|
|
||||||
# Scale
|
|
||||||
self.scale = ttk.Scale(
|
|
||||||
self,
|
|
||||||
from_=12.0,
|
|
||||||
to=-60.0,
|
|
||||||
orient="vertical",
|
|
||||||
variable=self.gain,
|
|
||||||
command=self.scale_callback,
|
|
||||||
length=self._parent.height,
|
|
||||||
)
|
|
||||||
self.scale.grid(column=1, row=0)
|
|
||||||
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
|
||||||
self.scale.bind("<Button-1>", self.scale_press)
|
|
||||||
self.scale.bind("<Enter>", self.scale_enter)
|
|
||||||
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
|
||||||
self.scale.bind("<Leave>", self.scale_leave)
|
|
||||||
self.scale.bind("<MouseWheel>", self._on_mousewheel)
|
|
||||||
|
|
||||||
# Mute button
|
|
||||||
self.button_mute = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text="MUTE",
|
|
||||||
command=partial(self.toggle_mute, "mute"),
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{self.identifier}Mute{self.index}.TButton'}",
|
|
||||||
variable=self.mute,
|
|
||||||
)
|
|
||||||
self.button_mute.grid(column=0, row=1, columnspan=2)
|
|
||||||
|
|
||||||
self.button_conf = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text="CONFIG",
|
|
||||||
command=self.open_config,
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{self.identifier}Conf{self.index}.TButton'}",
|
|
||||||
variable=self.conf,
|
|
||||||
)
|
|
||||||
self.button_conf.grid(column=0, row=2, columnspan=2)
|
|
||||||
|
|
||||||
def watch_pdirty(self):
|
|
||||||
self.after(1, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
def watch_pdirty_step(self):
|
|
||||||
"""keeps params synced but ensures sliders are responsive"""
|
|
||||||
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
|
||||||
self.sync()
|
|
||||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""sync params with voicemeeter"""
|
"""sync parameters"""
|
||||||
retval = self.getter("label")
|
retval = self.getter("label")
|
||||||
if len(retval) > 10:
|
if len(retval) > 10:
|
||||||
retval = f"{retval[:8]}.."
|
retval = f"{retval[:8]}.."
|
||||||
|
if not retval:
|
||||||
|
self.parent.columnconfigure(self.index, minsize=0)
|
||||||
|
self.parent.parent.subject_ldirty.remove(self)
|
||||||
|
self.grid_remove()
|
||||||
|
else:
|
||||||
|
self.parent.parent.subject_ldirty.add(self)
|
||||||
|
self.grid()
|
||||||
self.configure(text=retval)
|
self.configure(text=retval)
|
||||||
self.gain.set(self.getter("gain"))
|
self.gain.set(self.getter("gain"))
|
||||||
self.mute.set(self.getter("mute"))
|
self.mute.set(self.getter("mute"))
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}Mute{self.index}.TButton",
|
f"{self.identifier}Mute{self.index}.TButton",
|
||||||
background=f'{"red" if self.mute.get() else "white"}',
|
background=f'{"red" if self.mute.get() else "white"}',
|
||||||
)
|
)
|
||||||
self.s.configure(
|
|
||||||
f"{self.identifier}Conf{self.index}.TButton", background="white"
|
|
||||||
)
|
|
||||||
|
|
||||||
def col_row_configure(self):
|
def convert_level(self, val):
|
||||||
|
if _base_values.vban_connected:
|
||||||
|
return round(-val * 0.01, 1)
|
||||||
|
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
||||||
|
|
||||||
|
def grid_configure(self):
|
||||||
self.grid(sticky=(tk.N, tk.S))
|
self.grid(sticky=(tk.N, tk.S))
|
||||||
[
|
[
|
||||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||||
@ -187,8 +143,8 @@ class Channel(ttk.LabelFrame):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Strip(Channel):
|
class Strip(ChannelLabelFrame):
|
||||||
"""Concrete class representing a single"""
|
"""Concrete class representing a single strip"""
|
||||||
|
|
||||||
def __init__(self, parent, index, id):
|
def __init__(self, parent, index, id):
|
||||||
super().__init__(parent, index, id)
|
super().__init__(parent, index, id)
|
||||||
@ -199,251 +155,153 @@ class Strip(Channel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""use the correct interface"""
|
"""returns the strip class for this labelframe in the current interface"""
|
||||||
|
|
||||||
_target = super(Strip, self).target
|
_target = super(Strip, self).target
|
||||||
return getattr(_target, self.identifier)[self.index]
|
return getattr(_target, self.identifier)[self.index]
|
||||||
|
|
||||||
def open_config(self):
|
def update(self):
|
||||||
if self.conf.get():
|
"""update levels"""
|
||||||
self.config_frame = StripConfig(
|
|
||||||
self._parent._parent,
|
|
||||||
self.index,
|
|
||||||
self.identifier,
|
|
||||||
)
|
|
||||||
self.config_frame.grid(column=0, row=1, columnspan=4)
|
|
||||||
self._parent._parent.channel_frame.reset_config_buttons(self)
|
|
||||||
if self._parent._parent.bus_frame is not None:
|
|
||||||
self._parent._parent.bus_frame.reset_config_buttons(self)
|
|
||||||
else:
|
|
||||||
self.config_frame.destroy()
|
|
||||||
|
|
||||||
if not _base_vals.using_theme:
|
|
||||||
self.s.configure(
|
|
||||||
f"{self.identifier}Conf{self.index}.TButton",
|
|
||||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
|
||||||
)
|
|
||||||
|
|
||||||
def watch_levels(self):
|
|
||||||
self.after(1, self.watch_levels_step)
|
|
||||||
|
|
||||||
def watch_levels_step(self):
|
|
||||||
if not _base_vals.dragging:
|
|
||||||
if (
|
|
||||||
self._parent._parent.ldirty
|
|
||||||
and any(
|
|
||||||
self._parent._parent.comp_strip[
|
|
||||||
self.level_offset : self.level_offset + 1
|
|
||||||
]
|
|
||||||
)
|
|
||||||
and _base_vals.strip_level_array_size
|
|
||||||
== len(self._parent._parent.comp_strip)
|
|
||||||
):
|
|
||||||
vals = (
|
vals = (
|
||||||
self.convert_level(
|
self.convert_level(self.parent.target.strip_levels[self.level_offset]),
|
||||||
self._parent._parent.strip_levels[self.level_offset]
|
self.convert_level(self.parent.target.strip_levels[self.level_offset + 1]),
|
||||||
),
|
|
||||||
self.convert_level(
|
|
||||||
self._parent._parent.strip_levels[self.level_offset + 1]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.level.set(
|
self.level.set(
|
||||||
(0 if self.mute.get() else 100 + (max(vals) - 18) + self.gain.get())
|
(0 if self.mute.get() else 100 + (max(vals) - 18) + self.gain.get())
|
||||||
)
|
)
|
||||||
self.after(
|
|
||||||
_base_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
|
||||||
self.watch_levels_step,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Bus(Channel):
|
class Bus(ChannelLabelFrame):
|
||||||
"""Concrete bus class representing a single bus"""
|
"""Concrete bus class representing a single bus"""
|
||||||
|
|
||||||
def __init__(self, parent, index, id):
|
def __init__(self, parent, index, id):
|
||||||
super().__init__(parent, index, id)
|
super().__init__(parent, index, id)
|
||||||
self.level_offset = self.index * 8
|
self.level_offset = index * 8
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""use the correct interface"""
|
"""returns the bus class for this labelframe in the current interface"""
|
||||||
|
|
||||||
_target = super(Bus, self).target
|
_target = super(Bus, self).target
|
||||||
return getattr(_target, self.identifier)[self.index]
|
return getattr(_target, self.identifier)[self.index]
|
||||||
|
|
||||||
def open_config(self):
|
def update(self):
|
||||||
if self.conf.get():
|
"""update levels"""
|
||||||
self.config_frame = BusConfig(
|
|
||||||
self._parent._parent,
|
|
||||||
self.index,
|
|
||||||
self.identifier,
|
|
||||||
)
|
|
||||||
if _base_vals.extends_horizontal:
|
|
||||||
self.config_frame.grid(column=0, row=1, columnspan=4)
|
|
||||||
else:
|
|
||||||
self.config_frame.grid(column=0, row=3, columnspan=4)
|
|
||||||
self._parent._parent.channel_frame.reset_config_buttons(self)
|
|
||||||
self._parent._parent.bus_frame.update_bus_modes()
|
|
||||||
self._parent._parent.bus_frame.reset_config_buttons(self)
|
|
||||||
else:
|
|
||||||
self._parent._parent.bus_modes_cache[
|
|
||||||
"vban" if _base_vals.vban_connected else "vmr"
|
|
||||||
][self.index].set(self.config_frame.bus_mode)
|
|
||||||
self.config_frame.destroy()
|
|
||||||
|
|
||||||
if not _base_vals.using_theme:
|
|
||||||
self.s.configure(
|
|
||||||
f"{self.identifier}Conf{self.index}.TButton",
|
|
||||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
|
||||||
)
|
|
||||||
|
|
||||||
def watch_levels(self):
|
|
||||||
self.after(1, self.watch_levels_step)
|
|
||||||
|
|
||||||
def watch_levels_step(self):
|
|
||||||
if not _base_vals.dragging:
|
|
||||||
if (
|
|
||||||
self._parent._parent.ldirty
|
|
||||||
and any(
|
|
||||||
self._parent._parent.comp_bus[
|
|
||||||
self.level_offset : self.level_offset + 1
|
|
||||||
]
|
|
||||||
)
|
|
||||||
and _base_vals.bus_level_array_size
|
|
||||||
== len(self._parent._parent.comp_bus)
|
|
||||||
):
|
|
||||||
vals = (
|
vals = (
|
||||||
self.convert_level(
|
self.convert_level(self.parent.target.bus_levels[self.level_offset]),
|
||||||
self._parent._parent.bus_levels[self.level_offset]
|
self.convert_level(self.parent.target.bus_levels[self.level_offset + 1]),
|
||||||
),
|
|
||||||
self.convert_level(
|
|
||||||
self._parent._parent.bus_levels[self.level_offset + 1]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.level.set((0 if self.mute.get() else 100 + (max(vals) - 18)))
|
self.level.set((0 if self.mute.get() else 100 + (max(vals) - 18)))
|
||||||
self.after(
|
|
||||||
_base_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
|
||||||
self.watch_levels_step,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ChannelFrame(ttk.Frame):
|
class ChannelFrame(ttk.Frame):
|
||||||
@classmethod
|
def init(self, parent, id):
|
||||||
def make_strips(cls, parent):
|
|
||||||
return cls(parent, is_strip=True)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def make_buses(cls, parent):
|
|
||||||
return cls(parent, is_strip=False)
|
|
||||||
|
|
||||||
def __init__(self, parent, is_strip: bool = True):
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self._is_strip = is_strip
|
self.id = id
|
||||||
self.phys_in, self.virt_in = parent.kind.ins
|
self.phys_in, self.virt_in = parent.kind.ins
|
||||||
self.phys_out, self.virt_out = parent.kind.outs
|
self.phys_out, self.virt_out = parent.kind.outs
|
||||||
_base_vals.strip_level_array_size = 2 * self.phys_in + 8 * self.virt_in
|
|
||||||
_base_vals.bus_level_array_size = 8 * (self.phys_out + self.virt_out)
|
|
||||||
|
|
||||||
defaults = {
|
# registers channelframe as pdirty observer
|
||||||
"width": 80,
|
self.parent.subject_pdirty.add(self)
|
||||||
"height": 150,
|
|
||||||
}
|
|
||||||
self.configuration = defaults | self.configuration
|
|
||||||
self.width = self.configuration["width"]
|
|
||||||
self.height = self.configuration["height"]
|
|
||||||
|
|
||||||
# create labelframes
|
|
||||||
if is_strip:
|
|
||||||
self.strips = tuple(
|
|
||||||
Strip(self, i, self.identifier)
|
|
||||||
for i in range(self.phys_in + self.virt_in)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.buses = tuple(
|
|
||||||
Bus(self, i, self.identifier)
|
|
||||||
for i in range(self.phys_out + self.virt_out)
|
|
||||||
)
|
|
||||||
|
|
||||||
# position label frames. destroy any without label text
|
|
||||||
self.labelframes = self.strips if is_strip else self.buses
|
|
||||||
|
|
||||||
self.col_row_configure()
|
|
||||||
|
|
||||||
for i, labelframe in enumerate(self.labelframes):
|
|
||||||
labelframe.grid(row=0, column=i)
|
|
||||||
if not labelframe.cget("text"):
|
|
||||||
self.columnconfigure(i, minsize=0)
|
|
||||||
labelframe.grid_remove()
|
|
||||||
|
|
||||||
self.watch_pdirty()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the current interface"""
|
||||||
return self._parent.target
|
|
||||||
|
|
||||||
@property
|
return self.parent.target
|
||||||
def configuration(self):
|
|
||||||
return self._parent.configuration["channel"]
|
|
||||||
|
|
||||||
@configuration.setter
|
|
||||||
def configuration(self, val):
|
|
||||||
self._parent.configuration["channel"] = val
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
return "strip" if self._is_strip else "bus"
|
return self.id
|
||||||
|
|
||||||
def update_bus_modes(self):
|
@property
|
||||||
[
|
def labelframes(self):
|
||||||
self._parent.bus_modes_cache[
|
"""returns a tuple of current channel labelframe addresses"""
|
||||||
"vban" if _base_vals.vban_connected else "vmr"
|
|
||||||
][i].set(labelframe.config_frame.bus_mode)
|
|
||||||
for i, labelframe in enumerate(self.labelframes)
|
|
||||||
if labelframe is not None and labelframe.config_frame
|
|
||||||
]
|
|
||||||
|
|
||||||
def reset_config_buttons(self, current):
|
return tuple(
|
||||||
if not _base_vals.using_theme:
|
frame
|
||||||
[
|
for frame in self.winfo_children()
|
||||||
labelframe.s.configure(
|
if isinstance(frame, ttk.LabelFrame)
|
||||||
f"{labelframe.identifier}Conf{labelframe.index}.TButton",
|
|
||||||
background="white",
|
|
||||||
)
|
)
|
||||||
for labelframe in self.labelframes
|
|
||||||
if labelframe is not None
|
|
||||||
]
|
|
||||||
[
|
|
||||||
labelframe.conf.set(False)
|
|
||||||
for labelframe in self.labelframes
|
|
||||||
if labelframe is not None and labelframe != current
|
|
||||||
]
|
|
||||||
[
|
|
||||||
labelframe.config_frame.destroy()
|
|
||||||
for labelframe in self.labelframes
|
|
||||||
if labelframe is not None
|
|
||||||
and labelframe.config_frame
|
|
||||||
and labelframe != current
|
|
||||||
]
|
|
||||||
|
|
||||||
def col_row_configure(self):
|
def grid_configure(self):
|
||||||
[
|
[
|
||||||
self.columnconfigure(i, minsize=self.width)
|
self.columnconfigure(i, minsize=_configuration.level_width)
|
||||||
|
for i, _ in enumerate(self.labelframes)
|
||||||
|
]
|
||||||
|
[
|
||||||
|
self.rowconfigure(0, minsize=_configuration.level_height)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
[self.rowconfigure(0, minsize=130) for i, _ in enumerate(self.labelframes)]
|
|
||||||
|
|
||||||
def watch_pdirty(self):
|
def update(self):
|
||||||
self.after(1, self.watch_pdirty_step)
|
for labelframe in self.labelframes:
|
||||||
|
labelframe.sync()
|
||||||
|
|
||||||
def watch_pdirty_step(self):
|
def teardown(self):
|
||||||
if self._parent.pdirty:
|
# deregisters channelframe as pdirty observer
|
||||||
self.watch_labels()
|
|
||||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
def watch_labels(self):
|
self.parent.subject_pdirty.remove(self)
|
||||||
for i, labelframe in enumerate(self.labelframes):
|
self.destroy()
|
||||||
if not labelframe.getter("label"):
|
|
||||||
|
|
||||||
|
def _make_channelframe(parent, id):
|
||||||
|
"""
|
||||||
|
Creates a Channel Frame class of type strip or bus
|
||||||
|
"""
|
||||||
|
|
||||||
|
phys_in, virt_in = parent.kind.ins
|
||||||
|
phys_out, virt_out = parent.kind.outs
|
||||||
|
|
||||||
|
def init_labels(self, id):
|
||||||
|
"""
|
||||||
|
Grids each labelframe, grid_removes any without a label
|
||||||
|
"""
|
||||||
|
|
||||||
|
for i, labelframe in enumerate(
|
||||||
|
getattr(self, "strips" if id == "strip" else "buses")
|
||||||
|
):
|
||||||
|
labelframe.grid(row=0, column=i)
|
||||||
|
if not labelframe.target.label:
|
||||||
self.columnconfigure(i, minsize=0)
|
self.columnconfigure(i, minsize=0)
|
||||||
labelframe.grid_remove()
|
labelframe.grid_remove()
|
||||||
|
|
||||||
|
def init_strip(self, *args, **kwargs):
|
||||||
|
self.init(parent, id)
|
||||||
|
self.strips = tuple(Strip(self, i, id) for i in range(phys_in + virt_in))
|
||||||
|
self.grid(row=0, column=0, sticky=(tk.W))
|
||||||
|
self.grid_configure()
|
||||||
|
init_labels(self, id)
|
||||||
|
|
||||||
|
def init_bus(self, *args, **kwargs):
|
||||||
|
self.init(parent, id)
|
||||||
|
self.buses = tuple(Bus(self, i, id) for i in range(phys_out + virt_out))
|
||||||
|
if _configuration.extended:
|
||||||
|
if _configuration.extends_horizontal:
|
||||||
|
self.grid(row=0, column=2)
|
||||||
else:
|
else:
|
||||||
self.columnconfigure(i, minsize=self.width)
|
self.grid(row=2, column=0, sticky=(tk.W))
|
||||||
labelframe.grid()
|
else:
|
||||||
|
self.grid(row=0, column=0)
|
||||||
|
self.grid_configure()
|
||||||
|
init_labels(self, id)
|
||||||
|
|
||||||
|
if id == "strip":
|
||||||
|
CHANNELFRAME_cls = type(
|
||||||
|
f"ChannelFrame{id.capitalize}",
|
||||||
|
(ChannelFrame,),
|
||||||
|
{
|
||||||
|
"__init__": init_strip,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
CHANNELFRAME_cls = type(
|
||||||
|
f"ChannelFrame{id.capitalize}",
|
||||||
|
(ChannelFrame,),
|
||||||
|
{
|
||||||
|
"__init__": init_bus,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return CHANNELFRAME_cls(parent)
|
||||||
|
@ -2,21 +2,21 @@ import tkinter as tk
|
|||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from .data import _base_vals
|
from . import builders
|
||||||
|
from .data import _configuration, _base_values
|
||||||
|
|
||||||
|
|
||||||
class Config(ttk.Frame):
|
class Config(ttk.Frame):
|
||||||
def __init__(self, parent, index, _id):
|
def __init__(self, parent, index, _id):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.id = _id
|
self.id = _id
|
||||||
self.s = parent.styletable
|
self.styletable = parent.styletable
|
||||||
|
|
||||||
self.phys_in, self.virt_in = parent.kind.ins
|
self.phys_in, self.virt_in = parent.kind.ins
|
||||||
self.phys_out, self.virt_out = parent.kind.outs
|
self.phys_out, self.virt_out = parent.kind.outs
|
||||||
|
|
||||||
self.watch_pdirty()
|
self.parent.subject_pdirty.add(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
@ -25,277 +25,99 @@ class Config(ttk.Frame):
|
|||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the current interface"""
|
||||||
return self._parent.target
|
|
||||||
|
return self.parent.target
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if param in dir(self.target):
|
|
||||||
return getattr(self.target, param)
|
return getattr(self.target, param)
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if param in dir(self.target):
|
|
||||||
setattr(self.target, param, value)
|
setattr(self.target, param, value)
|
||||||
|
|
||||||
def scale_enter(self, *args):
|
def scale_enter(self, *args):
|
||||||
_base_vals.in_scale_button_1 = True
|
_base_values.in_scale_button_1 = True
|
||||||
|
|
||||||
def scale_leave(self, *args):
|
def scale_leave(self, *args):
|
||||||
_base_vals.in_scale_button_1 = False
|
_base_values.in_scale_button_1 = False
|
||||||
self._parent.nav_frame.info_text.set("")
|
self.parent.nav_frame.info_text.set("")
|
||||||
|
|
||||||
def scale_callback(self, param, *args):
|
def scale_callback(self, param, *args):
|
||||||
"""callback function for scale widget"""
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
val = self.slider_vars[self.slider_params.index(param)].get()
|
val = self.slider_vars[self.slider_params.index(param)].get()
|
||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
self._parent.nav_frame.info_text.set(round(val, 1))
|
self.parent.nav_frame.info_text.set(round(val, 1))
|
||||||
|
|
||||||
def reset_scale(self, param, val, *args):
|
def reset_scale(self, param, val, *args):
|
||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
self.slider_vars[self.slider_params.index(param)].set(val)
|
self.slider_vars[self.slider_params.index(param)].set(val)
|
||||||
|
|
||||||
def col_row_configure(self):
|
def toggle_p(self, param):
|
||||||
[
|
val = self.param_vars[self.params.index(param)].get()
|
||||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
self.setter(param, val)
|
||||||
for child in self.winfo_children()
|
if not _configuration.themes_enabled:
|
||||||
if isinstance(child, ttk.Checkbutton)
|
self.styletable.configure(
|
||||||
]
|
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||||
self.grid(sticky=(tk.W))
|
)
|
||||||
|
|
||||||
def watch_pdirty(self):
|
def update(self):
|
||||||
self.after(1, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
def watch_pdirty_step(self):
|
|
||||||
"""keeps params synced but ensures sliders are responsive"""
|
|
||||||
if self._parent.pdirty and not _base_vals.in_scale_button_1:
|
|
||||||
self.sync()
|
self.sync()
|
||||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
|
|
||||||
class StripConfig(Config):
|
class StripConfig(Config):
|
||||||
def __init__(self, parent, index, _id):
|
def __init__(self, parent, index, _id):
|
||||||
super().__init__(parent, index, _id)
|
super().__init__(parent, index, _id)
|
||||||
|
self.grid(column=0, row=1, columnspan=4)
|
||||||
|
self.builder = builders.StripConfigFrameBuilder(self)
|
||||||
|
self.builder.setup()
|
||||||
|
self.make_row_0()
|
||||||
|
self.make_row_1()
|
||||||
|
self.make_row_2()
|
||||||
|
self.builder.grid_configure()
|
||||||
|
|
||||||
# create parameter variables
|
|
||||||
if self._parent.kind.name == "Basic":
|
|
||||||
self.slider_params = ("audibility",)
|
|
||||||
self.slider_vars = (tk.DoubleVar(),)
|
|
||||||
else:
|
|
||||||
self.slider_params = ("comp", "gate", "limit")
|
|
||||||
self.slider_vars = [
|
|
||||||
tk.DoubleVar() for i, _ in enumerate(self.slider_params)
|
|
||||||
]
|
|
||||||
|
|
||||||
self.phys_out_params = [f"A{i+1}" for i in range(self.phys_out)]
|
|
||||||
self.phys_out_params_vars = [
|
|
||||||
tk.BooleanVar() for i, _ in enumerate(self.phys_out_params)
|
|
||||||
]
|
|
||||||
|
|
||||||
self.virt_out_params = [f"B{i+1}" for i in range(self.virt_out)]
|
|
||||||
self.virt_out_params_vars = [
|
|
||||||
tk.BooleanVar() for i, _ in enumerate(self.virt_out_params)
|
|
||||||
]
|
|
||||||
|
|
||||||
self.params = ("mono", "solo")
|
|
||||||
self.param_vars = list(tk.BooleanVar() for i, _ in enumerate(self.params))
|
|
||||||
|
|
||||||
self.make_row0()
|
|
||||||
self.make_row1()
|
|
||||||
self.make_row2()
|
|
||||||
|
|
||||||
# sync all parameters
|
|
||||||
self.sync()
|
self.sync()
|
||||||
self.sync_sliders()
|
|
||||||
|
|
||||||
self.col_row_configure()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""use the correct interface"""
|
"""returns the strip class for this configframe in the current interface"""
|
||||||
|
|
||||||
_target = super(StripConfig, self).target
|
_target = super(StripConfig, self).target
|
||||||
return getattr(_target, self.identifier)[self.index]
|
return getattr(_target, self.identifier)[self.index]
|
||||||
|
|
||||||
def make_row0(self):
|
def make_row_0(self):
|
||||||
# Create sliders
|
|
||||||
if self.index < self.phys_in:
|
if self.index < self.phys_in:
|
||||||
if self._parent.kind.name == "Basic":
|
if self.parent.kind.name == "Basic":
|
||||||
# audibility
|
self.builder.create_audibility_slider()
|
||||||
aud_label = ttk.Label(self, text="Audibility")
|
|
||||||
aud_scale = ttk.Scale(
|
|
||||||
self,
|
|
||||||
from_=0.0,
|
|
||||||
to=10.0,
|
|
||||||
orient="horizontal",
|
|
||||||
length=_base_vals.level_width,
|
|
||||||
variable=self.slider_vars[self.slider_params.index("audibility")],
|
|
||||||
command=partial(self.scale_callback, "audibility"),
|
|
||||||
)
|
|
||||||
aud_scale.bind(
|
|
||||||
"<Double-Button-1>", partial(self.reset_scale, "audibility", 0)
|
|
||||||
)
|
|
||||||
aud_scale.bind("<Button-1>", self.scale_enter)
|
|
||||||
aud_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
|
||||||
|
|
||||||
aud_label.grid(column=0, row=0)
|
|
||||||
aud_scale.grid(column=1, row=0)
|
|
||||||
else:
|
else:
|
||||||
# comp
|
self.builder.create_comp_slider()
|
||||||
comp_label = ttk.Label(self, text="Comp")
|
self.builder.create_gate_slider()
|
||||||
comp_scale = ttk.Scale(
|
self.builder.create_limit_slider()
|
||||||
self,
|
|
||||||
from_=0.0,
|
|
||||||
to=10.0,
|
|
||||||
orient="horizontal",
|
|
||||||
length=_base_vals.level_width,
|
|
||||||
variable=self.slider_vars[self.slider_params.index("comp")],
|
|
||||||
command=partial(self.scale_callback, "comp"),
|
|
||||||
)
|
|
||||||
comp_scale.bind(
|
|
||||||
"<Double-Button-1>", partial(self.reset_scale, "comp", 0)
|
|
||||||
)
|
|
||||||
comp_scale.bind("<Button-1>", self.scale_enter)
|
|
||||||
comp_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
|
||||||
|
|
||||||
# gate
|
def make_row_1(self):
|
||||||
gate_label = ttk.Label(self, text="Gate")
|
self.builder.create_a_buttons()
|
||||||
gate_scale = ttk.Scale(
|
self.builder.create_b_buttons()
|
||||||
self,
|
|
||||||
from_=0.0,
|
|
||||||
to=10.0,
|
|
||||||
orient="horizontal",
|
|
||||||
length=_base_vals.level_width,
|
|
||||||
variable=self.slider_vars[self.slider_params.index("gate")],
|
|
||||||
command=partial(self.scale_callback, "gate"),
|
|
||||||
)
|
|
||||||
gate_scale.bind(
|
|
||||||
"<Double-Button-1>", partial(self.reset_scale, "gate", 0)
|
|
||||||
)
|
|
||||||
gate_scale.bind("<Button-1>", self.scale_enter)
|
|
||||||
gate_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
|
||||||
|
|
||||||
# limit
|
def make_row_2(self):
|
||||||
limit_label = ttk.Label(self, text="Limit")
|
self.builder.create_param_buttons()
|
||||||
limit_scale = ttk.Scale(
|
|
||||||
self,
|
|
||||||
from_=-40,
|
|
||||||
to=12,
|
|
||||||
orient="horizontal",
|
|
||||||
length=_base_vals.level_width,
|
|
||||||
variable=self.slider_vars[self.slider_params.index("limit")],
|
|
||||||
command=partial(self.scale_callback, "limit"),
|
|
||||||
)
|
|
||||||
limit_scale.bind(
|
|
||||||
"<Double-Button-1>", partial(self.reset_scale, "limit", 12)
|
|
||||||
)
|
|
||||||
limit_scale.bind("<Button-1>", self.scale_enter)
|
|
||||||
limit_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
|
||||||
|
|
||||||
# Position sliders
|
|
||||||
comp_label.grid(column=0, row=0)
|
|
||||||
comp_scale.grid(column=1, row=0)
|
|
||||||
gate_label.grid(column=2, row=0)
|
|
||||||
gate_scale.grid(column=3, row=0)
|
|
||||||
limit_label.grid(column=4, row=0)
|
|
||||||
limit_scale.grid(column=5, row=0)
|
|
||||||
|
|
||||||
def make_row1(self):
|
|
||||||
# create buttons
|
|
||||||
self.a_buttons = [
|
|
||||||
ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text=param,
|
|
||||||
command=partial(self.toggle_a, param),
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
|
||||||
variable=self.phys_out_params_vars[self.phys_out_params.index(param)],
|
|
||||||
)
|
|
||||||
for param in self.phys_out_params
|
|
||||||
]
|
|
||||||
self.b_buttons = [
|
|
||||||
ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text=param,
|
|
||||||
command=partial(self.toggle_b, param),
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
|
||||||
variable=self.virt_out_params_vars[self.virt_out_params.index(param)],
|
|
||||||
)
|
|
||||||
for param in self.virt_out_params
|
|
||||||
]
|
|
||||||
|
|
||||||
# set button positions
|
|
||||||
[
|
|
||||||
button.grid(
|
|
||||||
column=self.a_buttons.index(button),
|
|
||||||
row=1,
|
|
||||||
)
|
|
||||||
for button in self.a_buttons
|
|
||||||
]
|
|
||||||
[
|
|
||||||
button.grid(
|
|
||||||
column=len(self.a_buttons) + self.b_buttons.index(button),
|
|
||||||
row=1,
|
|
||||||
)
|
|
||||||
for button in self.b_buttons
|
|
||||||
]
|
|
||||||
|
|
||||||
def toggle_a(self, param):
|
def toggle_a(self, param):
|
||||||
val = self.phys_out_params_vars[self.phys_out_params.index(param)].get()
|
val = self.phys_out_params_vars[self.phys_out_params.index(param)].get()
|
||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_b(self, param):
|
def toggle_b(self, param):
|
||||||
val = self.virt_out_params_vars[self.virt_out_params.index(param)].get()
|
val = self.virt_out_params_vars[self.virt_out_params.index(param)].get()
|
||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_row2(self):
|
def teardown(self):
|
||||||
if self._parent.kind.name in ("Banana", "Potato"):
|
self.builder.teardown()
|
||||||
if self.index == self.phys_in:
|
|
||||||
self.params = list(map(lambda x: x.replace("mono", "mc"), self.params))
|
|
||||||
if self._parent.kind.name == "Banana":
|
|
||||||
pass
|
|
||||||
# karaoke modes not in RT Packet yet. May implement in future
|
|
||||||
"""
|
|
||||||
if self.index == self.phys_in + 1:
|
|
||||||
self.params = list(
|
|
||||||
map(lambda x: x.replace("mono", "k"), self.params)
|
|
||||||
)
|
|
||||||
self.param_vars[self.params.index("k")] = tk.IntVar
|
|
||||||
"""
|
|
||||||
else:
|
|
||||||
if self.index == self.phys_in + self.virt_in - 1:
|
|
||||||
self.params = list(
|
|
||||||
map(lambda x: x.replace("mono", "mc"), self.params)
|
|
||||||
)
|
|
||||||
|
|
||||||
param_buttons = [
|
|
||||||
ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text=param,
|
|
||||||
command=partial(self.toggle_p, param),
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
|
||||||
variable=self.param_vars[self.params.index(param)],
|
|
||||||
)
|
|
||||||
for param in self.params
|
|
||||||
]
|
|
||||||
[
|
|
||||||
button.grid(
|
|
||||||
column=param_buttons.index(button),
|
|
||||||
row=2,
|
|
||||||
)
|
|
||||||
for button in param_buttons
|
|
||||||
]
|
|
||||||
|
|
||||||
def toggle_p(self, param):
|
|
||||||
val = self.param_vars[self.params.index(param)].get()
|
|
||||||
self.setter(param, val)
|
|
||||||
if not _base_vals.using_theme:
|
|
||||||
self.s.configure(
|
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
|
||||||
)
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
[
|
[
|
||||||
@ -314,154 +136,110 @@ class StripConfig(Config):
|
|||||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||||
for param in self.params
|
for param in self.params
|
||||||
]
|
]
|
||||||
if not _base_vals.using_theme:
|
|
||||||
|
if not _configuration.themes_enabled:
|
||||||
[
|
[
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f"{param}.TButton",
|
||||||
background=f'{"green" if self.phys_out_params_vars[self.phys_out_params.index(param)].get() else "white"}',
|
background=f'{"green" if self.phys_out_params_vars[self.phys_out_params.index(param)].get() else "white"}',
|
||||||
)
|
)
|
||||||
for param in self.phys_out_params
|
for param in self.phys_out_params
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f"{param}.TButton",
|
||||||
background=f'{"green" if self.virt_out_params_vars[self.virt_out_params.index(param)].get() else "white"}',
|
background=f'{"green" if self.virt_out_params_vars[self.virt_out_params.index(param)].get() else "white"}',
|
||||||
)
|
)
|
||||||
for param in self.virt_out_params
|
for param in self.virt_out_params
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f"{param}.TButton",
|
||||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||||
)
|
)
|
||||||
for param in self.params
|
for param in self.params
|
||||||
]
|
]
|
||||||
|
|
||||||
def sync_sliders(self):
|
|
||||||
[
|
|
||||||
self.slider_vars[self.slider_params.index(param)].set(self.getter(param))
|
|
||||||
for param in self.slider_params
|
|
||||||
]
|
|
||||||
|
|
||||||
def col_row_configure(self):
|
|
||||||
super(StripConfig, self).col_row_configure()
|
|
||||||
[
|
|
||||||
self.columnconfigure(i, minsize=80)
|
|
||||||
for i in range(self.phys_out + self.virt_out)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class BusConfig(Config):
|
class BusConfig(Config):
|
||||||
def __init__(self, parent, index, _id):
|
def __init__(self, parent, index, _id):
|
||||||
super().__init__(parent, index, _id)
|
super().__init__(parent, index, _id)
|
||||||
# fmt: off
|
if _configuration.extends_horizontal:
|
||||||
# create parameter variables
|
self.grid(column=0, row=1, columnspan=4)
|
||||||
self.bus_modes = (
|
else:
|
||||||
"normal", "Amix", "Bmix", "Repeat", "Composite", "TVMix", "UpMix21",
|
self.grid(column=0, row=3, columnspan=4)
|
||||||
"UpMix41", "UpMix61", "CenterOnly", "LFEOnly", "RearOnly",
|
self.builder = builders.BusConfigFrameBuilder(self)
|
||||||
)
|
self.builder.setup()
|
||||||
# fmt: on
|
self.make_row_0()
|
||||||
self.params = ("mono", "eq", "eq_ab")
|
self.make_row_1()
|
||||||
self.param_vars = [tk.BooleanVar() for i, _ in enumerate(self.params)]
|
self.builder.grid_configure()
|
||||||
|
|
||||||
# sync all parameters
|
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
self.make_row0()
|
|
||||||
self.make_row1()
|
|
||||||
|
|
||||||
self.col_row_configure()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the bus class for this configframe in the current interface"""
|
||||||
|
|
||||||
_target = super(BusConfig, self).target
|
_target = super(BusConfig, self).target
|
||||||
return getattr(_target, self.identifier)[self.index]
|
return getattr(_target, self.identifier)[self.index]
|
||||||
|
|
||||||
@property
|
def make_row_0(self):
|
||||||
def bus_mode(self):
|
self.builder.create_bus_mode_button()
|
||||||
return self._parent.bus_modes_cache[
|
|
||||||
"vban" if _base_vals.vban_connected else "vmr"
|
|
||||||
][self.index].get()
|
|
||||||
|
|
||||||
@bus_mode.setter
|
def make_row_1(self):
|
||||||
def bus_mode(self, val):
|
self.builder.create_param_buttons()
|
||||||
self._parent.bus_modes_cache["vban" if _base_vals.vban_connected else "vmr"][
|
|
||||||
self.index
|
|
||||||
].set(val)
|
|
||||||
|
|
||||||
def make_row0(self):
|
def current_bus_mode(self):
|
||||||
self.bus_mode_label_text = tk.StringVar(value=f"Bus Mode: {self.bus_mode}")
|
for mode in self.bus_modes:
|
||||||
self.busmode_button = ttk.Button(self, textvariable=self.bus_mode_label_text)
|
if getattr(self.target.mode, mode):
|
||||||
self.busmode_button.grid(column=0, row=0, columnspan=2, sticky=(tk.W))
|
return mode
|
||||||
self.busmode_button.bind("<Button-1>", self.rotate_bus_modes_right)
|
|
||||||
self.busmode_button.bind("<Button-3>", self.rotate_bus_modes_left)
|
|
||||||
|
|
||||||
def rotate_bus_modes_right(self, *args):
|
def rotate_bus_modes_right(self, *args):
|
||||||
current_index = self.bus_modes.index(self.bus_mode)
|
current_mode = self.current_bus_mode()
|
||||||
if current_index + 1 < len(self.bus_modes):
|
next = self.bus_modes.index(current_mode) + 1
|
||||||
self.bus_mode = self.bus_modes[current_index + 1]
|
if next < len(self.bus_modes):
|
||||||
|
setattr(
|
||||||
|
self.target.mode,
|
||||||
|
self.bus_modes[next],
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[next]])
|
||||||
else:
|
else:
|
||||||
self.bus_mode = self.bus_modes[0]
|
self.target.mode.normal = True
|
||||||
setattr(self.target.mode, self.bus_mode.lower(), True)
|
self.bus_mode_label_text.set("Normal")
|
||||||
self.bus_mode_label_text.set(f"Bus Mode: {self.bus_mode}")
|
|
||||||
|
|
||||||
def rotate_bus_modes_left(self, *args):
|
def rotate_bus_modes_left(self, *args):
|
||||||
current_index = self.bus_modes.index(self.bus_mode)
|
current_mode = self.current_bus_mode()
|
||||||
if current_index == 0:
|
prev = self.bus_modes.index(current_mode) - 1
|
||||||
self.bus_mode = self.bus_modes[-1]
|
if prev < 0:
|
||||||
|
self.target.mode.rearonly = True
|
||||||
|
self.bus_mode_label_text.set("Rear Only")
|
||||||
else:
|
else:
|
||||||
self.bus_mode = self.bus_modes[current_index - 1]
|
setattr(
|
||||||
setattr(self.target.mode, self.bus_mode.lower(), True)
|
self.target.mode,
|
||||||
self.bus_mode_label_text.set(f"Bus Mode: {self.bus_mode}")
|
self.bus_modes[prev],
|
||||||
|
True,
|
||||||
def make_row1(self):
|
|
||||||
param_buttons = [
|
|
||||||
ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text=param,
|
|
||||||
command=partial(self.toggle_p, param),
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
|
||||||
variable=self.param_vars[self.params.index(param)],
|
|
||||||
)
|
)
|
||||||
for param in self.params
|
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[prev]])
|
||||||
]
|
|
||||||
[
|
|
||||||
button.grid(
|
|
||||||
column=param_buttons.index(button),
|
|
||||||
row=1,
|
|
||||||
)
|
|
||||||
for button in param_buttons
|
|
||||||
]
|
|
||||||
|
|
||||||
def toggle_p(self, param):
|
def teardown(self):
|
||||||
val = self.param_vars[self.params.index(param)].get()
|
self.builder.teardown()
|
||||||
self.setter(param, val)
|
|
||||||
if not _base_vals.using_theme:
|
|
||||||
self.s.configure(
|
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
|
||||||
)
|
|
||||||
|
|
||||||
def col_row_configure(self):
|
|
||||||
super(BusConfig, self).col_row_configure()
|
|
||||||
[
|
|
||||||
self.columnconfigure(i, minsize=80)
|
|
||||||
for i in range(self.phys_out + self.virt_out)
|
|
||||||
]
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
for i, mode in enumerate(self.bus_modes):
|
|
||||||
if getattr(self.target.mode, mode.lower()):
|
|
||||||
self.bus_mode = self.bus_modes[i]
|
|
||||||
[
|
[
|
||||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||||
for param in self.params
|
for param in self.params
|
||||||
]
|
]
|
||||||
if not _base_vals.using_theme:
|
self.bus_mode_label_text.set(self.bus_mode_map[self.current_bus_mode()])
|
||||||
|
if not _configuration.themes_enabled:
|
||||||
[
|
[
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f"{param}.TButton",
|
||||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||||
)
|
)
|
||||||
for param in self.params
|
for param in self.params
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Iterator:
|
||||||
|
pass
|
||||||
|
@ -16,5 +16,39 @@ for path in config_path:
|
|||||||
print(f"Invalid TOML profile: configs/{filename.stem}")
|
print(f"Invalid TOML profile: configs/{filename.stem}")
|
||||||
|
|
||||||
for name, cfg in configs.items():
|
for name, cfg in configs.items():
|
||||||
print(f"Loaded profile configs/{name}")
|
print(f"Loaded configuration configs/{name}")
|
||||||
configuration[name] = cfg
|
configuration[name] = cfg
|
||||||
|
|
||||||
|
_defaults = {
|
||||||
|
"profiles": {
|
||||||
|
"profile": None,
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"enabled": True,
|
||||||
|
"mode": "light",
|
||||||
|
},
|
||||||
|
"extends": {
|
||||||
|
"extended": True,
|
||||||
|
"extends_horizontal": True,
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"width": 80,
|
||||||
|
"height": 130,
|
||||||
|
},
|
||||||
|
"mwscroll_step": {
|
||||||
|
"size": 3,
|
||||||
|
},
|
||||||
|
"submixes": {
|
||||||
|
"default": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if "app" in configuration:
|
||||||
|
configuration["app"] = _defaults | configuration["app"]
|
||||||
|
else:
|
||||||
|
configuration["app"] = _defaults
|
||||||
|
|
||||||
|
|
||||||
|
def get_configuration(key):
|
||||||
|
if key in configuration:
|
||||||
|
return configuration[key]
|
||||||
|
@ -1,24 +1,53 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from voicemeeter import kinds
|
from voicemeeter import kinds
|
||||||
|
|
||||||
|
from .configurations import get_configuration
|
||||||
|
|
||||||
|
configuration = get_configuration("app")
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonMeta(type):
|
||||||
|
_instances = {}
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
if cls not in cls._instances:
|
||||||
|
instance = super().__call__(*args, **kwargs)
|
||||||
|
cls._instances[cls] = instance
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BaseValues:
|
class Configurations(metaclass=SingletonMeta):
|
||||||
|
# width of a single labelframe
|
||||||
|
level_width: int = 75
|
||||||
|
# height of a single labelframe
|
||||||
level_height: int = 100
|
level_height: int = 100
|
||||||
level_width: int = 80
|
|
||||||
|
|
||||||
|
# is the gui extended
|
||||||
|
extended: bool = configuration["extends"]["extended"]
|
||||||
|
# direction the gui extends
|
||||||
|
extends_horizontal: bool = configuration["extends"]["extends_horizontal"]
|
||||||
|
# are themes enabled
|
||||||
|
themes_enabled: bool = configuration["theme"]["enabled"]
|
||||||
|
# light or dark
|
||||||
|
theme_mode: str = configuration["theme"]["mode"]
|
||||||
|
# size of mousewheel scroll step
|
||||||
|
mwscroll_step: int = configuration["mwscroll_step"]["size"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def profile(self):
|
||||||
|
if "profiles" in configuration:
|
||||||
|
return configuration["profiles"]["profile"]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BaseValues(metaclass=SingletonMeta):
|
||||||
# are we dragging a scale with mouse 1
|
# are we dragging a scale with mouse 1
|
||||||
in_scale_button_1: bool = False
|
in_scale_button_1: bool = False
|
||||||
# are we dragging main window with mouse 1
|
# are we dragging main window with mouse 1
|
||||||
dragging: bool = False
|
dragging: bool = False
|
||||||
# direction the gui extends
|
|
||||||
extends_horizontal: bool = True
|
|
||||||
# a vban connection established
|
# a vban connection established
|
||||||
vban_connected: bool = False
|
vban_connected: bool = False
|
||||||
# are themes enabled
|
|
||||||
themes_enabled: bool = True
|
|
||||||
# are we using a theme
|
|
||||||
using_theme: bool = False
|
|
||||||
# bus assigned as current submix
|
# bus assigned as current submix
|
||||||
submixes: int = 0
|
submixes: int = 0
|
||||||
# pdirty delay
|
# pdirty delay
|
||||||
@ -29,12 +58,10 @@ class BaseValues:
|
|||||||
strip_level_array_size: int = None
|
strip_level_array_size: int = None
|
||||||
# size of bus level array for a kind
|
# size of bus level array for a kind
|
||||||
bus_level_array_size: int = None
|
bus_level_array_size: int = None
|
||||||
# size of mousewheel scroll step
|
|
||||||
mwscroll_step: int = 3
|
|
||||||
|
|
||||||
|
|
||||||
_base_vals = BaseValues()
|
_base_values = BaseValues()
|
||||||
|
_configuration = Configurations()
|
||||||
|
|
||||||
_kinds = {kind.id: kind for kind in kinds.all}
|
_kinds = {kind.id: kind for kind in kinds.all}
|
||||||
|
|
||||||
|
@ -2,7 +2,8 @@ import tkinter as tk
|
|||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from math import log
|
from math import log
|
||||||
|
|
||||||
from .data import _base_vals
|
from .data import _base_values, _configuration
|
||||||
|
from . import builders
|
||||||
|
|
||||||
|
|
||||||
class GainLayer(ttk.LabelFrame):
|
class GainLayer(ttk.LabelFrame):
|
||||||
@ -10,29 +11,28 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
|
|
||||||
def __init__(self, parent, index, j):
|
def __init__(self, parent, index, j):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.j = j
|
self.j = j
|
||||||
self.gain = tk.DoubleVar()
|
self.styletable = self.parent.parent.styletable
|
||||||
self.level = tk.DoubleVar()
|
|
||||||
self.on = tk.BooleanVar()
|
|
||||||
self.s = self._parent._parent.styletable
|
|
||||||
if index <= parent.phys_in:
|
if index <= parent.phys_in:
|
||||||
self.level_offset = index * 2
|
self.level_offset = index * 2
|
||||||
else:
|
else:
|
||||||
self.level_offset = parent.phys_in * 2 + (index - parent.phys_in) * 8
|
self.level_offset = parent.phys_in * 2 + (index - parent.phys_in) * 8
|
||||||
|
|
||||||
|
self.builder = builders.ChannelLabelFrameBuilder(self, index, id="gainlayer")
|
||||||
|
self.builder.setup()
|
||||||
|
self.builder.add_progressbar()
|
||||||
|
self.builder.add_scale()
|
||||||
|
self.builder.add_on_button()
|
||||||
self.sync()
|
self.sync()
|
||||||
self._make_widgets()
|
self.grid_configure()
|
||||||
|
|
||||||
self.col_row_configure()
|
|
||||||
self.watch_pdirty()
|
|
||||||
self.watch_levels()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the strip[i].gainlayer class in the current interface"""
|
||||||
_target = self._parent.target
|
|
||||||
|
_target = self.parent.target
|
||||||
return _target.strip[self.index].gainlayer[self.j]
|
return _target.strip[self.index].gainlayer[self.j]
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
@ -46,27 +46,33 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
def reset_gain(self, *args):
|
def reset_gain(self, *args):
|
||||||
self.setter("gain", 0)
|
self.setter("gain", 0)
|
||||||
self.gain.set(0)
|
self.gain.set(0)
|
||||||
self._parent._parent.nav_frame.info_text.set(0)
|
self.parent.parent.nav_frame.info_text.set(0)
|
||||||
|
|
||||||
def scale_enter(self, *args):
|
def scale_callback(self, *args):
|
||||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
def scale_leave(self, *args):
|
self.setter("gain", self.gain.get())
|
||||||
self._parent._parent.nav_frame.info_text.set("")
|
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
def scale_press(self, *args):
|
def scale_press(self, *args):
|
||||||
_base_vals.in_scale_button_1 = True
|
_base_values.in_scale_button_1 = True
|
||||||
|
|
||||||
def scale_release(self, *args):
|
def scale_release(self, *args):
|
||||||
_base_vals.in_scale_button_1 = False
|
_base_values.in_scale_button_1 = False
|
||||||
|
|
||||||
|
def scale_enter(self, *args):
|
||||||
|
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
|
def scale_leave(self, *args):
|
||||||
|
self.parent.parent.nav_frame.info_text.set("")
|
||||||
|
|
||||||
def _on_mousewheel(self, event):
|
def _on_mousewheel(self, event):
|
||||||
self.gain.set(
|
self.gain.set(
|
||||||
self.gain.get()
|
self.gain.get()
|
||||||
+ (
|
+ (
|
||||||
_base_vals.mwscroll_step
|
_base_values.mwscroll_step
|
||||||
if event.delta > 0
|
if event.delta > 0
|
||||||
else -_base_vals.mwscroll_step
|
else -_base_values.mwscroll_step
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.gain.get() > 12:
|
if self.gain.get() > 12:
|
||||||
@ -74,72 +80,23 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
elif self.gain.get() < -60:
|
elif self.gain.get() < -60:
|
||||||
self.gain.set(-60)
|
self.gain.set(-60)
|
||||||
self.setter("gain", self.gain.get())
|
self.setter("gain", self.gain.get())
|
||||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
def scale_callback(self, *args):
|
|
||||||
"""callback function for scale widget"""
|
|
||||||
self.setter("gain", self.gain.get())
|
|
||||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
|
||||||
|
|
||||||
def set_on(self):
|
def set_on(self):
|
||||||
"""enables a gainlayer. sets its button colour"""
|
"""enables a gainlayer. sets its button colour"""
|
||||||
|
|
||||||
setattr(
|
setattr(
|
||||||
self._parent.target.strip[self.index],
|
self.parent.target.strip[self.index],
|
||||||
self._parent.buses[self.j],
|
self.parent.buses[self.j],
|
||||||
self.on.get(),
|
self.on.get(),
|
||||||
)
|
)
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"On.TButton",
|
f"On.TButton",
|
||||||
background=f'{"green" if self.on.get() else "white"}',
|
background=f'{"green" if self.on.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def convert_level(self, val):
|
def grid_configure(self):
|
||||||
if _base_vals.vban_connected:
|
|
||||||
return round(-val * 0.01, 1)
|
|
||||||
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
|
||||||
|
|
||||||
def _make_widgets(self):
|
|
||||||
"""Creates a progressbar, scale, on button and config button for a single channel"""
|
|
||||||
# Progress bar
|
|
||||||
self.pb = ttk.Progressbar(
|
|
||||||
self,
|
|
||||||
maximum=100,
|
|
||||||
orient="vertical",
|
|
||||||
mode="determinate",
|
|
||||||
variable=self.level,
|
|
||||||
)
|
|
||||||
self.pb.grid(column=0, row=0)
|
|
||||||
|
|
||||||
# Scale
|
|
||||||
self.scale = ttk.Scale(
|
|
||||||
self,
|
|
||||||
from_=12.0,
|
|
||||||
to=-60.0,
|
|
||||||
orient="vertical",
|
|
||||||
variable=self.gain,
|
|
||||||
command=self.scale_callback,
|
|
||||||
length=self._parent.height,
|
|
||||||
)
|
|
||||||
self.scale.grid(column=1, row=0)
|
|
||||||
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
|
||||||
self.scale.bind("<Button-1>", self.scale_press)
|
|
||||||
self.scale.bind("<Enter>", self.scale_enter)
|
|
||||||
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
|
||||||
self.scale.bind("<Leave>", self.scale_leave)
|
|
||||||
self.scale.bind("<MouseWheel>", self._on_mousewheel)
|
|
||||||
|
|
||||||
# On button
|
|
||||||
self.button_on = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text="ON",
|
|
||||||
command=self.set_on,
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else 'On.TButton'}",
|
|
||||||
variable=self.on,
|
|
||||||
)
|
|
||||||
self.button_on.grid(column=0, row=1, columnspan=2)
|
|
||||||
|
|
||||||
def col_row_configure(self):
|
|
||||||
[
|
[
|
||||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||||
for child in self.winfo_children()
|
for child in self.winfo_children()
|
||||||
@ -150,141 +107,119 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
for child in self.winfo_children()
|
for child in self.winfo_children()
|
||||||
if isinstance(child, ttk.Progressbar) or isinstance(child, ttk.Scale)
|
if isinstance(child, ttk.Progressbar) or isinstance(child, ttk.Scale)
|
||||||
]
|
]
|
||||||
|
# pb and scale
|
||||||
self.columnconfigure(0, minsize=36)
|
self.columnconfigure(0, minsize=36)
|
||||||
self.columnconfigure(1, minsize=36)
|
self.columnconfigure(1, minsize=36)
|
||||||
|
# on button
|
||||||
|
if _configuration.themes_enabled:
|
||||||
self.rowconfigure(1, minsize=70)
|
self.rowconfigure(1, minsize=70)
|
||||||
|
else:
|
||||||
def watch_pdirty(self):
|
self.rowconfigure(1, minsize=55)
|
||||||
self.after(1, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
def watch_pdirty_step(self):
|
|
||||||
"""keeps params synced but ensures sliders are responsive"""
|
|
||||||
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
|
||||||
self.sync()
|
|
||||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""sync params with voicemeeter"""
|
"""sync params with voicemeeter"""
|
||||||
retval = self._parent.target.strip[self.index].label
|
retval = self.parent.target.strip[self.index].label
|
||||||
if len(retval) > 10:
|
if len(retval) > 10:
|
||||||
retval = f"{retval[:8]}.."
|
retval = f"{retval[:8]}.."
|
||||||
|
if not retval:
|
||||||
|
self.parent.columnconfigure(self.index, minsize=0)
|
||||||
|
self.parent.parent.subject_ldirty.remove(self)
|
||||||
|
self.grid_remove()
|
||||||
|
else:
|
||||||
|
self.parent.parent.subject_ldirty.add(self)
|
||||||
|
self.grid()
|
||||||
self.configure(text=retval)
|
self.configure(text=retval)
|
||||||
self.gain.set(self.getter("gain"))
|
self.gain.set(self.getter("gain"))
|
||||||
self.on.set(
|
self.on.set(
|
||||||
getattr(
|
getattr(
|
||||||
self._parent.target.strip[self.index],
|
self.parent.target.strip[self.index],
|
||||||
self._parent.buses[self.j],
|
self.parent.buses[self.j],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def watch_levels(self):
|
def convert_level(self, val):
|
||||||
self.after(1, self.watch_levels_step)
|
if _base_values.vban_connected:
|
||||||
|
return round(-val * 0.01, 1)
|
||||||
|
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
||||||
|
|
||||||
def watch_levels_step(self):
|
def update(self):
|
||||||
if not _base_vals.dragging:
|
"""update levels"""
|
||||||
if (
|
|
||||||
self._parent._parent.ldirty
|
|
||||||
and any(
|
|
||||||
self._parent._parent.comp_strip[
|
|
||||||
self.level_offset : self.level_offset + 1
|
|
||||||
]
|
|
||||||
)
|
|
||||||
and _base_vals.strip_level_array_size
|
|
||||||
== len(self._parent._parent.comp_strip)
|
|
||||||
):
|
|
||||||
vals = (
|
vals = (
|
||||||
self.convert_level(
|
self.convert_level(self.parent.target.strip_levels[self.level_offset]),
|
||||||
self._parent._parent.strip_levels[self.level_offset]
|
self.convert_level(self.parent.target.strip_levels[self.level_offset + 1]),
|
||||||
),
|
|
||||||
self.convert_level(
|
|
||||||
self._parent._parent.strip_levels[self.level_offset + 1]
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
self.level.set(
|
self.level.set(
|
||||||
(
|
(
|
||||||
0
|
0
|
||||||
if self._parent._parent.channel_frame.strips[
|
if self.parent.parent.strip_frame.strips[self.index].mute.get()
|
||||||
self.index
|
|
||||||
].mute.get()
|
|
||||||
or not self.on.get()
|
or not self.on.get()
|
||||||
else 100 + (max(vals) - 18) + self.gain.get()
|
else 100 + (max(vals) - 18) + self.gain.get()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.after(
|
|
||||||
_base_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
|
||||||
self.watch_levels_step,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SubMixFrame(ttk.Frame):
|
class SubMixFrame(ttk.Frame):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self.phys_in, self.virt_in = parent.kind.ins
|
self.phys_in, self.virt_in = parent.kind.ins
|
||||||
self.phys_out, self.virt_out = parent.kind.outs
|
self.phys_out, self.virt_out = parent.kind.outs
|
||||||
self.buses = tuple(f"A{i+1}" for i in range(self.phys_out)) + tuple(
|
self.buses = tuple(f"A{i+1}" for i in range(self.phys_out)) + tuple(
|
||||||
f"B{i+1}" for i in range(self.virt_out)
|
f"B{i+1}" for i in range(self.virt_out)
|
||||||
)
|
)
|
||||||
defaults = {
|
|
||||||
"width": 80,
|
|
||||||
"height": 150,
|
|
||||||
}
|
|
||||||
self.configuration = defaults | self.configuration
|
|
||||||
self.width = self.configuration["width"]
|
|
||||||
self.height = self.configuration["height"]
|
|
||||||
|
|
||||||
self.gainlayers = [
|
self.gainlayers = [
|
||||||
GainLayer(self, index, _base_vals.submixes) for index in range(8)
|
GainLayer(self, index, _base_values.submixes) for index in range(8)
|
||||||
]
|
]
|
||||||
[
|
for i, labelframe in enumerate(self.labelframes):
|
||||||
gainlayer.grid(row=0, column=self.gainlayers.index(gainlayer))
|
labelframe.grid(row=0, column=i)
|
||||||
for gainlayer in self.gainlayers
|
if not self.target.strip[i].label:
|
||||||
]
|
|
||||||
|
|
||||||
self.col_row_configure()
|
|
||||||
|
|
||||||
# destroy any without label text
|
|
||||||
for i, gainlayer in enumerate(self.gainlayers):
|
|
||||||
gainlayer.grid(row=0, column=i)
|
|
||||||
if not gainlayer.cget("text"):
|
|
||||||
self.columnconfigure(i, minsize=0)
|
self.columnconfigure(i, minsize=0)
|
||||||
gainlayer.grid_remove()
|
labelframe.grid_remove()
|
||||||
|
|
||||||
self.watch_pdirty()
|
if _configuration.extends_horizontal:
|
||||||
|
self.grid(row=0, column=2)
|
||||||
|
if parent.bus_frame:
|
||||||
|
parent.bus_frame.grid_remove()
|
||||||
|
else:
|
||||||
|
self._parent.submix_frame.grid(row=2, column=0)
|
||||||
|
if parent.bus_frame:
|
||||||
|
parent.bus_frame.grid_remove()
|
||||||
|
|
||||||
|
# registers submixframe as pdirty observer
|
||||||
|
self.parent.subject_pdirty.add(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the current interface"""
|
||||||
return self._parent.target
|
|
||||||
|
return self.parent.target
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def configuration(self):
|
def labelframes(self):
|
||||||
return self._parent.configuration["channel"]
|
"""returns a tuple of current gainlayer labelframe addresses"""
|
||||||
|
|
||||||
@configuration.setter
|
return tuple(
|
||||||
def configuration(self, val):
|
frame
|
||||||
self._parent.configuration["channel"] = val
|
for frame in self.winfo_children()
|
||||||
|
if isinstance(frame, ttk.LabelFrame)
|
||||||
|
)
|
||||||
|
|
||||||
def col_row_configure(self):
|
def grid_configure(self):
|
||||||
[
|
[
|
||||||
self.columnconfigure(i, minsize=self.width)
|
self.columnconfigure(i, minsize=_configuration.level_width)
|
||||||
for i, _ in enumerate(self.gainlayers)
|
for i, _ in enumerate(self.labelframes)
|
||||||
|
]
|
||||||
|
[
|
||||||
|
self.rowconfigure(0, minsize=_configuration.level_height)
|
||||||
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
[self.rowconfigure(0, minsize=130) for i, _ in enumerate(self.gainlayers)]
|
|
||||||
|
|
||||||
def watch_pdirty(self):
|
def update(self):
|
||||||
self.after(1, self.watch_pdirty_step)
|
for labelframe in self.labelframes:
|
||||||
|
labelframe.sync()
|
||||||
|
|
||||||
def watch_pdirty_step(self):
|
def teardown(self):
|
||||||
if self._parent.pdirty:
|
# deregisters submixframe as pdirty observer
|
||||||
self.watch_labels()
|
self.parent.subject_pdirty.remove(self)
|
||||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
self.destroy()
|
||||||
|
|
||||||
def watch_labels(self):
|
|
||||||
for i, gainlayer in enumerate(self.gainlayers):
|
|
||||||
if not self.target.strip[gainlayer.index].label:
|
|
||||||
self.columnconfigure(i, minsize=0)
|
|
||||||
gainlayer.grid_remove()
|
|
||||||
else:
|
|
||||||
self.columnconfigure(i, minsize=80)
|
|
||||||
gainlayer.grid()
|
|
||||||
|
@ -3,20 +3,23 @@ from tkinter import ttk, messagebox
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import sv_ttk
|
import sv_ttk
|
||||||
|
|
||||||
import vbancmd
|
import vbancmd
|
||||||
|
|
||||||
from .configurations import configuration
|
from .data import (
|
||||||
from .data import _base_vals, kind_get
|
get_configuration,
|
||||||
|
_base_values,
|
||||||
|
_configuration,
|
||||||
|
kind_get,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Menus(tk.Menu):
|
class Menus(tk.Menu):
|
||||||
def __init__(self, parent, vmr):
|
def __init__(self, parent, vmr):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self._vmr = vmr
|
self.vmr = vmr
|
||||||
if self.configuration_vban is not None:
|
self.vban_config = get_configuration("vban")
|
||||||
self.vban_conns = [None for i, _ in enumerate(self.configuration_vban)]
|
self.app_config = get_configuration("app")
|
||||||
self._is_topmost = tk.BooleanVar()
|
self._is_topmost = tk.BooleanVar()
|
||||||
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
||||||
|
|
||||||
@ -69,76 +72,23 @@ class Menus(tk.Menu):
|
|||||||
self.menu_profiles_load = tk.Menu(menu_profiles, tearoff=0)
|
self.menu_profiles_load = tk.Menu(menu_profiles, tearoff=0)
|
||||||
menu_profiles.add_cascade(menu=self.menu_profiles_load, label="Load profile")
|
menu_profiles.add_cascade(menu=self.menu_profiles_load, label="Load profile")
|
||||||
defaults = {"base", "blank"}
|
defaults = {"base", "blank"}
|
||||||
if len(vmr.profiles) > len(defaults) and all(
|
if len(self.target.profiles) > len(defaults) and all(
|
||||||
key in vmr.profiles for key in defaults
|
key in self.target.profiles for key in defaults
|
||||||
):
|
):
|
||||||
[
|
[
|
||||||
self.menu_profiles_load.add_command(
|
self.menu_profiles_load.add_command(
|
||||||
label=profile, command=partial(self.load_profile, profile)
|
label=profile, command=partial(self.load_profile, profile)
|
||||||
)
|
)
|
||||||
for profile in vmr.profiles.keys()
|
for profile in self.target.profiles.keys()
|
||||||
if profile not in defaults
|
if profile not in defaults
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
menu_profiles.entryconfig(0, state="disabled")
|
menu_profiles.entryconfig(0, state="disabled")
|
||||||
menu_profiles.add_command(label="Reset to defaults", command=self.load_defaults)
|
menu_profiles.add_command(label="Reset to defaults", command=self.load_defaults)
|
||||||
|
|
||||||
# vban connect menu
|
|
||||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
|
||||||
self.add_cascade(menu=self.menu_vban, label="VBAN")
|
|
||||||
if self.configuration_vban:
|
|
||||||
for i, _ in enumerate(self.configuration_vban):
|
|
||||||
setattr(self, f"menu_vban_{i+1}", tk.Menu(self.menu_vban, tearoff=0))
|
|
||||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
|
||||||
self.menu_vban.add_cascade(
|
|
||||||
menu=target_menu, label=f"VBAN Connect #{i+1}", underline=0
|
|
||||||
)
|
|
||||||
target_menu.add_command(
|
|
||||||
label="Connect", command=partial(self.vban_connect, i)
|
|
||||||
)
|
|
||||||
target_menu.add_command(
|
|
||||||
label="Disconnect", command=partial(self.vban_disconnect, i)
|
|
||||||
)
|
|
||||||
target_menu.entryconfig(1, state="disabled")
|
|
||||||
else:
|
|
||||||
self.entryconfig(3, state="disabled")
|
|
||||||
|
|
||||||
# layout menu
|
# layout menu
|
||||||
self.menu_layout = tk.Menu(self, tearoff=0)
|
self.menu_layout = tk.Menu(self, tearoff=0)
|
||||||
self.add_cascade(menu=self.menu_layout, label="Layout")
|
self.add_cascade(menu=self.menu_layout, label="Layout")
|
||||||
# layout/extends
|
|
||||||
self.menu_extends = tk.Menu(self.menu_layout, tearoff=0)
|
|
||||||
self.menu_layout.add_cascade(
|
|
||||||
menu=self.menu_extends, label="Extends", underline=0
|
|
||||||
)
|
|
||||||
self.menu_extends.add_command(
|
|
||||||
label="horizontal",
|
|
||||||
underline=0,
|
|
||||||
command=partial(self.switch_orientation, extends_horizontal=True),
|
|
||||||
)
|
|
||||||
self.menu_extends.add_command(
|
|
||||||
label="vertical",
|
|
||||||
underline=0,
|
|
||||||
command=partial(self.switch_orientation, extends_horizontal=False),
|
|
||||||
)
|
|
||||||
self.menu_extends.entryconfig(
|
|
||||||
0 if _base_vals.extends_horizontal else 1, state="disabled"
|
|
||||||
)
|
|
||||||
# layout/themes
|
|
||||||
self.menu_themes = tk.Menu(self.menu_layout, tearoff=0)
|
|
||||||
self.menu_layout.add_cascade(menu=self.menu_themes, label="Themes")
|
|
||||||
self.menu_themes.add_command(
|
|
||||||
label="light", command=partial(self.load_theme, "light")
|
|
||||||
)
|
|
||||||
self.menu_themes.add_command(
|
|
||||||
label="dark", command=partial(self.load_theme, "dark")
|
|
||||||
)
|
|
||||||
self.menu_themes.entryconfig(
|
|
||||||
0 if self.configuration_app["theme"]["mode"] == "light" else 1,
|
|
||||||
state="disabled",
|
|
||||||
)
|
|
||||||
if not _base_vals.themes_enabled:
|
|
||||||
self.entryconfig(6, state="disabled")
|
|
||||||
# layout/submixes
|
# layout/submixes
|
||||||
# here we build menu regardless of kind but disable if not Potato
|
# here we build menu regardless of kind but disable if not Potato
|
||||||
buses = tuple(f"A{i+1}" for i in range(5)) + tuple(f"B{i+1}" for i in range(3))
|
buses = tuple(f"A{i+1}" for i in range(5)) + tuple(f"B{i+1}" for i in range(3))
|
||||||
@ -155,9 +105,64 @@ class Menus(tk.Menu):
|
|||||||
)
|
)
|
||||||
for i in range(8)
|
for i in range(8)
|
||||||
]
|
]
|
||||||
self._selected_bus[_base_vals.submixes].set(True)
|
self._selected_bus[_base_values.submixes].set(True)
|
||||||
if self._parent.kind.name != "Potato":
|
if self.parent.kind.name != "Potato":
|
||||||
self.menu_layout.entryconfig(2, state="disabled")
|
self.menu_layout.entryconfig(0, state="disabled")
|
||||||
|
# layout/extends
|
||||||
|
self.menu_extends = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
|
self.menu_layout.add_cascade(
|
||||||
|
menu=self.menu_extends, label="Extends", underline=0
|
||||||
|
)
|
||||||
|
self.menu_extends.add_command(
|
||||||
|
label="horizontal",
|
||||||
|
underline=0,
|
||||||
|
command=partial(self.switch_orientation, extends_horizontal=True),
|
||||||
|
)
|
||||||
|
self.menu_extends.add_command(
|
||||||
|
label="vertical",
|
||||||
|
underline=0,
|
||||||
|
command=partial(self.switch_orientation, extends_horizontal=False),
|
||||||
|
)
|
||||||
|
self.menu_extends.entryconfig(
|
||||||
|
0 if _configuration.extends_horizontal else 1, state="disabled"
|
||||||
|
)
|
||||||
|
# layout/themes
|
||||||
|
self.menu_themes = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
|
self.menu_layout.add_cascade(menu=self.menu_themes, label="Themes")
|
||||||
|
self.menu_themes.add_command(
|
||||||
|
label="light", command=partial(self.load_theme, "light")
|
||||||
|
)
|
||||||
|
self.menu_themes.add_command(
|
||||||
|
label="dark", command=partial(self.load_theme, "dark")
|
||||||
|
)
|
||||||
|
self.menu_themes.entryconfig(
|
||||||
|
0 if self.app_config["theme"]["mode"] == "light" else 1,
|
||||||
|
state="disabled",
|
||||||
|
)
|
||||||
|
if not _configuration.themes_enabled:
|
||||||
|
self.entryconfig(6, state="disabled")
|
||||||
|
|
||||||
|
# vban connect menu
|
||||||
|
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||||
|
self.add_cascade(menu=self.menu_vban, label="VBAN")
|
||||||
|
if self.vban_config:
|
||||||
|
for i, _ in enumerate(self.vban_config):
|
||||||
|
setattr(self, f"menu_vban_{i+1}", tk.Menu(self.menu_vban, tearoff=0))
|
||||||
|
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||||
|
self.menu_vban.add_cascade(
|
||||||
|
menu=target_menu,
|
||||||
|
label=f"{self.vban_config[f'connection-{i+1}']['streamname']}",
|
||||||
|
underline=0,
|
||||||
|
)
|
||||||
|
target_menu.add_command(
|
||||||
|
label="Connect", command=partial(self.vban_connect, i)
|
||||||
|
)
|
||||||
|
target_menu.add_command(
|
||||||
|
label="Disconnect", command=partial(self.vban_disconnect, i)
|
||||||
|
)
|
||||||
|
target_menu.entryconfig(1, state="disabled")
|
||||||
|
else:
|
||||||
|
self.entryconfig(3, state="disabled")
|
||||||
|
|
||||||
# Help menu
|
# Help menu
|
||||||
self.menu_help = tk.Menu(self, tearoff=0)
|
self.menu_help = tk.Menu(self, tearoff=0)
|
||||||
@ -178,20 +183,13 @@ class Menus(tk.Menu):
|
|||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""use the correct interface"""
|
"""use the correct interface"""
|
||||||
return self._parent.target
|
return self.parent.target
|
||||||
|
|
||||||
@property
|
def enable_vban_menus(self):
|
||||||
def configuration_app(self):
|
[
|
||||||
return configuration["app"]
|
self.menu_vban.entryconfig(j, state="normal")
|
||||||
|
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||||
@configuration_app.setter
|
]
|
||||||
def configuration_app(self, val):
|
|
||||||
self.configuration_app = val
|
|
||||||
|
|
||||||
@property
|
|
||||||
def configuration_vban(self):
|
|
||||||
if "vban" in configuration:
|
|
||||||
return configuration["vban"]
|
|
||||||
|
|
||||||
def action_invoke_voicemeeter(self, cmd):
|
def action_invoke_voicemeeter(self, cmd):
|
||||||
getattr(self.target.command, cmd)()
|
getattr(self.target.command, cmd)()
|
||||||
@ -210,11 +208,10 @@ class Menus(tk.Menu):
|
|||||||
self.target.apply_profile("base")
|
self.target.apply_profile("base")
|
||||||
|
|
||||||
def always_on_top(self):
|
def always_on_top(self):
|
||||||
self._parent.attributes("-topmost", self._is_topmost.get())
|
self.parent.attributes("-topmost", self._is_topmost.get())
|
||||||
self._parent.update()
|
|
||||||
|
|
||||||
def switch_orientation(self, extends_horizontal: bool = True, *args):
|
def switch_orientation(self, extends_horizontal: bool = True, *args):
|
||||||
_base_vals.extends_horizontal = extends_horizontal
|
_configuration.extends_horizontal = extends_horizontal
|
||||||
if extends_horizontal:
|
if extends_horizontal:
|
||||||
self.menu_extends.entryconfig(0, state="disabled")
|
self.menu_extends.entryconfig(0, state="disabled")
|
||||||
self.menu_extends.entryconfig(1, state="normal")
|
self.menu_extends.entryconfig(1, state="normal")
|
||||||
@ -223,17 +220,17 @@ class Menus(tk.Menu):
|
|||||||
self.menu_extends.entryconfig(0, state="normal")
|
self.menu_extends.entryconfig(0, state="normal")
|
||||||
|
|
||||||
def set_submix(self, i):
|
def set_submix(self, i):
|
||||||
if _base_vals.submixes != i:
|
if _base_values.submixes != i:
|
||||||
_base_vals.submixes = i
|
_base_values.submixes = i
|
||||||
if self._parent.submix_frame is not None:
|
if self.parent.submix_frame is not None:
|
||||||
self._parent.submix_frame.destroy()
|
self.parent.submix_frame.teardown()
|
||||||
self._parent.nav_frame.show_submix()
|
self.parent.nav_frame.show_submix()
|
||||||
for j, var in enumerate(self._selected_bus):
|
for j, var in enumerate(self._selected_bus):
|
||||||
var.set(True if i == j else False)
|
var.set(True if i == j else False)
|
||||||
|
|
||||||
def load_theme(self, theme):
|
def load_theme(self, theme):
|
||||||
sv_ttk.set_theme(theme)
|
sv_ttk.set_theme(theme)
|
||||||
self.configuration_app["theme"]["mode"] = theme
|
self.app_config["theme"]["mode"] = theme
|
||||||
self.menu_themes.entryconfig(
|
self.menu_themes.entryconfig(
|
||||||
0,
|
0,
|
||||||
state=f"{'disabled' if theme == 'light' else 'normal'}",
|
state=f"{'disabled' if theme == 'light' else 'normal'}",
|
||||||
@ -268,50 +265,42 @@ class Menus(tk.Menu):
|
|||||||
]
|
]
|
||||||
|
|
||||||
opts = {}
|
opts = {}
|
||||||
opts |= self.configuration_vban[f"connection-{i+1}"]
|
opts |= self.vban_config[f"connection-{i+1}"]
|
||||||
kind_id = opts.pop("kind")
|
kind_id = opts.pop("kind")
|
||||||
self.vban_conns[i] = vbancmd.connect(kind_id, **opts)
|
self.vban = vbancmd.connect(kind_id, **opts)
|
||||||
# login to vban interface
|
# login to vban interface
|
||||||
self.vban_conns[i].login()
|
self.vban.login()
|
||||||
# destroy the current App frames
|
# destroy the current App frames
|
||||||
self._parent._destroy_top_level_frames()
|
self.parent._destroy_top_level_frames()
|
||||||
_base_vals.vban_connected = True
|
_base_values.vban_connected = True
|
||||||
# build new app frames according to a kind
|
# build new app frames according to a kind
|
||||||
kind = kind_get(kind_id)
|
kind = kind_get(kind_id)
|
||||||
self._parent._make_app(kind, self.vban_conns[i])
|
self.parent.build_app(kind, self.vban)
|
||||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||||
target_menu.entryconfig(0, state="disabled")
|
target_menu.entryconfig(0, state="disabled")
|
||||||
target_menu.entryconfig(1, state="normal")
|
target_menu.entryconfig(1, state="normal")
|
||||||
self.menu_layout.entryconfig(
|
self.menu_layout.entryconfig(
|
||||||
2, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
0, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def vban_disconnect(self, i):
|
def vban_disconnect(self, i):
|
||||||
# destroy the current App frames
|
# destroy the current App frames
|
||||||
self._parent._destroy_top_level_frames()
|
self.parent._destroy_top_level_frames()
|
||||||
_base_vals.vban_connected = False
|
_base_values.vban_connected = False
|
||||||
# logout of vban interface
|
# logout of vban interface
|
||||||
i_to_close = self.vban_conns[i]
|
self.vban.logout()
|
||||||
self.vban_conns[i] = None
|
|
||||||
i_to_close.logout()
|
|
||||||
# build new app frames according to a kind
|
# build new app frames according to a kind
|
||||||
kind = kind_get(self._vmr.type)
|
kind = kind_get(self.vmr.type)
|
||||||
self._parent._make_app(kind, None)
|
self.parent.build_app(kind, None)
|
||||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||||
target_menu.entryconfig(0, state="normal")
|
target_menu.entryconfig(0, state="normal")
|
||||||
target_menu.entryconfig(1, state="disabled")
|
target_menu.entryconfig(1, state="disabled")
|
||||||
self.menu_layout.entryconfig(
|
self.menu_layout.entryconfig(
|
||||||
2, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
0, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.after(15000, self.enable_vban_menus)
|
self.after(15000, self.enable_vban_menus)
|
||||||
|
|
||||||
def enable_vban_menus(self):
|
|
||||||
[
|
|
||||||
self.menu_vban.entryconfig(j, state="normal")
|
|
||||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
|
||||||
]
|
|
||||||
|
|
||||||
def documentation(self):
|
def documentation(self):
|
||||||
webbrowser.open_new(r"https://voicemeeter.com/")
|
webbrowser.open_new(r"https://voicemeeter.com/")
|
||||||
|
|
||||||
|
@ -1,156 +1,87 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from .channels import ChannelFrame
|
from . import builders
|
||||||
|
from .data import _configuration
|
||||||
from .gainlayer import SubMixFrame
|
from .gainlayer import SubMixFrame
|
||||||
from .data import _base_vals
|
|
||||||
|
|
||||||
|
|
||||||
class Navigation(ttk.Frame):
|
class Navigation(ttk.Frame):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._parent = parent
|
self.parent = parent
|
||||||
self.s = parent.styletable
|
self.grid(row=0, column=3, sticky=(tk.W, tk.E))
|
||||||
|
self.styletable = self.parent.styletable
|
||||||
|
|
||||||
self.submix = tk.BooleanVar()
|
self.builder = builders.NavigationFrameBuilder(self)
|
||||||
self.channel = tk.BooleanVar()
|
self.builder.setup()
|
||||||
self.extend = tk.BooleanVar()
|
self.builder.create_submix_button()
|
||||||
self.info = tk.BooleanVar()
|
self.builder.create_channel_button()
|
||||||
|
self.builder.create_extend_button()
|
||||||
|
self.builder.create_info_button()
|
||||||
|
self.builder.grid_configure()
|
||||||
|
|
||||||
self.channel_text = tk.StringVar()
|
self.mainframebuilder = builders.MainFrameBuilder(self.parent)
|
||||||
self.channel_text.set(parent.channel_frame.identifier.upper())
|
|
||||||
self.extend_text = tk.StringVar()
|
|
||||||
self.extend_text.set("EXTEND")
|
|
||||||
self.info_text = tk.StringVar()
|
|
||||||
self._parent.submix_frame = None
|
|
||||||
|
|
||||||
self._make_widgets()
|
|
||||||
|
|
||||||
self.col_row_configure()
|
|
||||||
|
|
||||||
def _make_widgets(self):
|
|
||||||
"""Creates the navigation buttons"""
|
|
||||||
self.submix_button = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
text="SUBMIX",
|
|
||||||
command=self.show_submix,
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Submix.TButton'}",
|
|
||||||
variable=self.submix,
|
|
||||||
)
|
|
||||||
self.channel_button = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
textvariable=self.channel_text,
|
|
||||||
command=self.switch_channel,
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Channel.TButton'}",
|
|
||||||
variable=self.channel,
|
|
||||||
)
|
|
||||||
self.extend_button = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
textvariable=self.extend_text,
|
|
||||||
command=self.extend_frame,
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Extend.TButton'}",
|
|
||||||
variable=self.extend,
|
|
||||||
)
|
|
||||||
self.info_button = ttk.Checkbutton(
|
|
||||||
self,
|
|
||||||
textvariable=self.info_text,
|
|
||||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Rec.TButton'}",
|
|
||||||
variable=self.info,
|
|
||||||
)
|
|
||||||
self.info_button["state"] = "active"
|
|
||||||
|
|
||||||
""" Position navigation buttons """
|
|
||||||
self.submix_button.grid(column=0, row=0)
|
|
||||||
self.channel_button.grid(column=0, row=1, rowspan=1)
|
|
||||||
self.extend_button.grid(column=0, row=2)
|
|
||||||
self.info_button.grid(column=0, row=3)
|
|
||||||
|
|
||||||
if self._parent.kind.name != "Potato":
|
|
||||||
self.submix_button["state"] = "disabled"
|
|
||||||
|
|
||||||
def show_submix(self):
|
def show_submix(self):
|
||||||
if self.submix.get():
|
if self.submix.get():
|
||||||
if _base_vals.extends_horizontal:
|
self.parent.submix_frame = SubMixFrame(self.parent)
|
||||||
self._parent.submix_frame = SubMixFrame(self._parent)
|
|
||||||
self._parent.submix_frame.grid(row=0, column=2)
|
|
||||||
if self._parent.bus_frame:
|
|
||||||
self._parent.bus_frame.grid_remove()
|
|
||||||
else:
|
else:
|
||||||
self._parent.submix_frame = SubMixFrame(self._parent)
|
if _configuration.extends_horizontal:
|
||||||
self._parent.submix_frame.grid(row=2, column=0, sticky=(tk.W))
|
self.parent.submix_frame.teardown()
|
||||||
if self._parent.bus_frame:
|
if self.parent.bus_frame:
|
||||||
self._parent.bus_frame.grid_remove()
|
self.parent.bus_frame.grid()
|
||||||
else:
|
else:
|
||||||
if _base_vals.extends_horizontal:
|
self.parent.columnconfigure(1, weight=0)
|
||||||
self._parent.submix_frame.destroy()
|
|
||||||
if self._parent.bus_frame:
|
|
||||||
self._parent.bus_frame.grid()
|
|
||||||
else:
|
else:
|
||||||
self._parent.columnconfigure(1, weight=0)
|
self.parent.submix_frame.teardown()
|
||||||
|
if self.parent.bus_frame:
|
||||||
|
self.parent.bus_frame.grid()
|
||||||
else:
|
else:
|
||||||
self._parent.submix_frame.destroy()
|
self.parent.rowconfigure(2, weight=0, minsize=0)
|
||||||
if self._parent.bus_frame:
|
|
||||||
self._parent.bus_frame.grid()
|
|
||||||
else:
|
|
||||||
self._parent.rowconfigure(2, weight=0, minsize=0)
|
|
||||||
|
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"Submix.TButton",
|
f"Submix.TButton",
|
||||||
background=f'{"purple" if self.submix.get() else "white"}',
|
background=f'{"purple" if self.submix.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def switch_channel(self):
|
def switch_channel(self):
|
||||||
if self.channel_text.get() == "STRIP":
|
if self.channel_text.get() == "STRIP":
|
||||||
self._parent.bus_frame = ChannelFrame.make_buses(self._parent)
|
self.mainframebuilder.create_channelframe("bus")
|
||||||
self._parent.bus_frame.grid(row=0, column=0)
|
self.parent.strip_frame.teardown()
|
||||||
self._parent.channel_frame.destroy()
|
|
||||||
else:
|
else:
|
||||||
self._parent.channel_frame = ChannelFrame.make_strips(self._parent)
|
self.mainframebuilder.create_channelframe("strip")
|
||||||
self._parent.channel_frame.grid(row=0, column=0)
|
self.parent.bus_frame.teardown()
|
||||||
self._parent.bus_frame.destroy()
|
|
||||||
|
|
||||||
self.extend_button["state"] = (
|
self.extend_button["state"] = (
|
||||||
"disabled" if self.channel_text.get() == "STRIP" else "normal"
|
"disabled" if self.channel_text.get() == "STRIP" else "normal"
|
||||||
)
|
)
|
||||||
[frame.destroy() for frame in self._parent.configframes]
|
[frame.teardown() for frame in self.parent.configframes]
|
||||||
self.channel_text.set("BUS" if self.channel_text.get() == "STRIP" else "STRIP")
|
self.channel_text.set("BUS" if self.channel_text.get() == "STRIP" else "STRIP")
|
||||||
|
|
||||||
def extend_frame(self):
|
def extend_frame(self):
|
||||||
|
_configuration.extended = self.extend.get()
|
||||||
if self.extend.get():
|
if self.extend.get():
|
||||||
self.channel_button["state"] = "disabled"
|
self.channel_button["state"] = "disabled"
|
||||||
self._parent.bus_frame = ChannelFrame.make_buses(self._parent)
|
self.mainframebuilder.create_channelframe("bus")
|
||||||
if _base_vals.extends_horizontal:
|
|
||||||
self._parent.bus_frame.grid(row=0, column=2)
|
|
||||||
else:
|
|
||||||
self._parent.bus_frame.grid(row=2, column=0, sticky=(tk.W))
|
|
||||||
else:
|
else:
|
||||||
[
|
[
|
||||||
frame.destroy()
|
frame.teardown()
|
||||||
for frame in self._parent.configframes
|
for frame in self.parent.configframes
|
||||||
if "!busconfig" in str(frame)
|
if "!busconfig" in str(frame)
|
||||||
]
|
]
|
||||||
self._parent.bus_frame.destroy()
|
self.parent.bus_frame.teardown()
|
||||||
self._parent.bus_frame = None
|
self.parent.bus_frame = None
|
||||||
self.channel_button["state"] = "normal"
|
self.channel_button["state"] = "normal"
|
||||||
|
|
||||||
if self._parent.submix_frame:
|
if self.parent.submix_frame:
|
||||||
self._parent.submix_frame.destroy()
|
self.parent.submix_frame.teardown()
|
||||||
self.submix.set(False)
|
self.submix.set(False)
|
||||||
if not _base_vals.using_theme:
|
if not _configuration.themes_enabled:
|
||||||
self.s.configure(
|
self.styletable.configure(
|
||||||
f"Submix.TButton",
|
f"Submix.TButton",
|
||||||
background=f'{"purple" if self.submix.get() else "white"}',
|
background=f'{"purple" if self.submix.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")
|
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")
|
||||||
|
|
||||||
def col_row_configure(self):
|
|
||||||
[
|
|
||||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
|
||||||
for child in self.winfo_children()
|
|
||||||
if isinstance(child, ttk.Checkbutton)
|
|
||||||
]
|
|
||||||
|
|
||||||
self.rowconfigure(1, minsize=self._parent.channel_frame.height - 18)
|
|
||||||
self.grid(sticky=(tk.N))
|
|
||||||
|
32
vmcompact/subject.py
Normal file
32
vmcompact/subject.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
class Subject:
|
||||||
|
def __init__(self):
|
||||||
|
"""list of current observers"""
|
||||||
|
|
||||||
|
self._observables = []
|
||||||
|
|
||||||
|
def notify(self, modifier=None):
|
||||||
|
"""Alert the observers"""
|
||||||
|
|
||||||
|
for observer in self._observables:
|
||||||
|
observer.update()
|
||||||
|
|
||||||
|
def add(self, observer):
|
||||||
|
"""adds an observer to observables"""
|
||||||
|
|
||||||
|
if observer not in self._observables:
|
||||||
|
self._observables.append(observer)
|
||||||
|
|
||||||
|
def remove(self, observer):
|
||||||
|
"""removes an observer from observables"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._observables.remove(observer)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get(self) -> list:
|
||||||
|
"""returns the current observables"""
|
||||||
|
return self._observables
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self._observables.clear()
|
Loading…
Reference in New Issue
Block a user