mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2026-04-08 17:03:32 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5975f0772 | |||
| 59d2a95ec4 | |||
| 4bae1e1d15 | |||
| 1e3751b19f | |||
| 2ec1c74b7d | |||
| 0a19e28370 | |||
| 5ab1fd7102 | |||
| d896193ade | |||
| 8c0c31dc0d | |||
| c8b3e9fc33 | |||
| d4358bf7d3 | |||
| 194b95d67b | |||
| 0f734e87b7 | |||
| 944ef9ca1c | |||
| fc20bb0c1e | |||
| 5ccc2a6dab | |||
| cfc1279f6c | |||
| d4df11f62d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -131,3 +131,5 @@ dmypy.json
|
|||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
.vscode/
|
||||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -7,7 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- [ ] Add support for forest theme (should be coming soon)
|
- [ ] Add support for forest theme (if rbende adds it to pypi)
|
||||||
|
|
||||||
|
## [1.8.0] - 2023-06-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Ability to toggle the navigation frame. This may also be set in app.toml, check example config.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- xpadding added to channel labelframes. This may also be configured through app.toml.
|
||||||
|
- During startup of the app there is now a twelve second grace period before parameter updates begin if the GUI was not previously launched. This is aimed at removing the stutter (due to VM engine startup) on initial launch. Be mindful of this if changing settings on the base Voicemeeter app. After the grace period all updates continue as normal.
|
||||||
|
|
||||||
|
- dependency updates:
|
||||||
|
- sv_ttk updated to v2.5.1.
|
||||||
|
- voicemeeter-api updated to v2.0.2.
|
||||||
|
|
||||||
|
## [1.7.0] - 2023-06-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- There are changes to how some parameters must be set in user toml configs.
|
||||||
|
- use `comp.knob` to set a strip comp slider.
|
||||||
|
- use `gate.knob` to set a strip gate slider.
|
||||||
|
- use `eq.on` to set a bus eq.on button.
|
||||||
|
- use `eq.ab` to set a bus eq.ab button.
|
||||||
|
|
||||||
|
Check example configs.
|
||||||
|
|
||||||
|
- `configs` directory may now be located in one of the following locations:
|
||||||
|
- \<current working directory>/configs/
|
||||||
|
- \<user home directory>/.configs/vm-compact/configs/
|
||||||
|
- \<user home directory>/Documents/Voicemeeter/configs/
|
||||||
|
|
||||||
|
- dependency updates:
|
||||||
|
- sv_ttk updated to v2.4.5.
|
||||||
|
- voicemeeter-api updated to v2.0.1.
|
||||||
|
- vban-cmd updated to v2.0.0.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A number of changes that reduce the amount of api calls being made.
|
||||||
|
|
||||||
## [1.6.0] - 2022-09-29
|
## [1.6.0] - 2022-09-29
|
||||||
|
|
||||||
|
|||||||
@@ -3,19 +3,23 @@
|
|||||||
# config="example"
|
# config="example"
|
||||||
# load with themes enabled? set the default mode
|
# load with themes enabled? set the default mode
|
||||||
[theme]
|
[theme]
|
||||||
enabled=true
|
enabled = true
|
||||||
mode="light"
|
mode = "light"
|
||||||
# load in extended mode? if so which orientation
|
# load in extended mode? if so which orientation
|
||||||
[extends]
|
[extends]
|
||||||
extended=true
|
extended = true
|
||||||
extends_horizontal=true
|
extends_horizontal = true
|
||||||
# default dimensions for channel label frames
|
# default dimensions for channel label frames
|
||||||
[channel]
|
[channel]
|
||||||
width=80
|
width = 80
|
||||||
height=130
|
height = 130
|
||||||
|
xpadding = 2
|
||||||
# size of a single mouse wheel scroll step
|
# size of a single mouse wheel scroll step
|
||||||
[mwscroll_step]
|
[mwscroll_step]
|
||||||
size=3
|
size = 3
|
||||||
# default submix bus
|
# default submix bus
|
||||||
[submixes]
|
[submixes]
|
||||||
default=0
|
default = 0
|
||||||
|
# show the navigation frame?
|
||||||
|
[navigation]
|
||||||
|
show = true
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
label = "PhysStrip0"
|
label = "PhysStrip0"
|
||||||
A1 = true
|
A1 = true
|
||||||
gain = -8.8
|
gain = -8.8
|
||||||
comp = 3.2
|
comp.knob = 3.2
|
||||||
|
|
||||||
[strip-1]
|
[strip-1]
|
||||||
label = "PhysStrip1"
|
label = "PhysStrip1"
|
||||||
B1 = true
|
B1 = true
|
||||||
gate = 4.1
|
gate.knob = 4.1
|
||||||
|
|
||||||
[strip-2]
|
[strip-2]
|
||||||
label = "PhysStrip2"
|
label = "PhysStrip2"
|
||||||
@@ -34,12 +34,12 @@ mono = true
|
|||||||
|
|
||||||
[bus-2]
|
[bus-2]
|
||||||
label = "PhysBus2"
|
label = "PhysBus2"
|
||||||
eq = true
|
eq.on = true
|
||||||
mode = "composite"
|
mode = "composite"
|
||||||
|
|
||||||
[bus-3]
|
[bus-3]
|
||||||
label = "VirtBus0"
|
label = "VirtBus0"
|
||||||
eq_ab = true
|
eq.ab = true
|
||||||
mode = "upmix61"
|
mode = "upmix61"
|
||||||
|
|
||||||
[bus-4]
|
[bus-4]
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
label = "PhysStrip0"
|
label = "PhysStrip0"
|
||||||
A1 = true
|
A1 = true
|
||||||
gain = -8.8
|
gain = -8.8
|
||||||
comp = 3.2
|
comp.knob = 3.2
|
||||||
|
|
||||||
[strip-1]
|
[strip-1]
|
||||||
label = "PhysStrip1"
|
label = "PhysStrip1"
|
||||||
B1 = true
|
B1 = true
|
||||||
gate = 4.1
|
gate.knob = 4.1
|
||||||
|
|
||||||
[strip-2]
|
[strip-2]
|
||||||
label = "PhysStrip2"
|
label = "PhysStrip2"
|
||||||
@@ -50,7 +50,7 @@ mono = true
|
|||||||
|
|
||||||
[bus-2]
|
[bus-2]
|
||||||
label = "PhysBus2"
|
label = "PhysBus2"
|
||||||
eq = true
|
eq.on = true
|
||||||
|
|
||||||
[bus-3]
|
[bus-3]
|
||||||
label = "PhysBus3"
|
label = "PhysBus3"
|
||||||
@@ -62,7 +62,7 @@ mode = "composite"
|
|||||||
|
|
||||||
[bus-5]
|
[bus-5]
|
||||||
label = "VirtBus0"
|
label = "VirtBus0"
|
||||||
eq_ab = true
|
eq.ab = true
|
||||||
|
|
||||||
[bus-6]
|
[bus-6]
|
||||||
label = "VirtBus1"
|
label = "VirtBus1"
|
||||||
|
|||||||
49
poetry.lock
generated
49
poetry.lock
generated
@@ -1,6 +1,6 @@
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "22.10.0"
|
version = "22.12.0"
|
||||||
description = "The uncompromising code formatter."
|
description = "The uncompromising code formatter."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -32,23 +32,37 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.5"
|
version = "0.4.6"
|
||||||
description = "Cross-platform colored terminal text."
|
description = "Cross-platform colored terminal text."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "isort"
|
||||||
|
version = "5.12.0"
|
||||||
|
description = "A Python utility / library to sort Python imports."
|
||||||
|
category = "dev"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
colors = ["colorama (>=0.4.3)"]
|
||||||
|
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||||
|
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||||
|
plugins = ["setuptools"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy-extensions"
|
name = "mypy-extensions"
|
||||||
version = "0.4.3"
|
version = "1.0.0"
|
||||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
description = "Type system extensions for programs checked with the mypy type checker."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = ">=3.5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "pathspec"
|
||||||
version = "0.10.1"
|
version = "0.11.1"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Utility library for gitignore style pattern matching of file paths."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -56,23 +70,23 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "2.5.2"
|
version = "3.8.0"
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"]
|
||||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest (>=7.3.1)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sv-ttk"
|
name = "sv-ttk"
|
||||||
version = "2.0"
|
version = "2.5.1"
|
||||||
description = "A gorgeous theme for Tkinter that looks like Windows 11"
|
description = "A gorgeous theme for Tkinter, based on Windows 11's UI"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.4"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
@@ -84,7 +98,7 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cmd"
|
name = "vban-cmd"
|
||||||
version = "1.8.1"
|
version = "2.0.0"
|
||||||
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -95,7 +109,7 @@ tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voicemeeter-api"
|
name = "voicemeeter-api"
|
||||||
version = "0.8.4"
|
version = "2.0.2"
|
||||||
description = "A Python wrapper for the Voiceemeter API"
|
description = "A Python wrapper for the Voiceemeter API"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
@@ -107,12 +121,13 @@ tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "ba702e1c74c507e75070c2c58bffe5a7787d1dfa1b4ff7b5cedce7374871f7db"
|
content-hash = "3a59de3a76e4c0ca11c0166750fa1af7d7c887750f855b48c45359068ef04798"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
black = []
|
black = []
|
||||||
click = []
|
click = []
|
||||||
colorama = []
|
colorama = []
|
||||||
|
isort = []
|
||||||
mypy-extensions = []
|
mypy-extensions = []
|
||||||
pathspec = []
|
pathspec = []
|
||||||
platformdirs = []
|
platformdirs = []
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "voicemeeter-compact"
|
name = "voicemeeter-compact"
|
||||||
version = "1.6.5"
|
version = "1.8.1"
|
||||||
description = "A Compact Voicemeeter Remote App"
|
description = "A Compact Voicemeeter Remote App"
|
||||||
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/onyx-and-iris/voicemeeter-compact"
|
repository = "https://github.com/onyx-and-iris/voicemeeter-compact"
|
||||||
|
|
||||||
packages = [
|
packages = [{ include = "vmcompact" }]
|
||||||
{ include = "vmcompact" },
|
|
||||||
]
|
|
||||||
include = ["vmcompact/img/cat.ico"]
|
include = ["vmcompact/img/cat.ico"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
sv-ttk = "^2.0"
|
sv-ttk = "^2.5.1"
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||||
voicemeeter-api = "^0.8.4"
|
voicemeeter-api = "^2.0.2"
|
||||||
vban-cmd = "^1.8.1"
|
vban-cmd = "^2.0.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = {version = "^22.6.0", allow-prereleases = true}
|
black = { version = "^22.6.0", allow-prereleases = true }
|
||||||
|
isort = "^5.12.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from functools import cached_property
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
from .builders import MainFrameBuilder
|
from .builders import MainFrameBuilder
|
||||||
|
from .configurations import loader
|
||||||
from .data import _base_values, _configuration, _kinds_all
|
from .data import _base_values, _configuration, _kinds_all
|
||||||
from .errors import VMCompactErrors
|
from .errors import VMCompactError
|
||||||
from .menu import Menus
|
from .menu import Menus
|
||||||
from .subject import Subject
|
from .subject import Subject
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class App(tk.Tk):
|
class App(tk.Tk):
|
||||||
"""App mainframe"""
|
"""App mainframe"""
|
||||||
@@ -32,15 +37,17 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
def __init__(self, vmr):
|
def __init__(self, vmr):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self._vmr = vmr
|
self._vmr = vmr
|
||||||
self._vmr.event.ldirty = True
|
self._vmr.event.add(["pdirty", "ldirty"])
|
||||||
self._vmr.event.remove(["mdirty", "midi"])
|
self.after(12000 if self._vmr.gui.launched_by_api else 1, self.start_updates)
|
||||||
|
self._vmr.init_thread()
|
||||||
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
|
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
|
||||||
if icon_path.is_file():
|
if icon_path.is_file():
|
||||||
self.iconbitmap(str(icon_path))
|
self.iconbitmap(str(icon_path))
|
||||||
self.minsize(275, False)
|
self.minsize(275, False)
|
||||||
self.subject = Subject()
|
self.subject = Subject()
|
||||||
|
self._configs = None
|
||||||
self["menu"] = Menus(self, vmr)
|
self["menu"] = Menus(self, vmr)
|
||||||
self.styletable = ttk.Style()
|
self.styletable = ttk.Style()
|
||||||
if _configuration.config:
|
if _configuration.config:
|
||||||
@@ -51,6 +58,15 @@ class App(tk.Tk):
|
|||||||
self.drag_id = ""
|
self.drag_id = ""
|
||||||
self.bind("<Configure>", self.dragging)
|
self.bind("<Configure>", self.dragging)
|
||||||
|
|
||||||
|
def start_updates(self):
|
||||||
|
self.logger.debug("updates started")
|
||||||
|
_base_values.run_update = True
|
||||||
|
if self._vmr.gui.launched_by_api:
|
||||||
|
self.on_pdirty()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{type(self).__name__}App"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the current interface"""
|
||||||
@@ -75,8 +91,8 @@ class App(tk.Tk):
|
|||||||
if kind:
|
if kind:
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
|
|
||||||
# register app as observer
|
# register event callbacks
|
||||||
self.target.subject.add(self)
|
self.target.subject.add([self.on_pdirty, self.on_ldirty])
|
||||||
|
|
||||||
self.bus_frame = None
|
self.bus_frame = None
|
||||||
self.submix_frame = None
|
self.submix_frame = None
|
||||||
@@ -91,12 +107,12 @@ class App(tk.Tk):
|
|||||||
if self.kind.name == "potato":
|
if self.kind.name == "potato":
|
||||||
self.builder.create_banner()
|
self.builder.create_banner()
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_pdirty(self):
|
||||||
"""called whenever notified of update"""
|
if _base_values.run_update:
|
||||||
|
|
||||||
if subject == "pdirty" and _base_values.run_update:
|
|
||||||
self.after(1, self.subject.notify, "pdirty")
|
self.after(1, self.subject.notify, "pdirty")
|
||||||
elif subject == "ldirty" and not _base_values.dragging:
|
|
||||||
|
def on_ldirty(self):
|
||||||
|
if not _base_values.dragging:
|
||||||
self.after(1, self.subject.notify, "ldirty")
|
self.after(1, self.subject.notify, "ldirty")
|
||||||
|
|
||||||
def _destroy_top_level_frames(self):
|
def _destroy_top_level_frames(self):
|
||||||
@@ -127,6 +143,11 @@ class App(tk.Tk):
|
|||||||
self.drag_id = ""
|
self.drag_id = ""
|
||||||
_base_values.dragging = False
|
_base_values.dragging = False
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def userconfigs(self):
|
||||||
|
self._configs = loader(self.kind.name)
|
||||||
|
return self._configs
|
||||||
|
|
||||||
|
|
||||||
_apps = {kind.name: App.make(kind) for kind in _kinds_all}
|
_apps = {kind.name: App.make(kind) for kind in _kinds_all}
|
||||||
|
|
||||||
@@ -137,5 +158,5 @@ def connect(kind_id: str, vmr) -> App:
|
|||||||
try:
|
try:
|
||||||
VMMIN_cls = _apps[kind_id]
|
VMMIN_cls = _apps[kind_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise VMCompactErrors(f"Invalid kind: {kind_id}")
|
raise VMCompactError(f"Invalid kind: {kind_id}")
|
||||||
return VMMIN_cls(vmr)
|
return VMMIN_cls(vmr)
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
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.parent.subject.add(self)
|
||||||
self.submix.set(self.target.bus[_configuration.submixes].label)
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
self.submix = tk.StringVar(value=self.target.bus[_configuration.submixes].label)
|
||||||
|
|
||||||
self.label = ttk.Label(
|
self.label = ttk.Label(
|
||||||
self,
|
self,
|
||||||
@@ -17,19 +21,15 @@ class Banner(ttk.Frame):
|
|||||||
)
|
)
|
||||||
self.label.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
|
self.label.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||||
|
|
||||||
self.upd_submix()
|
|
||||||
|
|
||||||
@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 upd_submix(self):
|
def on_update(self, subject):
|
||||||
self.after(1, self.upd_submix_step)
|
if subject == "submix":
|
||||||
|
if not _base_values.dragging:
|
||||||
def upd_submix_step(self):
|
self.logger.debug("checking submix for banner")
|
||||||
if not _base_values.dragging:
|
self.submix.set(self.target.bus[_configuration.submixes].label)
|
||||||
self.submix.set(self.target.bus[_configuration.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)
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ from .config import BusConfig, StripConfig
|
|||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
from .navigation import Navigation
|
from .navigation import Navigation
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AbstractBuilder(abc.ABC):
|
class AbstractBuilder(abc.ABC):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -28,11 +30,10 @@ class AbstractBuilder(abc.ABC):
|
|||||||
class MainFrameBuilder(AbstractBuilder):
|
class MainFrameBuilder(AbstractBuilder):
|
||||||
"""Responsible for building the frames that sit directly on the mainframe"""
|
"""Responsible for building the frames that sit directly on the mainframe"""
|
||||||
|
|
||||||
logger = logging.getLogger("builders.mainframebuilder")
|
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.kind = app.kind
|
self.kind = app.kind
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.app.title(
|
self.app.title(
|
||||||
@@ -194,9 +195,9 @@ class NavigationFrameBuilder(AbstractBuilder):
|
|||||||
if isinstance(child, ttk.Checkbutton)
|
if isinstance(child, ttk.Checkbutton)
|
||||||
]
|
]
|
||||||
if _configuration.themes_enabled:
|
if _configuration.themes_enabled:
|
||||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height)
|
self.navframe.rowconfigure(1, minsize=_configuration.channel_height)
|
||||||
else:
|
else:
|
||||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height + 10)
|
self.navframe.rowconfigure(1, minsize=_configuration.channel_height + 10)
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
pass
|
pass
|
||||||
@@ -242,7 +243,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
orient="vertical",
|
orient="vertical",
|
||||||
variable=self.labelframe.gain,
|
variable=self.labelframe.gain,
|
||||||
command=self.labelframe.scale_callback,
|
command=self.labelframe.scale_callback,
|
||||||
length=_configuration.level_height,
|
length=_configuration.channel_height,
|
||||||
)
|
)
|
||||||
self.scale.grid(column=1, row=0)
|
self.scale.grid(column=1, row=0)
|
||||||
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
||||||
@@ -323,7 +324,7 @@ class ChannelConfigFrameBuilder(AbstractBuilder):
|
|||||||
]
|
]
|
||||||
self.configframe.grid(sticky=(tk.W))
|
self.configframe.grid(sticky=(tk.W))
|
||||||
[
|
[
|
||||||
self.configframe.columnconfigure(i, minsize=_configuration.level_width)
|
self.configframe.columnconfigure(i, minsize=_configuration.channel_width)
|
||||||
for i in range(self.configframe.phys_out + self.configframe.virt_out)
|
for i in range(self.configframe.phys_out + self.configframe.virt_out)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -336,7 +337,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
self.configframe.slider_params = ("audibility",)
|
self.configframe.slider_params = ("audibility",)
|
||||||
self.configframe.slider_vars = (tk.DoubleVar(),)
|
self.configframe.slider_vars = (tk.DoubleVar(),)
|
||||||
else:
|
else:
|
||||||
self.configframe.slider_params = ("comp", "gate", "limit")
|
self.configframe.slider_params = ("comp.knob", "gate.knob", "limit")
|
||||||
self.configframe.slider_vars = [
|
self.configframe.slider_vars = [
|
||||||
tk.DoubleVar() for _ in self.configframe.slider_params
|
tk.DoubleVar() for _ in self.configframe.slider_params
|
||||||
]
|
]
|
||||||
@@ -391,18 +392,18 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
from_=0.0,
|
from_=0.0,
|
||||||
to=10.0,
|
to=10.0,
|
||||||
orient="horizontal",
|
orient="horizontal",
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("comp")
|
self.configframe.slider_params.index("comp.knob")
|
||||||
],
|
],
|
||||||
command=partial(self.configframe.scale_callback, "comp"),
|
command=partial(self.configframe.scale_callback, "comp.knob"),
|
||||||
)
|
)
|
||||||
comp_scale.bind(
|
comp_scale.bind(
|
||||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "comp", 0)
|
"<Double-Button-1>", partial(self.configframe.reset_scale, "comp.knob", 0)
|
||||||
)
|
)
|
||||||
comp_scale.bind("<Button-1>", self.configframe.scale_press)
|
comp_scale.bind("<Button-1>", self.configframe.scale_press)
|
||||||
comp_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
comp_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
||||||
comp_scale.bind("<Enter>", partial(self.configframe.scale_enter, "comp"))
|
comp_scale.bind("<Enter>", partial(self.configframe.scale_enter, "comp.knob"))
|
||||||
comp_scale.bind("<Leave>", self.configframe.scale_leave)
|
comp_scale.bind("<Leave>", self.configframe.scale_leave)
|
||||||
|
|
||||||
comp_label.grid(column=0, row=0)
|
comp_label.grid(column=0, row=0)
|
||||||
@@ -415,18 +416,18 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
from_=0.0,
|
from_=0.0,
|
||||||
to=10.0,
|
to=10.0,
|
||||||
orient="horizontal",
|
orient="horizontal",
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("gate")
|
self.configframe.slider_params.index("gate.knob")
|
||||||
],
|
],
|
||||||
command=partial(self.configframe.scale_callback, "gate"),
|
command=partial(self.configframe.scale_callback, "gate.knob"),
|
||||||
)
|
)
|
||||||
gate_scale.bind(
|
gate_scale.bind(
|
||||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "gate", 0)
|
"<Double-Button-1>", partial(self.configframe.reset_scale, "gate.knob", 0)
|
||||||
)
|
)
|
||||||
gate_scale.bind("<Button-1>", self.configframe.scale_press)
|
gate_scale.bind("<Button-1>", self.configframe.scale_press)
|
||||||
gate_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
gate_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
||||||
gate_scale.bind("<Enter>", partial(self.configframe.scale_enter, "gate"))
|
gate_scale.bind("<Enter>", partial(self.configframe.scale_enter, "gate.knob"))
|
||||||
gate_scale.bind("<Leave>", self.configframe.scale_leave)
|
gate_scale.bind("<Leave>", self.configframe.scale_leave)
|
||||||
|
|
||||||
gate_label.grid(column=2, row=0)
|
gate_label.grid(column=2, row=0)
|
||||||
@@ -439,7 +440,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
from_=-40,
|
from_=-40,
|
||||||
to=12,
|
to=12,
|
||||||
orient="horizontal",
|
orient="horizontal",
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("limit")
|
self.configframe.slider_params.index("limit")
|
||||||
],
|
],
|
||||||
@@ -463,7 +464,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
from_=0.0,
|
from_=0.0,
|
||||||
to=10.0,
|
to=10.0,
|
||||||
orient="horizontal",
|
orient="horizontal",
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("audibility")
|
self.configframe.slider_params.index("audibility")
|
||||||
],
|
],
|
||||||
@@ -563,7 +564,7 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
}
|
}
|
||||||
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self.configframe.params = ("mono", "eq", "eq_ab")
|
self.configframe.params = ("mono", "eq.on", "eq.ab")
|
||||||
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params]
|
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params]
|
||||||
self.configframe.bus_mode_label_text = tk.StringVar(
|
self.configframe.bus_mode_label_text = tk.StringVar(
|
||||||
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
|
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from math import log
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from . import builders
|
from . import builders
|
||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ChannelLabelFrame(ttk.LabelFrame):
|
class ChannelLabelFrame(ttk.LabelFrame):
|
||||||
"""Base class for a single channel"""
|
"""Base class for a single channel"""
|
||||||
@@ -14,6 +16,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.styletable = self.parent.parent.styletable
|
self.styletable = self.parent.parent.styletable
|
||||||
|
|
||||||
self.builder = builders.ChannelLabelFrameBuilder(self, index, id)
|
self.builder = builders.ChannelLabelFrameBuilder(self, index, id)
|
||||||
@@ -40,18 +43,21 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
return self.parent.target
|
return self.parent.target
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if hasattr(self.target, param):
|
try:
|
||||||
return getattr(self.target, param)
|
return getattr(self.target, param)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger(f"{type(e).__name__}: {e}")
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if hasattr(self.target, param):
|
if param in dir(self.target): # avoid calling getattr (with hasattr)
|
||||||
setattr(self.target, param, value)
|
setattr(self.target, param, value)
|
||||||
|
|
||||||
def scale_callback(self, *args):
|
def scale_callback(self, *args):
|
||||||
"""callback function for scale widget"""
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
self.setter("gain", self.gain.get())
|
val = round(self.gain.get(), 1)
|
||||||
self.gainlabel.set(round(self.gain.get(), 1))
|
self.setter("gain", val)
|
||||||
|
self.gainlabel.set(val)
|
||||||
|
|
||||||
def toggle_mute(self, *args):
|
def toggle_mute(self, *args):
|
||||||
self.target.mute = self.mute.get()
|
self.target.mute = self.mute.get()
|
||||||
@@ -148,7 +154,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
self.configure(text=retval)
|
self.configure(text=retval)
|
||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
self.grid(sticky=(tk.N, tk.S))
|
self.grid(padx=_configuration.channel_xpadding, 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))
|
||||||
for child in self.winfo_children()
|
for child in self.winfo_children()
|
||||||
@@ -252,7 +258,7 @@ class ChannelFrame(ttk.Frame):
|
|||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
[
|
[
|
||||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
self.columnconfigure(i, minsize=_configuration.channel_width)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
[self.rowconfigure(0, minsize=100) for i, _ in enumerate(self.labelframes)]
|
[self.rowconfigure(0, minsize=100) for i, _ in enumerate(self.labelframes)]
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import tkinter as tk
|
import logging
|
||||||
from functools import partial
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from . import builders
|
from . import builders
|
||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Config(ttk.Frame):
|
class Config(ttk.Frame):
|
||||||
def __init__(self, parent, index, _id):
|
def __init__(self, parent, index, _id):
|
||||||
@@ -12,6 +13,7 @@ class Config(ttk.Frame):
|
|||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.id = _id
|
self.id = _id
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.styletable = 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
|
||||||
@@ -29,12 +31,26 @@ class Config(ttk.Frame):
|
|||||||
return self.parent.target
|
return self.parent.target
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if hasattr(self.target, param):
|
param = param.split(".")
|
||||||
return getattr(self.target, param)
|
try:
|
||||||
|
if len(param) == 2:
|
||||||
|
target = getattr(self.target, param[0])
|
||||||
|
return getattr(target, param[1])
|
||||||
|
else:
|
||||||
|
return getattr(self.target, param[0])
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger.error(f"{type(e).__name__}: {e}")
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if hasattr(self.target, param):
|
param = param.split(".")
|
||||||
setattr(self.target, param, value)
|
try:
|
||||||
|
if len(param) == 2:
|
||||||
|
target = getattr(self.target, param[0])
|
||||||
|
setattr(target, param[1], value)
|
||||||
|
else:
|
||||||
|
setattr(self.target, param[0], value)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger(f"{type(e).__name__}: {e}")
|
||||||
|
|
||||||
def scale_press(self, *args):
|
def scale_press(self, *args):
|
||||||
self.after(1, self.remove_events)
|
self.after(1, self.remove_events)
|
||||||
@@ -66,7 +82,7 @@ class Config(ttk.Frame):
|
|||||||
"""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, round(val, 1))
|
||||||
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):
|
||||||
@@ -98,6 +114,7 @@ class StripConfig(Config):
|
|||||||
self.make_row_2()
|
self.make_row_2()
|
||||||
self.builder.grid_configure()
|
self.builder.grid_configure()
|
||||||
|
|
||||||
|
self.parent.target.clear_dirty()
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -155,6 +172,12 @@ class StripConfig(Config):
|
|||||||
self.param_vars[i].set(self.getter(param))
|
self.param_vars[i].set(self.getter(param))
|
||||||
for i, param in enumerate(self.params)
|
for i, param in enumerate(self.params)
|
||||||
]
|
]
|
||||||
|
if not _base_values.vban_connected: # slider vars not defined in RT Packet
|
||||||
|
[
|
||||||
|
self.slider_vars[i].set(self.getter(param))
|
||||||
|
for i, param in enumerate(self.slider_params)
|
||||||
|
if self.index < self.phys_in
|
||||||
|
]
|
||||||
|
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
[
|
[
|
||||||
@@ -193,6 +216,7 @@ class BusConfig(Config):
|
|||||||
self.make_row_1()
|
self.make_row_1()
|
||||||
self.builder.grid_configure()
|
self.builder.grid_configure()
|
||||||
|
|
||||||
|
self.parent.target.clear_dirty()
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -6,26 +6,32 @@ try:
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
import tomli as tomllib
|
import tomli as tomllib
|
||||||
|
|
||||||
LOGGER = logging.getLogger("configurations")
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
configuration = {}
|
configuration = {}
|
||||||
|
|
||||||
config_path = [Path.cwd() / "configs"]
|
configpaths = [
|
||||||
for path in config_path:
|
Path.cwd() / "configs",
|
||||||
if path.is_dir():
|
Path.home() / ".config" / "vm-compact" / "configs",
|
||||||
filenames = list(path.glob("*.toml"))
|
Path.home() / "Documents" / "Voicemeeter" / "configs",
|
||||||
configs = {}
|
]
|
||||||
for filename in filenames:
|
for configpath in configpaths:
|
||||||
name = filename.with_suffix("").stem
|
if configpath.is_dir():
|
||||||
try:
|
filepaths = list(configpath.glob("*.toml"))
|
||||||
with open(filename, "rb") as f:
|
if any(f.stem in ("app", "vban") for f in filepaths):
|
||||||
configs[name] = tomllib.load(f)
|
configs = {}
|
||||||
except tomllib.TOMLDecodeError:
|
for filepath in filepaths:
|
||||||
print(f"Invalid TOML config: configs/{filename.stem}")
|
filename = filepath.with_suffix("").stem
|
||||||
|
if filename in ("app", "vban"):
|
||||||
|
try:
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
configs[filename] = tomllib.load(f)
|
||||||
|
logger.info(f"configuration: {filename} loaded into memory")
|
||||||
|
except tomllib.TOMLDecodeError:
|
||||||
|
logger.error(f"Invalid TOML config: configs/{filename.stem}")
|
||||||
|
|
||||||
for name, cfg in configs.items():
|
configuration |= configs
|
||||||
LOGGER.info(f"Loaded configuration configs/{name}")
|
break
|
||||||
configuration[name] = cfg
|
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
"configs": {
|
"configs": {
|
||||||
@@ -42,6 +48,7 @@ _defaults = {
|
|||||||
"channel": {
|
"channel": {
|
||||||
"width": 80,
|
"width": 80,
|
||||||
"height": 130,
|
"height": 130,
|
||||||
|
"xpadding": 3,
|
||||||
},
|
},
|
||||||
"mwscroll_step": {
|
"mwscroll_step": {
|
||||||
"size": 3,
|
"size": 3,
|
||||||
@@ -49,10 +56,16 @@ _defaults = {
|
|||||||
"submixes": {
|
"submixes": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
},
|
},
|
||||||
|
"navigation": {"show": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if "app" in configuration:
|
if "app" in configuration:
|
||||||
configuration["app"] = _defaults | configuration["app"]
|
for key in _defaults:
|
||||||
|
if key in configuration["app"]:
|
||||||
|
configuration["app"][key] = _defaults[key] | configuration["app"][key]
|
||||||
|
else:
|
||||||
|
configuration["app"][key] = _defaults[key]
|
||||||
else:
|
else:
|
||||||
configuration["app"] = _defaults
|
configuration["app"] = _defaults
|
||||||
|
|
||||||
@@ -60,3 +73,19 @@ else:
|
|||||||
def get_configuration(key):
|
def get_configuration(key):
|
||||||
if key in configuration:
|
if key in configuration:
|
||||||
return configuration[key]
|
return configuration[key]
|
||||||
|
|
||||||
|
|
||||||
|
def loader(kind_id):
|
||||||
|
configs = {}
|
||||||
|
userconfigpath = Path.home() / ".config" / "vm-compact" / "configs" / kind_id
|
||||||
|
if userconfigpath.exists():
|
||||||
|
filepaths = list(userconfigpath.glob("*.toml"))
|
||||||
|
for filepath in filepaths:
|
||||||
|
identifier = filepath.with_suffix("").stem
|
||||||
|
try:
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
configs[identifier] = tomllib.load(f)
|
||||||
|
logger.info(f"loader: {identifier} loaded into memory")
|
||||||
|
except tomllib.TOMLDecodeError:
|
||||||
|
logger.error(f"Invalid TOML config: configs/{filename.stem}")
|
||||||
|
return configs
|
||||||
|
|||||||
@@ -32,10 +32,15 @@ class Configurations(metaclass=SingletonMeta):
|
|||||||
# bus assigned as current submix
|
# bus assigned as current submix
|
||||||
submixes: int = configuration["submixes"]["default"]
|
submixes: int = configuration["submixes"]["default"]
|
||||||
|
|
||||||
# width of a single labelframe
|
# width of a single channel labelframe
|
||||||
level_width: int = configuration["channel"]["width"]
|
channel_width: int = configuration["channel"]["width"]
|
||||||
# height of a single labelframe
|
# height of a single channel labelframe
|
||||||
level_height: int = configuration["channel"]["height"]
|
channel_height: int = configuration["channel"]["height"]
|
||||||
|
# xpadding for a single channel labelframe
|
||||||
|
channel_xpadding: int = configuration["channel"]["xpadding"]
|
||||||
|
|
||||||
|
# do we grid the navigation frame?
|
||||||
|
navigation_show: bool = configuration["navigation"]["show"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def config(self):
|
def config(self):
|
||||||
@@ -46,7 +51,7 @@ class Configurations(metaclass=SingletonMeta):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class BaseValues(metaclass=SingletonMeta):
|
class BaseValues(metaclass=SingletonMeta):
|
||||||
# pause updates after releasing scale
|
# pause updates after releasing scale
|
||||||
run_update: bool = True
|
run_update: bool = False
|
||||||
# are we dragging main window with mouse 1
|
# are we dragging main window with mouse 1
|
||||||
dragging: bool = False
|
dragging: bool = False
|
||||||
# a vban connection established
|
# a vban connection established
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
class VMCompactErrors(Exception):
|
class VMCompactError(Exception):
|
||||||
"""Base classs for VMCompact Errors"""
|
"""Exception raised when general errors occur"""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from math import log
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from . import builders
|
from . import builders
|
||||||
@@ -42,11 +41,13 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
return "gainlayer"
|
return "gainlayer"
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if hasattr(self.target, param):
|
try:
|
||||||
return getattr(self.target, param)
|
return getattr(self.target, param)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger(f"{type(e).__name__}: {e}")
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if hasattr(self.target, param):
|
if param in dir(self.target): # avoid calling getattr (with hasattr)
|
||||||
setattr(self.target, param, value)
|
setattr(self.target, param, value)
|
||||||
|
|
||||||
def reset_gain(self, *args):
|
def reset_gain(self, *args):
|
||||||
@@ -57,8 +58,9 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
def scale_callback(self, *args):
|
def scale_callback(self, *args):
|
||||||
"""callback function for scale widget"""
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
self.setter("gain", self.gain.get())
|
val = round(self.gain.get(), 1)
|
||||||
self.gainlabel.set(round(self.gain.get(), 1))
|
self.setter("gain", val)
|
||||||
|
self.gainlabel.set(val)
|
||||||
|
|
||||||
def scale_press(self, *args):
|
def scale_press(self, *args):
|
||||||
self.after(1, self.remove_events)
|
self.after(1, self.remove_events)
|
||||||
@@ -157,12 +159,14 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
self.level.set(
|
self.level.set(
|
||||||
(
|
(
|
||||||
0
|
0
|
||||||
if self.parent.target.strip[self.index].mute or not self.on.get()
|
if self.parent.parent.strip_frame.strips[self.index].mute.get()
|
||||||
|
or not self.on.get()
|
||||||
else 72 + val - 12 + self.gain.get()
|
else 72 + val - 12 + self.gain.get()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
|
self.grid(padx=_configuration.channel_xpadding, sticky=(tk.N, tk.S))
|
||||||
[
|
[
|
||||||
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()
|
||||||
@@ -250,11 +254,11 @@ class SubMixFrame(ttk.Frame):
|
|||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
[
|
[
|
||||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
self.columnconfigure(i, minsize=_configuration.channel_width)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
self.rowconfigure(0, minsize=_configuration.level_height)
|
self.rowconfigure(0, minsize=_configuration.channel_height)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -2,27 +2,30 @@ import logging
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox
|
||||||
|
|
||||||
import sv_ttk
|
import sv_ttk
|
||||||
import vban_cmd
|
import vban_cmd
|
||||||
from vban_cmd.error import VBANCMDError
|
from vban_cmd.error import VBANCMDConnectionError
|
||||||
|
|
||||||
from .data import _base_values, _configuration, get_configuration, kind_get
|
from .data import _base_values, _configuration, get_configuration, kind_get
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Menus(tk.Menu):
|
class Menus(tk.Menu):
|
||||||
logger = logging.getLogger("menu.menus")
|
|
||||||
|
|
||||||
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
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.vban_config = get_configuration("vban")
|
self.vban_config = get_configuration("vban")
|
||||||
self.app_config = get_configuration("app")
|
self.app_config = get_configuration("app")
|
||||||
self._is_topmost = tk.BooleanVar()
|
self._is_topmost = tk.BooleanVar()
|
||||||
self._lock = tk.BooleanVar()
|
self._lock = tk.BooleanVar()
|
||||||
self._unlock = tk.BooleanVar()
|
self._unlock = tk.BooleanVar()
|
||||||
|
self._navigation_show = tk.BooleanVar(value=_configuration.navigation_show)
|
||||||
|
self._navigation_hide = tk.BooleanVar(value=not _configuration.navigation_show)
|
||||||
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
||||||
|
|
||||||
# voicemeeter menu
|
# voicemeeter menu
|
||||||
@@ -92,6 +95,14 @@ class Menus(tk.Menu):
|
|||||||
for profile in self.target.configs.keys()
|
for profile in self.target.configs.keys()
|
||||||
if profile not in self.config_defaults
|
if profile not in self.config_defaults
|
||||||
]
|
]
|
||||||
|
elif self.parent.userconfigs:
|
||||||
|
[
|
||||||
|
self.menu_configs_load.add_command(
|
||||||
|
label=name, command=partial(self.load_custom_profile, data)
|
||||||
|
)
|
||||||
|
for name, data in self.parent.userconfigs.items()
|
||||||
|
if name not in self.config_defaults
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
self.menu_configs.entryconfig(0, state="disabled")
|
self.menu_configs.entryconfig(0, state="disabled")
|
||||||
self.menu_configs.add_command(
|
self.menu_configs.add_command(
|
||||||
@@ -153,6 +164,23 @@ class Menus(tk.Menu):
|
|||||||
)
|
)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.menu_layout.entryconfig(2, state="disabled")
|
self.menu_layout.entryconfig(2, state="disabled")
|
||||||
|
# layout/navigation
|
||||||
|
self.menu_navigation = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
|
self.menu_layout.add_cascade(menu=self.menu_navigation, label="Navigation")
|
||||||
|
self.menu_navigation.add_checkbutton(
|
||||||
|
label="show",
|
||||||
|
onvalue=1,
|
||||||
|
offvalue=0,
|
||||||
|
variable=self._navigation_show,
|
||||||
|
command=partial(self.toggle_navigation, "show"),
|
||||||
|
)
|
||||||
|
self.menu_navigation.add_checkbutton(
|
||||||
|
label="hide",
|
||||||
|
onvalue=1,
|
||||||
|
offvalue=0,
|
||||||
|
variable=self._navigation_hide,
|
||||||
|
command=partial(self.toggle_navigation, "hide"),
|
||||||
|
)
|
||||||
|
|
||||||
# vban connect menu
|
# vban connect menu
|
||||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||||
@@ -212,15 +240,24 @@ class Menus(tk.Menu):
|
|||||||
self._unlock.set(not self._lock.get())
|
self._unlock.set(not self._lock.get())
|
||||||
setattr(self.target.command, cmd, val)
|
setattr(self.target.command, cmd, val)
|
||||||
|
|
||||||
|
def load_custom_profile(self, profile):
|
||||||
|
self.logger.info(f"loading user profile {profile}")
|
||||||
|
self.target.apply(profile)
|
||||||
|
|
||||||
def load_profile(self, profile):
|
def load_profile(self, profile):
|
||||||
|
self.logger.info(f"loading user profile {profile}")
|
||||||
self.target.apply_config(profile)
|
self.target.apply_config(profile)
|
||||||
|
|
||||||
def load_defaults(self):
|
def load_defaults(self):
|
||||||
resp = messagebox.askyesno(
|
msg = (
|
||||||
message="Are you sure you want to Reset values to defaults?\nPhysical strips B1, Virtual strips A1\nMono, Solo, Mute, EQ all OFF"
|
"Are you sure you want to Reset values to defaults?",
|
||||||
|
"Physical strips B1, Virtual strips A1",
|
||||||
|
"Mono, Solo, Mute, EQ all OFF",
|
||||||
|
"Gain sliders for Strip/Bus at 0.0",
|
||||||
)
|
)
|
||||||
|
resp = messagebox.askyesno(message="\n".join(msg))
|
||||||
if resp:
|
if resp:
|
||||||
self.target.apply_config("reset")
|
self.load_profile("reset")
|
||||||
|
|
||||||
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())
|
||||||
@@ -242,6 +279,7 @@ class Menus(tk.Menu):
|
|||||||
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(i == j)
|
var.set(i == j)
|
||||||
|
self.parent.subject.notify("submix")
|
||||||
|
|
||||||
def load_theme(self, theme):
|
def load_theme(self, theme):
|
||||||
sv_ttk.set_theme(theme)
|
sv_ttk.set_theme(theme)
|
||||||
@@ -282,6 +320,11 @@ class Menus(tk.Menu):
|
|||||||
for key in self.target.configs.keys()
|
for key in self.target.configs.keys()
|
||||||
if key not in self.config_defaults
|
if key not in self.config_defaults
|
||||||
]
|
]
|
||||||
|
[
|
||||||
|
self.menu_configs_load.delete(key)
|
||||||
|
for key in self.parent.userconfigs.keys()
|
||||||
|
if key not in self.config_defaults
|
||||||
|
]
|
||||||
|
|
||||||
[
|
[
|
||||||
self.menu_vban.entryconfig(j, state="disabled")
|
self.menu_vban.entryconfig(j, state="disabled")
|
||||||
@@ -300,9 +343,29 @@ class Menus(tk.Menu):
|
|||||||
for profile in self.target.configs.keys()
|
for profile in self.target.configs.keys()
|
||||||
if profile not in self.config_defaults
|
if profile not in self.config_defaults
|
||||||
]
|
]
|
||||||
|
elif self.parent.userconfigs:
|
||||||
|
[
|
||||||
|
self.menu_configs_load.add_command(
|
||||||
|
label=name, command=partial(self.load_custom_profile, data)
|
||||||
|
)
|
||||||
|
for name, data in self.parent.userconfigs.items()
|
||||||
|
if name not in self.config_defaults
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
self.menu_configs.entryconfig(0, state="disabled")
|
self.menu_configs.entryconfig(0, state="disabled")
|
||||||
|
|
||||||
|
def toggle_navigation(self, cmd=None):
|
||||||
|
if cmd == "show":
|
||||||
|
self.logger.debug("show navframe")
|
||||||
|
self.parent.nav_frame.grid()
|
||||||
|
self._navigation_show.set(True)
|
||||||
|
self._navigation_hide.set(not self._navigation_show.get())
|
||||||
|
else:
|
||||||
|
self.logger.debug("hide navframe")
|
||||||
|
self.parent.nav_frame.grid_remove()
|
||||||
|
self._navigation_hide.set(True)
|
||||||
|
self._navigation_show.set(not self._navigation_hide.get())
|
||||||
|
|
||||||
def vban_connect(self, i):
|
def vban_connect(self, i):
|
||||||
opts = {}
|
opts = {}
|
||||||
opts |= self.vban_config[f"connection-{i+1}"]
|
opts |= self.vban_config[f"connection-{i+1}"]
|
||||||
@@ -312,16 +375,19 @@ class Menus(tk.Menu):
|
|||||||
try:
|
try:
|
||||||
self.logger.info(f"Attempting vban connection to {opts.get('ip')}")
|
self.logger.info(f"Attempting vban connection to {opts.get('ip')}")
|
||||||
self.vban.login()
|
self.vban.login()
|
||||||
except VBANCMDError as e:
|
except VBANCMDConnectionError as e:
|
||||||
self.vban.logout()
|
self.vban.logout()
|
||||||
msg = (str(e), f"Please check your connection settings")
|
msg = (
|
||||||
|
f"Timeout attempting to establish connection to {opts.get('ip')}",
|
||||||
|
f"Please check your connection settings",
|
||||||
|
)
|
||||||
messagebox.showerror("Connection Error", "\n".join(msg))
|
messagebox.showerror("Connection Error", "\n".join(msg))
|
||||||
msg = (str(e), f"resuming local connection")
|
msg = (str(e), f"resuming local connection")
|
||||||
self.logger.error(", ".join(msg))
|
self.logger.error(", ".join(msg))
|
||||||
self.after(1, self.enable_vban_menus)
|
self.after(1, self.enable_vban_menus)
|
||||||
return
|
return
|
||||||
self.menu_teardown(i)
|
self.menu_teardown(i)
|
||||||
self.vban.event.ldirty = True
|
self.vban.event.add(["pdirty", "ldirty"])
|
||||||
# destroy the current App frames
|
# destroy the current App frames
|
||||||
self.parent._destroy_top_level_frames()
|
self.parent._destroy_top_level_frames()
|
||||||
_base_values.vban_connected = True
|
_base_values.vban_connected = True
|
||||||
@@ -336,6 +402,11 @@ class Menus(tk.Menu):
|
|||||||
self.menu_layout.entryconfig(
|
self.menu_layout.entryconfig(
|
||||||
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
|
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
|
||||||
)
|
)
|
||||||
|
# ensure the configs are reloaded into memory
|
||||||
|
if "config" in self.parent.target.__dict__:
|
||||||
|
del self.parent.target.__dict__["config"]
|
||||||
|
if "userconfigs" in self.parent.__dict__:
|
||||||
|
del self.parent.__dict__["userconfigs"]
|
||||||
self.menu_setup()
|
self.menu_setup()
|
||||||
|
|
||||||
def vban_disconnect(self, i):
|
def vban_disconnect(self, i):
|
||||||
@@ -356,6 +427,11 @@ class Menus(tk.Menu):
|
|||||||
self.menu_layout.entryconfig(
|
self.menu_layout.entryconfig(
|
||||||
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
|
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
|
||||||
)
|
)
|
||||||
|
# ensure the configs are reloaded into memory
|
||||||
|
if "config" in self.parent.target.__dict__:
|
||||||
|
del self.parent.target.__dict__["config"]
|
||||||
|
if "userconfigs" in self.parent.__dict__:
|
||||||
|
del self.parent.__dict__["userconfigs"]
|
||||||
self.menu_setup()
|
self.menu_setup()
|
||||||
|
|
||||||
self.after(15000, self.enable_vban_menus)
|
self.after(15000, self.enable_vban_menus)
|
||||||
|
|||||||
@@ -6,14 +6,17 @@ from . import builders
|
|||||||
from .data import _configuration
|
from .data import _configuration
|
||||||
from .gainlayer import SubMixFrame
|
from .gainlayer import SubMixFrame
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Navigation(ttk.Frame):
|
class Navigation(ttk.Frame):
|
||||||
logger = logging.getLogger("navigation.navigation")
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.grid(row=0, column=3, padx=(0, 2), pady=(5, 5), sticky=(tk.W, tk.E))
|
self.grid(row=0, column=3, padx=(0, 2), pady=(5, 5), sticky=(tk.W, tk.E))
|
||||||
|
if not _configuration.navigation_show:
|
||||||
|
self.grid_remove()
|
||||||
self.styletable = self.parent.styletable
|
self.styletable = self.parent.styletable
|
||||||
|
|
||||||
self.builder = builders.NavigationFrameBuilder(self)
|
self.builder = builders.NavigationFrameBuilder(self)
|
||||||
|
|||||||
Reference in New Issue
Block a user