9 Commits

Author SHA1 Message Date
onyx-and-iris
732368a65b fix math error in level updates 2022-10-05 23:39:51 +01:00
onyx-and-iris
d7df79b798 upd vban-cmd ver
patch bump
2022-10-05 23:12:25 +01:00
onyx-and-iris
0906f3343b upd dependencies
patch bump
2022-10-05 23:03:46 +01:00
onyx-and-iris
294dfe7d03 changes to progressbar level udpates 2022-10-05 23:03:27 +01:00
onyx-and-iris
4db7be172b bump dep versions
minor version bump
2022-10-04 16:06:47 +01:00
onyx-and-iris
35775f5024 logger.info logs added
voicemeeter-api, vban-cmd dependency ver updated

changelog updated to reflect changes

minor version bump
2022-09-29 13:45:06 +01:00
onyx-and-iris
d4b2b90fc0 run vban.logout() to cleanup threads/sockets.
bump vban-cmd dependency ver.

patch bump
2022-09-24 08:04:59 +01:00
onyx-and-iris
6de79977cc show messagebox on connection timeout
patch bump
2022-09-23 20:23:55 +01:00
onyx-and-iris
2c290e2632 now using event toggles when dragging sliders
patch bump
2022-09-16 09:44:47 +01:00
14 changed files with 161 additions and 91 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# quick test
z_*.py
quick.py
# Byte-compiled / optimized / DLL files
__pycache__/

View File

@@ -9,11 +9,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [ ] Add support for forest theme (should be coming soon)
## [1.4.0] - 2022-09-03
## [1.6.0] - 2022-09-29
### Added
- Logging module used in place of print statements across the interface.
## [1.5.1] - 2022-09-16
### Added
- 1.5.1 binary to releases
### Changed
- sv_ttk updated to v2.0.
- event toggles used to pause updates when dragging sliders.
## [1.4.2] - 2022-09-03
### Added
- tomli/tomllib compatibility layer to support python 3.10
- 1.4.2 binary to releases
## [1.3.0] - 2022-07-14

View File

@@ -4,10 +4,10 @@
# kind = 'banana'
# ip = '<ip address 1>'
# streamname = 'Command1'
# port = 6990
# port = 6980
# [connection-2]
# kind = 'potato'
# ip = '<ip address 2>'
# streamname = 'Command1'
# port = 6990
# port = 6980

31
poetry.lock generated
View File

@@ -84,7 +84,7 @@ python-versions = ">=3.7"
[[package]]
name = "vban-cmd"
version = "1.4.2"
version = "1.5.4"
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
category = "main"
optional = false
@@ -95,7 +95,7 @@ tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
[[package]]
name = "voicemeeter-api"
version = "0.7.0"
version = "0.8.2"
description = "A Python wrapper for the Voiceemeter API"
category = "main"
optional = false
@@ -107,31 +107,16 @@ tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "738f40473a635574dc00427d128af1c3bf45f4290aca9b4d8ae78b6992a486c7"
content-hash = "5c59ce43f535924db8cfddd40b9b97ff53861fa43f340396d06a69a0ca80451f"
[metadata.files]
black = []
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
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"},
]
click = []
colorama = []
mypy-extensions = []
pathspec = []
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
platformdirs = []
sv-ttk = []
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tomli = []
vban-cmd = []
voicemeeter-api = []

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "voicemeeter-compact"
version = "1.5.0"
version = "1.6.4"
description = "A Compact Voicemeeter Remote App"
authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT"
@@ -16,8 +16,8 @@ include = ["vmcompact/img/cat.ico"]
python = "^3.10"
sv-ttk = "^2.0"
tomli = { version = "^2.0.1", python = "<3.11" }
voicemeeter-api = "^0.7.0"
vban-cmd = "^1.4.2"
voicemeeter-api = "^0.8.2"
vban-cmd = "^1.5.4"
[tool.poetry.dev-dependencies]
black = {version = "^22.6.0", allow-prereleases = true}

