mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2025-04-06 21:43:45 +01:00
Compare commits
4 Commits
3e68488231
...
b809bcb28f
Author | SHA1 | Date | |
---|---|---|---|
b809bcb28f | |||
a0b9a92a2a | |||
9faf8ae10c | |||
82cf0e914b |
11
CHANGELOG.md
11
CHANGELOG.md
@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- [ ] Add support for forest theme (if rbende adds it to pypi)
|
||||
|
||||
## [1.9.0] - 2023-07-10
|
||||
|
||||
### Added
|
||||
|
||||
- Should the voicemeeter-compact app lose communication with Voicemeeter GUI a popup will show asking to restart the GUI.
|
||||
- If yes is selected the app's mainframe will redraw, there will be a grace period before updates start again due to Voicemeeter engine startup.
|
||||
|
||||
### Fixed
|
||||
|
||||
- From the menu, Voicemeeter->Shutdown now closes both the compact app and the main Voicemeeter GUI.
|
||||
|
||||
## [1.8.0] - 2023-06-29
|
||||
|
||||
### Added
|
||||
|
16
README.md
16
README.md
@ -34,16 +34,16 @@ import vmcompact
|
||||
|
||||
|
||||
def main():
|
||||
# pass the kind_id and the vm object to the app
|
||||
with voicemeeterlib.api(kind_id) as vm:
|
||||
app = vmcompact.connect(kind_id, vm)
|
||||
# choose the kind of Voicemeeter (Local connection)
|
||||
KIND_ID = "banana"
|
||||
|
||||
# pass the KIND_ID and the vm object to the app
|
||||
with voicemeeterlib.api(KIND_ID) as vm:
|
||||
app = vmcompact.connect(KIND_ID, vm)
|
||||
app.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# choose the kind of Voicemeeter (Local connection)
|
||||
kind_id = "banana"
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
@ -53,9 +53,9 @@ It's important to know that only labelled strips and buses will appear in the Ch
|
||||
|
||||
If the GUI looks like the above when you first load it, then no channels are labelled. From the menu, `Configs->Load config` you may load an example config. Save your current Voicemeeter settings first :).
|
||||
|
||||
### kind_id
|
||||
### KIND_ID
|
||||
|
||||
Set the kind of Voicemeeter, kind_id may be:
|
||||
Set the kind of Voicemeeter, KIND_ID may be:
|
||||
|
||||
- `basic`
|
||||
- `banana`
|
||||
|
@ -4,12 +4,12 @@ import vmcompact
|
||||
|
||||
|
||||
def main():
|
||||
with voicemeeterlib.api(kind_id) as vmr:
|
||||
app = vmcompact.connect(kind_id, vmr)
|
||||
KIND_ID = "banana"
|
||||
|
||||
with voicemeeterlib.api(KIND_ID) as vmr:
|
||||
app = vmcompact.connect(KIND_ID, vmr)
|
||||
app.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
kind_id = "banana"
|
||||
|
||||
main()
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "voicemeeter-compact"
|
||||
version = "1.8.4"
|
||||
version = "1.9.0"
|
||||
description = "A Compact Voicemeeter Remote App"
|
||||
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
||||
license = "MIT"
|
||||
|
@ -2,12 +2,14 @@ import logging
|
||||
import tkinter as tk
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox, ttk
|
||||
from typing import NamedTuple
|
||||
|
||||
import voicemeeterlib
|
||||
|
||||
from .builders import MainFrameBuilder
|
||||
from .configurations import loader
|
||||
from .data import _base_values, _configuration, _kinds_all
|
||||
from .data import _base_values, _configuration, _kinds_all, get_configuration
|
||||
from .errors import VMCompactError
|
||||
from .menu import Menus
|
||||
from .subject import Subject
|
||||
@ -48,7 +50,7 @@ class App(tk.Tk):
|
||||
self.minsize(275, False)
|
||||
self.subject = Subject()
|
||||
self._configs = None
|
||||
self["menu"] = Menus(self, vmr)
|
||||
self.menu = self["menu"] = Menus(self, vmr)
|
||||
self.styletable = ttk.Style()
|
||||
if _configuration.config:
|
||||
vmr.apply_config(_configuration.config)
|
||||
@ -58,11 +60,7 @@ class App(tk.Tk):
|
||||
self.drag_id = ""
|
||||
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()
|
||||
self.after(1, self.healthcheck_step)
|
||||
|
||||
def __str__(self):
|
||||
return f"{type(self).__name__}App"
|
||||
@ -148,6 +146,42 @@ class App(tk.Tk):
|
||||
self._configs = loader(self.kind.name, self.target)
|
||||
return self._configs
|
||||
|
||||
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 healthcheck_step(self):
|
||||
if not _base_values.vban_connected:
|
||||
try:
|
||||
self._vmr.pdirty
|
||||
except voicemeeterlib.error.CAPIError:
|
||||
resp = messagebox.askyesno(message="Restart Voicemeeter GUI?")
|
||||
if resp:
|
||||
self.logger.debug(
|
||||
"healthcheck failed, rebuilding the app after GUI restart."
|
||||
)
|
||||
self._vmr.end_thread()
|
||||
self._vmr.run_voicemeeter(self._vmr.kind.name)
|
||||
_base_values.run_update = False
|
||||
self._vmr.init_thread()
|
||||
self.after(8000, self.start_updates)
|
||||
self._destroy_top_level_frames()
|
||||
self.build_app(self._vmr.kind)
|
||||
vban_config = get_configuration("vban")
|
||||
for i, _ in enumerate(vban_config):
|
||||
target = getattr(self.menu, f"menu_vban_{i+1}")
|
||||
target.entryconfig(0, state="normal")
|
||||
target.entryconfig(1, state="disabled")
|
||||
[
|
||||
self.menu.menu_vban.entryconfig(j, state="normal")
|
||||
for j, _ in enumerate(self.menu.menu_vban.winfo_children())
|
||||
]
|
||||
else:
|
||||
self.destroy()
|
||||
self.after(250, self.healthcheck_step)
|
||||
|
||||
|
||||
_apps = {kind.name: App.make(kind) for kind in _kinds_all}
|
||||
|
||||
|
@ -249,7 +249,13 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
||||
self.scale.bind("<Button-1>", self.labelframe.scale_press)
|
||||
self.scale.bind("<ButtonRelease-1>", self.labelframe.scale_release)
|
||||
self.scale.bind("<MouseWheel>", self.labelframe._on_mousewheel)
|
||||
self.scale.bind(
|
||||
"<MouseWheel>",
|
||||
partial(
|
||||
self.labelframe.pause_updates,
|
||||
self.labelframe._on_mousewheel,
|
||||
),
|
||||
)
|
||||
|
||||
def add_gain_label(self):
|
||||
self.labelframe.gain_label = ttk.Label(
|
||||
@ -263,7 +269,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
self.button_mute = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
text="MUTE",
|
||||
command=partial(self.labelframe.toggle_mute, "mute"),
|
||||
command=partial(self.labelframe.pause_updates, self.labelframe.toggle_mute),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Mute{self.index}.TButton'}",
|
||||
variable=self.labelframe.mute,
|
||||
)
|
||||
@ -283,7 +289,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
self.button_on = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
text="ON",
|
||||
command=self.labelframe.set_on,
|
||||
command=partial(self.labelframe.pause_updates, self.labelframe.set_on),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}On{self.index}.TButton'}",
|
||||
variable=self.labelframe.on,
|
||||
)
|
||||
@ -486,7 +492,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_a, param),
|
||||
command=partial(
|
||||
self.configframe.pause_updates, self.configframe.toggle_a, param
|
||||
),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||
variable=self.configframe.phys_out_params_vars[
|
||||
self.configframe.phys_out_params.index(param)
|
||||
@ -507,7 +515,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_b, param),
|
||||
command=partial(
|
||||
self.configframe.pause_updates, self.configframe.toggle_b, param
|
||||
),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||
variable=self.configframe.virt_out_params_vars[
|
||||
self.configframe.virt_out_params.index(param)
|
||||
@ -528,7 +538,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_p, param),
|
||||
command=partial(
|
||||
self.configframe.pause_updates, self.configframe.toggle_p, param
|
||||
),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||
variable=self.configframe.param_vars[i],
|
||||
)
|
||||
@ -578,10 +590,16 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
column=0, row=0, columnspan=2, sticky=(tk.W)
|
||||
)
|
||||
self.configframe.busmode_button.bind(
|
||||
"<Button-1>", self.configframe.rotate_bus_modes_right
|
||||
"<Button-1>",
|
||||
partial(
|
||||
self.configframe.pause_updates, self.configframe.rotate_bus_modes_right
|
||||
),
|
||||
)
|
||||
self.configframe.busmode_button.bind(
|
||||
"<Button-3>", self.configframe.rotate_bus_modes_left
|
||||
"<Button-3>",
|
||||
partial(
|
||||
self.configframe.pause_updates, self.configframe.rotate_bus_modes_left
|
||||
),
|
||||
)
|
||||
|
||||
def create_param_buttons(self):
|
||||
@ -589,7 +607,9 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_p, param),
|
||||
command=partial(
|
||||
self.configframe.pause_updates, self.configframe.toggle_p, param
|
||||
),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||
variable=self.configframe.param_vars[i],
|
||||
)
|
||||
|
@ -88,17 +88,27 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
self.parent.target.event.add("ldirty")
|
||||
self.after(500, self.resume_updates)
|
||||
|
||||
def pause_updates(self, func, *args):
|
||||
"""function wrapper, adds a 50ms delay on updates"""
|
||||
_base_values.run_update = False
|
||||
|
||||
func(*args)
|
||||
|
||||
self.after(50, 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()
|
||||
+ (
|
||||
_configuration.mwscroll_step
|
||||
if event.delta > 0
|
||||
else -_configuration.mwscroll_step
|
||||
round(
|
||||
self.gain.get()
|
||||
+ (
|
||||
_configuration.mwscroll_step
|
||||
if event.delta > 0
|
||||
else -_configuration.mwscroll_step
|
||||
),
|
||||
1,
|
||||
)
|
||||
)
|
||||
if self.gain.get() > 12:
|
||||
@ -106,7 +116,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
elif self.gain.get() < -60:
|
||||
self.gain.set(-60)
|
||||
self.setter("gain", self.gain.get())
|
||||
self.after(1, self.resume_updates)
|
||||
self.gainlabel.set(round(self.gain.get(), 1))
|
||||
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
|
@ -68,6 +68,14 @@ class Config(ttk.Frame):
|
||||
self.parent.target.event.add("ldirty")
|
||||
self.after(350, self.resume_updates)
|
||||
|
||||
def pause_updates(self, func, *args):
|
||||
"""function wrapper, adds a 50ms delay on updates"""
|
||||
_base_values.run_update = False
|
||||
|
||||
func(*args)
|
||||
|
||||
self.after(50, self.resume_updates)
|
||||
|
||||
def resume_updates(self):
|
||||
_base_values.run_update = True
|
||||
|
||||
|
@ -78,6 +78,14 @@ class GainLayer(ttk.LabelFrame):
|
||||
self.parent.target.event.add("ldirty")
|
||||
self.after(500, self.resume_updates)
|
||||
|
||||
def pause_updates(self, func, *args):
|
||||
"""function wrapper, adds a 50ms delay on updates"""
|
||||
_base_values.run_update = False
|
||||
|
||||
func(*args)
|
||||
|
||||
self.after(50, self.resume_updates)
|
||||
|
||||
def resume_updates(self):
|
||||
_base_values.run_update = True
|
||||
|
||||
|
@ -224,7 +224,10 @@ class Menus(tk.Menu):
|
||||
]
|
||||
|
||||
def action_invoke_voicemeeter(self, cmd):
|
||||
getattr(self.target.command, cmd)()
|
||||
if fn := getattr(self.target.command, cmd):
|
||||
fn()
|
||||
if cmd == "shutdown":
|
||||
self.parent.destroy()
|
||||
|
||||
def action_set_voicemeeter(self, cmd, val=True):
|
||||
if cmd == "lock":
|
||||
@ -398,7 +401,7 @@ class Menus(tk.Menu):
|
||||
self.vban.logout()
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(self.vmr.type)
|
||||
self.parent.build_app(kind, None)
|
||||
self.parent.build_app(kind)
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
target_menu.entryconfig(0, state="normal")
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
|
Loading…
x
Reference in New Issue
Block a user