mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2026-04-08 17:03:32 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ab1fd7102 | |||
| d896193ade | |||
| 8c0c31dc0d | |||
| c8b3e9fc33 | |||
| d4358bf7d3 | |||
| 194b95d67b | |||
| 0f734e87b7 | |||
| 944ef9ca1c | |||
| fc20bb0c1e | |||
| 5ccc2a6dab | |||
| cfc1279f6c | |||
| d4df11f62d | |||
|
|
3b6e1f61a4 | ||
|
|
3f306ddf62 | ||
|
|
732368a65b | ||
|
|
d7df79b798 | ||
|
|
0906f3343b | ||
|
|
294dfe7d03 | ||
|
|
4db7be172b | ||
|
|
35775f5024 | ||
|
|
d4b2b90fc0 | ||
|
|
6de79977cc |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
# quick test
|
# quick test
|
||||||
z_*.py
|
quick.py
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@@ -131,3 +131,5 @@ dmypy.json
|
|||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
.vscode/
|
||||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -7,7 +7,39 @@ 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.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
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Logging module used in place of print statements across the interface.
|
||||||
|
|
||||||
## [1.5.1] - 2022-09-16
|
## [1.5.1] - 2022-09-16
|
||||||
|
|
||||||
@@ -20,8 +52,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- sv_ttk updated to v2.0.
|
- sv_ttk updated to v2.0.
|
||||||
- event toggles used to pause updates when dragging sliders.
|
- event toggles used to pause updates when dragging sliders.
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
## [1.4.2] - 2022-09-03
|
## [1.4.2] - 2022-09-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
# kind = 'banana'
|
# kind = 'banana'
|
||||||
# ip = '<ip address 1>'
|
# ip = '<ip address 1>'
|
||||||
# streamname = 'Command1'
|
# streamname = 'Command1'
|
||||||
# port = 6990
|
# port = 6980
|
||||||
|
|
||||||
# [connection-2]
|
# [connection-2]
|
||||||
# kind = 'potato'
|
# kind = 'potato'
|
||||||
# ip = '<ip address 2>'
|
# ip = '<ip address 2>'
|
||||||
# streamname = 'Command1'
|
# streamname = 'Command1'
|
||||||
# port = 6990
|
# port = 6980
|
||||||
|
|||||||
54
poetry.lock
generated
54
poetry.lock
generated
@@ -1,10 +1,10 @@
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "22.8.0"
|
version = "22.10.0"
|
||||||
description = "The uncompromising code formatter."
|
description = "The uncompromising code formatter."
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.2"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
click = ">=8.0.0"
|
click = ">=8.0.0"
|
||||||
@@ -38,6 +38,20 @@ category = "dev"
|
|||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
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 = "0.4.3"
|
||||||
@@ -68,8 +82,8 @@ test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytes
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sv-ttk"
|
name = "sv-ttk"
|
||||||
version = "2.0"
|
version = "2.4.5"
|
||||||
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.4"
|
||||||
@@ -84,7 +98,7 @@ python-versions = ">=3.7"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cmd"
|
name = "vban-cmd"
|
||||||
version = "1.4.2"
|
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.7.0"
|
version = "2.0.1"
|
||||||
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,31 +121,17 @@ 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 = "738f40473a635574dc00427d128af1c3bf45f4290aca9b4d8ae78b6992a486c7"
|
content-hash = "ffb9af7ef7aa87ac08a09293de5e99487155faaa459cd49964ac95589deb69fa"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
black = []
|
black = []
|
||||||
click = [
|
click = []
|
||||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
colorama = []
|
||||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
isort = []
|
||||||
]
|
mypy-extensions = []
|
||||||
colorama = [
|
|
||||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
|
||||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
|
||||||
]
|
|
||||||
mypy-extensions = [
|
|
||||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
|
||||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
|
||||||
]
|
|
||||||
pathspec = []
|
pathspec = []
|
||||||
platformdirs = [
|
platformdirs = []
|
||||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
|
||||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
|
||||||
]
|
|
||||||
sv-ttk = []
|
sv-ttk = []
|
||||||
tomli = [
|
tomli = []
|
||||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
|
||||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
|
||||||
]
|
|
||||||
vban-cmd = []
|
vban-cmd = []
|
||||||
voicemeeter-api = []
|
voicemeeter-api = []
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "voicemeeter-compact"
|
name = "voicemeeter-compact"
|
||||||
version = "1.5.1"
|
version = "1.7.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.4.5"
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||||
voicemeeter-api = "^0.7.0"
|
voicemeeter-api = "^2.0.1"
|
||||||
vban-cmd = "^1.4.2"
|
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,11 +1,13 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -34,12 +36,14 @@ class App(tk.Tk):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._vmr = vmr
|
self._vmr = vmr
|
||||||
self._vmr.event.add("ldirty")
|
self._vmr.event.add(["pdirty", "ldirty"])
|
||||||
|
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:
|
||||||
@@ -50,6 +54,9 @@ class App(tk.Tk):
|
|||||||
self.drag_id = ""
|
self.drag_id = ""
|
||||||
self.bind("<Configure>", self.dragging)
|
self.bind("<Configure>", self.dragging)
|
||||||
|
|
||||||
|
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"""
|
||||||
@@ -74,8 +81,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
|
||||||
@@ -90,12 +97,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):
|
||||||
@@ -126,6 +133,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}
|
||||||
|
|
||||||
@@ -136,5 +148,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)
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import abc
|
import abc
|
||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
@@ -11,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
|
||||||
@@ -30,6 +33,7 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
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(
|
||||||
@@ -39,24 +43,26 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
if _configuration.themes_enabled:
|
if _configuration.themes_enabled:
|
||||||
if sv_ttk.get_theme() not in ("light", "dark"):
|
if sv_ttk.get_theme() not in ("light", "dark"):
|
||||||
sv_ttk.set_theme(_configuration.theme_mode)
|
sv_ttk.set_theme(_configuration.theme_mode)
|
||||||
print(f"Sunvalley {sv_ttk.get_theme().capitalize()} Theme applied")
|
self.logger.info(
|
||||||
|
f"Sunvalley {sv_ttk.get_theme().capitalize()} Theme applied"
|
||||||
self.app.target.event.remove("mdirty")
|
)
|
||||||
self.app.target.event.remove("midi")
|
|
||||||
|
|
||||||
def create_channelframe(self, type_):
|
def create_channelframe(self, type_):
|
||||||
if type_ == "strip":
|
if type_ == "strip":
|
||||||
self.app.strip_frame = _make_channelframe(self.app, type_)
|
self.app.strip_frame = _make_channelframe(self.app, type_)
|
||||||
else:
|
else:
|
||||||
self.app.bus_frame = _make_channelframe(self.app, type_)
|
self.app.bus_frame = _make_channelframe(self.app, type_)
|
||||||
|
self.logger.info(f"Finished building channelframe type {type_}")
|
||||||
|
|
||||||
def create_separator(self):
|
def create_separator(self):
|
||||||
self.app.sep = ttk.Separator(self.app, orient="vertical")
|
self.app.sep = ttk.Separator(self.app, orient="vertical")
|
||||||
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
||||||
self.app.columnconfigure(1, minsize=15)
|
self.app.columnconfigure(1, minsize=15)
|
||||||
|
self.logger.info(f"Finished building separator")
|
||||||
|
|
||||||
def create_navframe(self):
|
def create_navframe(self):
|
||||||
self.app.nav_frame = Navigation(self.app)
|
self.app.nav_frame = Navigation(self.app)
|
||||||
|
self.logger.info(f"Finished building navframe")
|
||||||
|
|
||||||
def create_configframe(self, type_, index, id):
|
def create_configframe(self, type_, index, id):
|
||||||
if type_ == "strip":
|
if type_ == "strip":
|
||||||
@@ -102,6 +108,7 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
)
|
)
|
||||||
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
||||||
]
|
]
|
||||||
|
self.logger.info(f"Finished building configframe for {type_}[{index}]")
|
||||||
self.app.after(5, self.reset_config_frames)
|
self.app.after(5, self.reset_config_frames)
|
||||||
|
|
||||||
def reset_config_frames(self):
|
def reset_config_frames(self):
|
||||||
@@ -114,6 +121,7 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
def create_banner(self):
|
def create_banner(self):
|
||||||
self.app.banner = Banner(self.app)
|
self.app.banner = Banner(self.app)
|
||||||
self.app.banner.grid(row=4, column=0, columnspan=3)
|
self.app.banner.grid(row=4, column=0, columnspan=3)
|
||||||
|
self.logger.info(f"Finished building banner")
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
pass
|
pass
|
||||||
@@ -219,7 +227,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
"""Adds a progress bar widget to a single label frame"""
|
"""Adds a progress bar widget to a single label frame"""
|
||||||
self.labelframe.pb = ttk.Progressbar(
|
self.labelframe.pb = ttk.Progressbar(
|
||||||
self.labelframe,
|
self.labelframe,
|
||||||
maximum=100,
|
maximum=72,
|
||||||
orient="vertical",
|
orient="vertical",
|
||||||
mode="determinate",
|
mode="determinate",
|
||||||
variable=self.labelframe.level,
|
variable=self.labelframe.level,
|
||||||
@@ -329,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
|
||||||
]
|
]
|
||||||
@@ -386,16 +394,16 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
orient="horizontal",
|
orient="horizontal",
|
||||||
length=_configuration.level_width,
|
length=_configuration.level_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)
|
||||||
@@ -410,16 +418,16 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
orient="horizontal",
|
orient="horizontal",
|
||||||
length=_configuration.level_width,
|
length=_configuration.level_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)
|
||||||
@@ -556,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()
|
||||||
@@ -186,7 +192,7 @@ class Strip(ChannelLabelFrame):
|
|||||||
if self.target.levels.is_updated:
|
if self.target.levels.is_updated:
|
||||||
val = max(self.target.levels.prefader)
|
val = max(self.target.levels.prefader)
|
||||||
self.level.set(
|
self.level.set(
|
||||||
(0 if self.mute.get() else 100 + val - 18 + self.gain.get())
|
(0 if self.mute.get() else 72 + val - 12 + self.gain.get())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -208,7 +214,7 @@ class Bus(ChannelLabelFrame):
|
|||||||
if self.index < self.parent.parent.kind.num_bus:
|
if self.index < self.parent.parent.kind.num_bus:
|
||||||
if self.target.levels.is_updated or self.level.get() != -118:
|
if self.target.levels.is_updated or self.level.get() != -118:
|
||||||
val = max(self.target.levels.all)
|
val = max(self.target.levels.all)
|
||||||
self.level.set((0 if self.mute.get() else 100 + val - 18))
|
self.level.set((0 if self.mute.get() else 72 + val - 12))
|
||||||
|
|
||||||
|
|
||||||
class ChannelFrame(ttk.Frame):
|
class ChannelFrame(ttk.Frame):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -5,24 +6,32 @@ try:
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
import tomli as tomllib
|
import tomli as tomllib
|
||||||
|
|
||||||
|
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
|
||||||
print(f"Loaded configuration configs/{name}")
|
break
|
||||||
configuration[name] = cfg
|
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
"configs": {
|
"configs": {
|
||||||
@@ -57,3 +66,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
|
||||||
|
|||||||
@@ -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,8 +159,9 @@ 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()
|
||||||
else 100 + val - 18 + self.gain.get()
|
or not self.on.get()
|
||||||
|
else 72 + val - 12 + self.gain.get()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
|
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 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):
|
||||||
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()
|
||||||
@@ -88,6 +93,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(
|
||||||
@@ -208,15 +221,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())
|
||||||
@@ -238,6 +260,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)
|
||||||
@@ -267,12 +290,20 @@ class Menus(tk.Menu):
|
|||||||
for menu in self.menu_layout.winfo_children()
|
for menu in self.menu_layout.winfo_children()
|
||||||
if isinstance(menu, tk.Menu)
|
if isinstance(menu, tk.Menu)
|
||||||
]
|
]
|
||||||
|
self.logger.info(
|
||||||
|
f"Finished loading theme Sunvalley {sv_ttk.get_theme().capitalize()} theme"
|
||||||
|
)
|
||||||
|
|
||||||
def vban_connect(self, i):
|
def menu_teardown(self, i):
|
||||||
# remove config load menus
|
# remove config load menus
|
||||||
[
|
[
|
||||||
self.menu_configs_load.delete(key)
|
self.menu_configs_load.delete(key)
|
||||||
for key in self.vmr.configs.keys()
|
for key in self.target.configs.keys()
|
||||||
|
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
|
if key not in self.config_defaults
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -282,13 +313,50 @@ class Menus(tk.Menu):
|
|||||||
if j != i
|
if j != i
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def menu_setup(self):
|
||||||
|
if len(self.target.configs) > len(self.config_defaults) and all(
|
||||||
|
key in self.target.configs for key in self.config_defaults
|
||||||
|
):
|
||||||
|
[
|
||||||
|
self.menu_configs_load.add_command(
|
||||||
|
label=profile, command=partial(self.load_profile, profile)
|
||||||
|
)
|
||||||
|
for profile in self.target.configs.keys()
|
||||||
|
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:
|
||||||
|
self.menu_configs.entryconfig(0, state="disabled")
|
||||||
|
|
||||||
|
def vban_connect(self, i):
|
||||||
opts = {}
|
opts = {}
|
||||||
opts |= self.vban_config[f"connection-{i+1}"]
|
opts |= self.vban_config[f"connection-{i+1}"]
|
||||||
kind_id = opts.pop("kind")
|
kind_id = opts.pop("kind")
|
||||||
self.vban = vban_cmd.api(kind_id, **opts)
|
self.vban = vban_cmd.api(kind_id, **opts)
|
||||||
# login to vban interface
|
# login to vban interface
|
||||||
self.vban.login()
|
try:
|
||||||
self.vban.event.add("ldirty")
|
self.logger.info(f"Attempting vban connection to {opts.get('ip')}")
|
||||||
|
self.vban.login()
|
||||||
|
except VBANCMDConnectionError as e:
|
||||||
|
self.vban.logout()
|
||||||
|
msg = (
|
||||||
|
f"Timeout attempting to establish connection to {opts.get('ip')}",
|
||||||
|
f"Please check your connection settings",
|
||||||
|
)
|
||||||
|
messagebox.showerror("Connection Error", "\n".join(msg))
|
||||||
|
msg = (str(e), f"resuming local connection")
|
||||||
|
self.logger.error(", ".join(msg))
|
||||||
|
self.after(1, self.enable_vban_menus)
|
||||||
|
return
|
||||||
|
self.menu_teardown(i)
|
||||||
|
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
|
||||||
@@ -303,27 +371,15 @@ 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'}"
|
||||||
)
|
)
|
||||||
# rebuild config load menus
|
# ensure the configs are reloaded into memory
|
||||||
if len(self.target.configs) > len(self.config_defaults) and all(
|
if "config" in self.parent.target.__dict__:
|
||||||
key in self.target.configs for key in self.config_defaults
|
del self.parent.target.__dict__["config"]
|
||||||
):
|
if "userconfigs" in self.parent.__dict__:
|
||||||
[
|
del self.parent.__dict__["userconfigs"]
|
||||||
self.menu_configs_load.add_command(
|
self.menu_setup()
|
||||||
label=profile, command=partial(self.load_profile, profile)
|
|
||||||
)
|
|
||||||
for profile in self.target.configs.keys()
|
|
||||||
if profile not in self.config_defaults
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
self.menu_configs.entryconfig(0, state="disabled")
|
|
||||||
|
|
||||||
def vban_disconnect(self, i):
|
def vban_disconnect(self, i):
|
||||||
# remove config load menus
|
self.menu_teardown(i)
|
||||||
[
|
|
||||||
self.menu_configs_load.delete(key)
|
|
||||||
for key in self.vban.configs.keys()
|
|
||||||
if key not in self.config_defaults
|
|
||||||
]
|
|
||||||
|
|
||||||
# destroy the current App frames
|
# destroy the current App frames
|
||||||
self.parent._destroy_top_level_frames()
|
self.parent._destroy_top_level_frames()
|
||||||
@@ -340,19 +396,12 @@ 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'}"
|
||||||
)
|
)
|
||||||
# rebuild config load menus
|
# ensure the configs are reloaded into memory
|
||||||
if len(self.target.configs) > len(self.config_defaults) and all(
|
if "config" in self.parent.target.__dict__:
|
||||||
key in self.target.configs for key in self.config_defaults
|
del self.parent.target.__dict__["config"]
|
||||||
):
|
if "userconfigs" in self.parent.__dict__:
|
||||||
[
|
del self.parent.__dict__["userconfigs"]
|
||||||
self.menu_configs_load.add_command(
|
self.menu_setup()
|
||||||
label=profile, command=partial(self.load_profile, profile)
|
|
||||||
)
|
|
||||||
for profile in self.target.configs.keys()
|
|
||||||
if profile not in self.config_defaults
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
self.menu_configs.entryconfig(0, state="disabled")
|
|
||||||
|
|
||||||
self.after(15000, self.enable_vban_menus)
|
self.after(15000, self.enable_vban_menus)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
@@ -5,11 +6,14 @@ 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):
|
||||||
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))
|
||||||
self.styletable = self.parent.styletable
|
self.styletable = self.parent.styletable
|
||||||
|
|
||||||
@@ -26,6 +30,9 @@ class Navigation(ttk.Frame):
|
|||||||
def show_submix(self):
|
def show_submix(self):
|
||||||
if self.submix.get():
|
if self.submix.get():
|
||||||
self.parent.submix_frame = SubMixFrame(self.parent)
|
self.parent.submix_frame = SubMixFrame(self.parent)
|
||||||
|
self.logger.info(
|
||||||
|
f"Finished building submixframe for submix {_configuration.submixes}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if _configuration.extends_horizontal:
|
if _configuration.extends_horizontal:
|
||||||
self.parent.submix_frame.teardown()
|
self.parent.submix_frame.teardown()
|
||||||
@@ -39,6 +46,9 @@ class Navigation(ttk.Frame):
|
|||||||
self.parent.bus_frame.grid()
|
self.parent.bus_frame.grid()
|
||||||
else:
|
else:
|
||||||
self.parent.rowconfigure(2, weight=0, minsize=0)
|
self.parent.rowconfigure(2, weight=0, minsize=0)
|
||||||
|
self.logger.info(
|
||||||
|
f"Finished tearing down submixframe for submix {_configuration.submixes}"
|
||||||
|
)
|
||||||
|
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
|
|||||||
Reference in New Issue
Block a user