View File

@@ -35,6 +35,8 @@ class App(tk.Tk):
self._vmr = vmr
self._vmr.event.add("ldirty")
self._vmr.event.remove("mdirty")
self._vmr.event.remove("midi")
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
if icon_path.is_file():
self.iconbitmap(str(icon_path))
@@ -93,11 +95,10 @@ class App(tk.Tk):
def on_update(self, subject):
"""called whenever notified of update"""
if not _base_values.in_scale_button_1:
if subject == "pdirty":
self.after(1, self.subject.notify, "pdirty")
elif subject == "ldirty" and not _base_values.dragging:
self.after(1, self.subject.notify, "ldirty")
if subject == "pdirty" and _base_values.run_update:
self.after(1, self.subject.notify, "pdirty")
elif subject == "ldirty" and not _base_values.dragging:
self.after(1, self.subject.notify, "ldirty")
def _destroy_top_level_frames(self):
"""
@@ -118,16 +119,14 @@ class App(tk.Tk):
def dragging(self, event, *args):
if event.widget is self:
if self.drag_id == "":
_base_values.in_scale_button_1 = True
_base_values.dragging = True
else:
self.after_cancel(self.drag_id)
self.drag_id = self.after(100, self.stop_drag)
def stop_drag(self):
_base_values.dragging = False
_base_values.in_scale_button_1 = False
self.drag_id = ""
_base_values.dragging = False
_apps = {kind.name: App.make(kind) for kind in _kinds_all}

View File

@@ -1,4 +1,5 @@
import abc
import logging
import tkinter as tk
from functools import partial
from tkinter import ttk
@@ -27,6 +28,8 @@ class AbstractBuilder(abc.ABC):
class MainFrameBuilder(AbstractBuilder):
"""Responsible for building the frames that sit directly on the mainframe"""
logger = logging.getLogger("builders.mainframebuilder")
def __init__(self, app):
self.kind = app.kind
self.app = app
@@ -39,21 +42,26 @@ class MainFrameBuilder(AbstractBuilder):
if _configuration.themes_enabled:
if sv_ttk.get_theme() not in ("light", "dark"):
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"
)
def create_channelframe(self, type_):
if type_ == "strip":
self.app.strip_frame = _make_channelframe(self.app, type_)
else:
self.app.bus_frame = _make_channelframe(self.app, type_)
self.logger.info(f"Finished building channelframe type {type_}")
def create_separator(self):
self.app.sep = ttk.Separator(self.app, orient="vertical")
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.app.columnconfigure(1, minsize=15)
self.logger.info(f"Finished building separator")
def create_navframe(self):
self.app.nav_frame = Navigation(self.app)
self.logger.info(f"Finished building navframe")
def create_configframe(self, type_, index, id):
if type_ == "strip":
@@ -99,6 +107,7 @@ class MainFrameBuilder(AbstractBuilder):
)
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)
def reset_config_frames(self):
@@ -111,6 +120,7 @@ class MainFrameBuilder(AbstractBuilder):
def create_banner(self):
self.app.banner = Banner(self.app)
self.app.banner.grid(row=4, column=0, columnspan=3)
self.logger.info(f"Finished building banner")
def teardown(self):
pass
@@ -216,7 +226,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
"""Adds a progress bar widget to a single label frame"""
self.labelframe.pb = ttk.Progressbar(
self.labelframe,
maximum=100,
maximum=72,
orient="vertical",
mode="determinate",
variable=self.labelframe.level,

View File

@@ -64,15 +64,29 @@ class ChannelLabelFrame(ttk.LabelFrame):
def reset_gain(self, *args):
self.setter("gain", 0)
self.gain.set(0)
self.gainlabel.set(self.gain.get())
def scale_press(self, *args):
_base_values.in_scale_button_1 = True
self.after(1, self.remove_events)
def remove_events(self):
self.parent.target.event.remove("pdirty")
self.parent.target.event.remove("ldirty")
def scale_release(self, *args):
_base_values.in_scale_button_1 = False
_base_values.run_update = False
self.after(1, self.add_events)
def add_events(self):
self.parent.target.event.add("pdirty")
self.parent.target.event.add("ldirty")
self.after(500, self.resume_updates)
def resume_updates(self):
_base_values.run_update = True
def _on_mousewheel(self, event):
_base_values.in_scale_button_1 = True
_base_values.run_update = False
self.gain.set(
self.gain.get()
+ (
@@ -86,7 +100,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
elif self.gain.get() < -60:
self.gain.set(-60)
self.setter("gain", self.gain.get())
_base_values.in_scale_button_1 = False
self.after(1, self.resume_updates)
def open_config(self):
if self.conf.get():
@@ -172,7 +186,7 @@ class Strip(ChannelLabelFrame):
if self.target.levels.is_updated:
val = max(self.target.levels.prefader)
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())
)
@@ -194,7 +208,7 @@ class Bus(ChannelLabelFrame):
if self.index < self.parent.parent.kind.num_bus:
if self.target.levels.is_updated or self.level.get() != -118:
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):

View File

@@ -37,10 +37,23 @@ class Config(ttk.Frame):
setattr(self.target, param, value)
def scale_press(self, *args):
_base_values.in_scale_button_1 = True
self.after(1, self.remove_events)
def remove_events(self):
self.parent.target.event.remove("pdirty")
self.parent.target.event.remove("ldirty")
def scale_release(self, *args):
_base_values.in_scale_button_1 = False
_base_values.run_update = False
self.after(1, self.add_events)
def add_events(self):
self.parent.target.event.add("pdirty")
self.parent.target.event.add("ldirty")
self.after(350, self.resume_updates)
def resume_updates(self):
_base_values.run_update = True
def scale_enter(self, param, *args):
val = self.slider_vars[self.slider_params.index(param)].get()

View File

@@ -1,3 +1,4 @@
import logging
from pathlib import Path
try:
@@ -5,6 +6,8 @@ try:
except ModuleNotFoundError:
import tomli as tomllib
LOGGER = logging.getLogger("configurations")
configuration = {}
config_path = [Path.cwd() / "configs"]
@@ -21,7 +24,7 @@ for path in config_path:
print(f"Invalid TOML config: configs/{filename.stem}")
for name, cfg in configs.items():
print(f"Loaded configuration configs/{name}")
LOGGER.info(f"Loaded configuration configs/{name}")
configuration[name] = cfg
_defaults = {

View File

@@ -45,8 +45,8 @@ class Configurations(metaclass=SingletonMeta):
@dataclass
class BaseValues(metaclass=SingletonMeta):
# are we dragging a scale with mouse 1
in_scale_button_1: bool = False
# pause updates after releasing scale
run_update: bool = True
# are we dragging main window with mouse 1
dragging: bool = False
# a vban connection established

View File

@@ -52,7 +52,7 @@ class GainLayer(ttk.LabelFrame):
def reset_gain(self, *args):
self.setter("gain", 0)
self.gain.set(0)
self.gainlabel.set(0)
self.gainlabel.set(self.gain.get())
def scale_callback(self, *args):
"""callback function for scale widget"""
@@ -61,12 +61,26 @@ class GainLayer(ttk.LabelFrame):
self.gainlabel.set(round(self.gain.get(), 1))
def scale_press(self, *args):
_base_values.in_scale_button_1 = True
self.after(1, self.remove_events)
def remove_events(self):
self.parent.target.event.remove("pdirty")
self.parent.target.event.remove("ldirty")
def scale_release(self, *args):
_base_values.in_scale_button_1 = False
_base_values.run_update = False
self.after(1, self.add_events)
def add_events(self):
self.parent.target.event.add("pdirty")
self.parent.target.event.add("ldirty")
self.after(500, self.resume_updates)
def resume_updates(self):
_base_values.run_update = True
def _on_mousewheel(self, event):
_base_values.run_update = False
self.gain.set(
self.gain.get()
+ (
@@ -80,6 +94,7 @@ class GainLayer(ttk.LabelFrame):
elif self.gain.get() < -60:
self.gain.set(-60)
self.setter("gain", self.gain.get())
self.after(1, self.resume_updates)
def set_on(self):
"""enables a gainlayer. sets its button colour"""
@@ -143,7 +158,7 @@ class GainLayer(ttk.LabelFrame):
(
0
if self.parent.target.strip[self.index].mute or not self.on.get()
else 100 + val - 18 + self.gain.get()
else 72 + val - 12 + self.gain.get()
)
)

View File

@@ -1,3 +1,4 @@
import logging
import tkinter as tk
import webbrowser
from functools import partial
@@ -5,11 +6,14 @@ from tkinter import messagebox, ttk
import sv_ttk
import vban_cmd
from vban_cmd.error import VBANCMDError
from .data import _base_values, _configuration, get_configuration, kind_get
class Menus(tk.Menu):
logger = logging.getLogger("menu.menus")
def __init__(self, parent, vmr):
super().__init__()
self.parent = parent
@@ -267,12 +271,15 @@ class Menus(tk.Menu):
for menu in self.menu_layout.winfo_children()
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
[
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
]
@@ -282,12 +289,38 @@ class Menus(tk.Menu):
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
]
else:
self.menu_configs.entryconfig(0, state="disabled")
def vban_connect(self, i):
opts = {}
opts |= self.vban_config[f"connection-{i+1}"]
kind_id = opts.pop("kind")
self.vban = vban_cmd.api(kind_id, **opts)
# login to vban interface
self.vban.login()
try:
self.logger.info(f"Attempting vban connection to {opts.get('ip')}")
self.vban.login()
except VBANCMDError as e:
self.vban.logout()
msg = (str(e), 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("ldirty")
# destroy the current App frames
self.parent._destroy_top_level_frames()
@@ -303,27 +336,10 @@ class Menus(tk.Menu):
self.menu_layout.entryconfig(
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
)
# rebuild config load menus
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
]
else:
self.menu_configs.entryconfig(0, state="disabled")
self.menu_setup()
def vban_disconnect(self, i):
# remove config load menus
[
self.menu_configs_load.delete(key)
for key in self.vban.configs.keys()
if key not in self.config_defaults
]
self.menu_teardown(i)
# destroy the current App frames
self.parent._destroy_top_level_frames()
@@ -340,19 +356,7 @@ class Menus(tk.Menu):
self.menu_layout.entryconfig(
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
)
# rebuild config load menus
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
]
else:
self.menu_configs.entryconfig(0, state="disabled")
self.menu_setup()
self.after(15000, self.enable_vban_menus)

View File

@@ -1,3 +1,4 @@
import logging
import tkinter as tk
from tkinter import ttk
@@ -7,6 +8,8 @@ from .gainlayer import SubMixFrame
class Navigation(ttk.Frame):
logger = logging.getLogger("navigation.navigation")
def __init__(self, parent):
super().__init__(parent)
self.parent = parent
@@ -26,6 +29,9 @@ class Navigation(ttk.Frame):
def show_submix(self):
if self.submix.get():
self.parent.submix_frame = SubMixFrame(self.parent)
self.logger.info(
f"Finished building submixframe for submix {_configuration.submixes}"
)
else:
if _configuration.extends_horizontal:
self.parent.submix_frame.teardown()
@@ -39,6 +45,9 @@ class Navigation(ttk.Frame):
self.parent.bus_frame.grid()
else:
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:
self.styletable.configure(
@@ -84,4 +93,4 @@ class Navigation(ttk.Frame):
background=f'{"purple" if self.submix.get() else "white"}',
)
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")