mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2024-11-15 17:40:52 +00:00
3c1abdd2ac
fix peak in watch levels
279 lines
9.0 KiB
Python
279 lines
9.0 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk, messagebox as msg
|
|
from functools import partial
|
|
from math import log
|
|
|
|
from .data import _base_vals
|
|
|
|
|
|
class GainLayer(ttk.LabelFrame):
|
|
"""Concrete class representing a single gainlayer"""
|
|
|
|
def __init__(self, parent, index, j):
|
|
super().__init__(parent)
|
|
self._parent = parent
|
|
self.index = index
|
|
self.j = j
|
|
self.gain = tk.DoubleVar()
|
|
self.level = tk.DoubleVar()
|
|
self.on = tk.BooleanVar()
|
|
self.s = self._parent._parent.styletable
|
|
|
|
self.sync()
|
|
self._make_widgets()
|
|
|
|
self.col_row_configure()
|
|
self.watch_pdirty()
|
|
self.watch_levels()
|
|
|
|
@property
|
|
def target(self):
|
|
"""returns the current interface"""
|
|
_target = self._parent.target
|
|
return _target.strip[self.index].gainlayer[self.j]
|
|
|
|
def getter(self, param):
|
|
if param in dir(self.target):
|
|
return getattr(self.target, param)
|
|
|
|
def setter(self, param, value):
|
|
if param in dir(self.target):
|
|
setattr(self.target, param, value)
|
|
|
|
def reset_gain(self, *args):
|
|
self.setter("gain", 0)
|
|
self.gain.set(0)
|
|
self._parent._parent.nav_frame.info_text.set(0)
|
|
|
|
def scale_enter(self, *args):
|
|
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
|
|
|
def scale_leave(self, *args):
|
|
self._parent._parent.nav_frame.info_text.set("")
|
|
|
|
def scale_press(self, *args):
|
|
_base_vals.in_scale_button_1 = True
|
|
|
|
def scale_release(self, *args):
|
|
_base_vals.in_scale_button_1 = False
|
|
|
|
def scale_callback(self, *args):
|
|
"""callback function for scale widget"""
|
|
self.setter("gain", self.gain.get())
|
|
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
|
|
|
def set_on(self):
|
|
"""enables a gainlayer. sets its button colour"""
|
|
setattr(
|
|
self._parent.target.strip[self.index],
|
|
self._parent.buses[self.j],
|
|
self.on.get(),
|
|
)
|
|
if not _base_vals.using_theme:
|
|
self.s.configure(
|
|
f"On.TButton",
|
|
background=f'{"green" if self.on.get() else "white"}',
|
|
)
|
|
|
|
def convert_level(self, val):
|
|
if _base_vals.vban_connected:
|
|
return round(-val * 0.01, 1)
|
|
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
|
|
|
def _make_widgets(self):
|
|
"""Creates a progressbar, scale, on button and config button for a single channel"""
|
|
# Progress bar
|
|
self.pb = ttk.Progressbar(
|
|
self,
|
|
maximum=100,
|
|
orient="vertical",
|
|
mode="determinate",
|
|
variable=self.level,
|
|
)
|
|
self.pb.grid(column=0, row=0)
|
|
|
|
# Scale
|
|
self.scale = ttk.Scale(
|
|
self,
|
|
from_=12.0,
|
|
to=-60.0,
|
|
orient="vertical",
|
|
variable=self.gain,
|
|
command=self.scale_callback,
|
|
length=self._parent.height,
|
|
)
|
|
self.scale.grid(column=1, row=0)
|
|
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
|
self.scale.bind("<Button-1>", self.scale_press)
|
|
self.scale.bind("<Enter>", self.scale_enter)
|
|
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
|
self.scale.bind("<Leave>", self.scale_leave)
|
|
|
|
# On button
|
|
self.button_on = ttk.Checkbutton(
|
|
self,
|
|
text="ON",
|
|
command=self.set_on,
|
|
style=f"{'Toggle.TButton' if _base_vals.using_theme else 'On.TButton'}",
|
|
variable=self.on,
|
|
)
|
|
self.button_on.grid(column=0, row=1, columnspan=2)
|
|
|
|
def col_row_configure(self):
|
|
[
|
|
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
|
for child in self.winfo_children()
|
|
if isinstance(child, ttk.Checkbutton)
|
|
]
|
|
[
|
|
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S))
|
|
for child in self.winfo_children()
|
|
if isinstance(child, ttk.Progressbar) or isinstance(child, ttk.Scale)
|
|
]
|
|
self.columnconfigure(0, minsize=36)
|
|
self.columnconfigure(1, minsize=36)
|
|
self.rowconfigure(1, minsize=70)
|
|
|
|
def watch_pdirty(self):
|
|
self.after(1, self.watch_pdirty_step)
|
|
|
|
def watch_pdirty_step(self):
|
|
"""keeps params synced but ensures sliders are responsive"""
|
|
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
|
self.sync()
|
|
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
|
|
|
def sync(self):
|
|
"""sync params with voicemeeter"""
|
|
retval = self.getter("label")
|
|
if len(retval) > 10:
|
|
retval = f"{retval[:8]}.."
|
|
self.configure(text=retval)
|
|
self.gain.set(self.getter("gain"))
|
|
self.on.set(
|
|
getattr(
|
|
self._parent.target.strip[self.index],
|
|
self._parent.buses[self.j],
|
|
)
|
|
)
|
|
|
|
def watch_levels(self):
|
|
self.after(1, self.watch_levels_step)
|
|
|
|
def watch_levels_step(self):
|
|
if not _base_vals.dragging:
|
|
if self._parent._parent.ldirty:
|
|
if self.index <= self._parent.phys_in:
|
|
vals = (
|
|
self.convert_level(
|
|
self._parent._parent.strip_levels[self.index * 2]
|
|
),
|
|
self.convert_level(
|
|
self._parent._parent.strip_levels[self.index * 2 + 1]
|
|
),
|
|
)
|
|
else:
|
|
vals = (
|
|
self.convert_level(
|
|
self._parent._parent.strip_levels[
|
|
self._parent.phys_in * 2
|
|
+ (self.index - self._parent.phys_in) * 8
|
|
]
|
|
),
|
|
self.convert_level(
|
|
self._parent._parent.strip_levels[
|
|
self._parent.phys_in * 2
|
|
+ (self.index - self._parent.phys_in) * 8
|
|
+ 1
|
|
]
|
|
),
|
|
)
|
|
peak = vals[0] if vals[0] > vals[1] else vals[1]
|
|
self.level.set(
|
|
(
|
|
0
|
|
if self._parent._parent.channel_frame.strips[
|
|
self.index
|
|
].mute.get()
|
|
else 100 + (peak - 18) + self.gain.get()
|
|
)
|
|
)
|
|
self.after(
|
|
_base_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
|
self.watch_levels_step,
|
|
)
|
|
|
|
|
|
class SubMixFrame(ttk.Frame):
|
|
def __init__(self, parent):
|
|
super().__init__(parent)
|
|
self._parent = parent
|
|
self.phys_in, self.virt_in = parent.kind.ins
|
|
self.phys_out, self.virt_out = parent.kind.outs
|
|
self.buses = tuple(f"A{i+1}" for i in range(self.phys_out)) + tuple(
|
|
f"B{i+1}" for i in range(self.virt_out)
|
|
)
|
|
defaults = {
|
|
"width": 80,
|
|
"height": 150,
|
|
}
|
|
self.configuration = defaults | self.configuration
|
|
self.width = self.configuration["width"]
|
|
self.height = self.configuration["height"]
|
|
|
|
self.gainlayers = [
|
|
GainLayer(self, index, _base_vals.submixes) for index in range(8)
|
|
]
|
|
[
|
|
gainlayer.grid(row=0, column=self.gainlayers.index(gainlayer))
|
|
for gainlayer in self.gainlayers
|
|
]
|
|
|
|
self.col_row_configure()
|
|
|
|
# destroy any without label text
|
|
for i, gainlayer in enumerate(self.gainlayers):
|
|
gainlayer.grid(row=0, column=i)
|
|
if not gainlayer.cget("text"):
|
|
self.columnconfigure(i, minsize=0)
|
|
gainlayer.grid_remove()
|
|
|
|
self.watch_pdirty()
|
|
|
|
@property
|
|
def target(self):
|
|
"""returns the current interface"""
|
|
return self._parent.target
|
|
|
|
@property
|
|
def configuration(self):
|
|
return self._parent.configuration["channel"]
|
|
|
|
@configuration.setter
|
|
def configuration(self, val):
|
|
self._parent.configuration["channel"] = val
|
|
|
|
def col_row_configure(self):
|
|
[
|
|
self.columnconfigure(i, minsize=self.width)
|
|
for i, _ in enumerate(self.gainlayers)
|
|
]
|
|
[self.rowconfigure(0, minsize=130) for i, _ in enumerate(self.gainlayers)]
|
|
|
|
def watch_pdirty(self):
|
|
self.after(1, self.watch_pdirty_step)
|
|
|
|
def watch_pdirty_step(self):
|
|
if self._parent.pdirty:
|
|
self.watch_labels()
|
|
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
|
|
|
def watch_labels(self):
|
|
for i, gainlayer in enumerate(self.gainlayers):
|
|
if not self.target.strip[gainlayer.index].label:
|
|
self.columnconfigure(i, minsize=0)
|
|
gainlayer.grid_remove()
|
|
else:
|
|
self.columnconfigure(i, minsize=80)
|
|
gainlayer.grid()
|