GateSlider added to compound

Advanced Gate slider layout and events defined
needs more testing.
This commit is contained in:
onyx-and-iris 2023-09-26 15:53:43 +01:00
parent 01e80dc4f6
commit af602e087d
4 changed files with 290 additions and 65 deletions

View File

@ -1,6 +1,6 @@
[project] [project]
name = "nvda_voicemeeter" name = "nvda_voicemeeter"
version = "0.4.2a1" version = "0.5.0a1"
description = "A Voicemeeter app compatible with NVDA" description = "A Voicemeeter app compatible with NVDA"
authors = [ authors = [
{ name = "onyx-and-iris", email = "code@onyxandiris.online" }, { name = "onyx-and-iris", email = "code@onyxandiris.online" },
@ -35,16 +35,60 @@ shell = "build.ps1"
line-length = 119 line-length = 119
[tool.ruff] [tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. select = [
select = ["E", "F"] "E",
# Avoid enforcing line-length violations (`E501`). Let Black deal with this. "F",
ignore = ["E501"] ]
ignore = [
# Allow autofix for all enabled rules (when `--fix`) is provided. "E501",
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"] ]
fixable = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"I",
"N",
"Q",
"S",
"T",
"W",
"ANN",
"ARG",
"BLE",
"COM",
"DJ",
"DTZ",
"EM",
"ERA",
"EXE",
"FBT",
"ICN",
"INP",
"ISC",
"NPY",
"PD",
"PGH",
"PIE",
"PL",
"PT",
"PTH",
"PYI",
"RET",
"RSE",
"RUF",
"SIM",
"SLF",
"TCH",
"TID",
"TRY",
"UP",
"YTT",
]
unfixable = [] unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [ exclude = [
".bzr", ".bzr",
".direnv", ".direnv",
@ -68,19 +112,15 @@ exclude = [
"node_modules", "node_modules",
"venv", "venv",
] ]
# Same as Black.
line-length = 119 line-length = 119
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Assume Python 3.10
target-version = "py310" target-version = "py310"
[tool.ruff.mccabe] [tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10 max-complexity = 10
[tool.ruff.per-file-ignores] [tool.ruff.per-file-ignores]
"__init__.py" = ["E402", "F401"] # Ignore unused import and variable not accessed violations "__init__.py" = [
"E402",
"F401",
]

View File

@ -1,5 +1,9 @@
from typing import Union
import PySimpleGUI as psg import PySimpleGUI as psg
from . import util
class LabelSlider(psg.Frame): class LabelSlider(psg.Frame):
"""Compound Label Slider Strip element""" """Compound Label Slider Strip element"""
@ -46,6 +50,7 @@ class CompSlider(psg.Slider):
expand_x=True, expand_x=True,
enable_events=True, enable_events=True,
orientation="horizontal", orientation="horizontal",
key=f"COMPRESSOR||SLIDER {param}",
**self.default_params(param), **self.default_params(param),
) )
@ -57,42 +62,36 @@ class CompSlider(psg.Slider):
"default_value": self.vm.strip[self.index].comp.gainin, "default_value": self.vm.strip[self.index].comp.gainin,
"resolution": 0.1, "resolution": 0.1,
"disabled": True, "disabled": True,
"key": f"COMPRESSOR||SLIDER {param}",
} }
case "RATIO": case "RATIO":
return { return {
"range": (1, 8), "range": (1, 8),
"default_value": self.vm.strip[self.index].comp.ratio, "default_value": self.vm.strip[self.index].comp.ratio,
"resolution": 0.1, "resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
} }
case "THRESHOLD": case "THRESHOLD":
return { return {
"range": (-40, -3), "range": (-40, -3),
"default_value": self.vm.strip[self.index].comp.threshold, "default_value": self.vm.strip[self.index].comp.threshold,
"resolution": 0.1, "resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
} }
case "ATTACK": case "ATTACK":
return { return {
"range": (0, 200), "range": (0, 200),
"default_value": self.vm.strip[self.index].comp.attack, "default_value": self.vm.strip[self.index].comp.attack,
"resolution": 0.1, "resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
} }
case "RELEASE": case "RELEASE":
return { return {
"range": (0, 5000), "range": (0, 5000),
"default_value": self.vm.strip[self.index].comp.release, "default_value": self.vm.strip[self.index].comp.release,
"resolution": 0.1, "resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
} }
case "KNEE": case "KNEE":
return { return {
"range": (0, 1), "range": (0, 1),
"default_value": self.vm.strip[self.index].comp.knee, "default_value": self.vm.strip[self.index].comp.knee,
"resolution": 0.01, "resolution": 0.01,
"key": f"COMPRESSOR||SLIDER {param}",
} }
case "OUTPUT GAIN": case "OUTPUT GAIN":
return { return {
@ -100,18 +99,102 @@ class CompSlider(psg.Slider):
"default_value": self.vm.strip[self.index].comp.gainout, "default_value": self.vm.strip[self.index].comp.gainout,
"resolution": 0.01, "resolution": 0.01,
"disabled": True, "disabled": True,
"key": f"COMPRESSOR||SLIDER {param}",
} }
@staticmethod
def check_bounds(param, val):
match param:
case "RATIO":
val = util.check_bounds(val, (1, 8))
case "THRESHOLD":
val = util.check_bounds(val, (-40, -3))
case "ATTACK":
val = util.check_bounds(val, (0, 200))
case "RELEASE":
val = util.check_bounds(val, (0, 5000))
case "KNEE":
val = util.check_bounds(val, (0, 1))
return val
class LabelSliderCompressor(psg.Frame):
"""Compound Label Slider Compressor element"""
def __init__(self, parent, index, param, *args, **kwargs): class GateSlider(psg.Slider):
def __init__(self, vm, index, param):
self.vm = vm
self.index = index
super().__init__(
disable_number_display=True,
expand_x=True,
enable_events=True,
orientation="horizontal",
key=f"GATE||SLIDER {param}",
**self.default_params(param),
)
def default_params(self, param):
match param:
case "THRESHOLD":
return {
"range": (-60, -10),
"default_value": self.vm.strip[self.index].gate.threshold,
"resolution": 0.1,
}
case "DAMPING":
return {
"range": (-60, -10),
"default_value": self.vm.strip[self.index].gate.damping,
"resolution": 0.1,
}
case "BPSIDECHAIN":
return {
"range": (100, 4000),
"default_value": self.vm.strip[self.index].gate.bpsidechain,
"resolution": 1,
}
case "ATTACK":
return {
"range": (0, 1000),
"default_value": self.vm.strip[self.index].gate.attack,
"resolution": 0.1,
}
case "HOLD":
return {
"range": (0, 5000),
"default_value": self.vm.strip[self.index].gate.hold,
"resolution": 0.1,
}
case "RELEASE":
return {
"range": (0, 5000),
"default_value": self.vm.strip[self.index].gate.release,
"resolution": 0.1,
}
@staticmethod
def check_bounds(param, val):
match param:
case "THRESHOLD":
val = util.check_bounds(val, (-60, -10))
case "DAMPING MAX":
val = util.check_bounds(val, (-60, -10))
case "BPSIDECHAIN":
val = util.check_bounds(val, (100, 4000))
case "ATTACK":
val = util.check_bounds(val, (0, 1000))
case "HOLD":
val = util.check_bounds(val, (0, 5000))
case "RELEASE":
val = util.check_bounds(val, (0, 5000))
return val
class LabelSliderAdvanced(psg.Frame):
"""Compound Label Slider element for Advanced Comp|Gate"""
def __init__(self, parent, index, param, slider_cls: Union[CompSlider, GateSlider], *args, **kwargs):
layout = [ layout = [
[ [
psg.Text(param.capitalize(), size=8), psg.Text(param.capitalize(), size=8),
CompSlider(parent.vm, index, param), slider_cls(parent.vm, index, param),
] ]
] ]
super().__init__(None, layout=layout, border_width=0, pad=0, *args, **kwargs) super().__init__(None, layout=layout, border_width=0, pad=0, *args, **kwargs)

View File

@ -4,7 +4,7 @@ from pathlib import Path
import PySimpleGUI as psg import PySimpleGUI as psg
from . import util from . import util
from .compound import LabelSliderCompressor from .compound import CompSlider, GateSlider, LabelSliderAdvanced
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -160,7 +160,7 @@ class Popup:
def compressor(self, index, title=None): def compressor(self, index, title=None):
def _make_comp_frame() -> psg.Frame: def _make_comp_frame() -> psg.Frame:
comp_layout = [ comp_layout = [
[LabelSliderCompressor(self.window, index, param)] [LabelSliderAdvanced(self.window, index, param, CompSlider)]
for param in ("INPUT GAIN", "RATIO", "THRESHOLD", "ATTACK", "RELEASE", "KNEE", "OUTPUT GAIN") for param in ("INPUT GAIN", "RATIO", "THRESHOLD", "ATTACK", "RELEASE", "KNEE", "OUTPUT GAIN")
] ]
return psg.Frame("ADVANCED COMPRESSOR", comp_layout) return psg.Frame("ADVANCED COMPRESSOR", comp_layout)
@ -232,17 +232,7 @@ class Popup:
else: else:
val -= 1 val -= 1
match param: val = CompSlider.check_bounds(param, val)
case "RATIO":
val = util.check_bounds(val, (1, 8))
case "THRESHOLD":
val = util.check_bounds(val, (-40, -3))
case "ATTACK":
val = util.check_bounds(val, (0, 200))
case "RELEASE":
val = util.check_bounds(val, (0, 5000))
case "KNEE":
val = util.check_bounds(val, (0, 1))
setattr(self.window.vm.strip[index].comp, param.lower(), val) setattr(self.window.vm.strip[index].comp, param.lower(), val)
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val) popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
@ -277,17 +267,7 @@ class Popup:
else: else:
val -= 3 val -= 3
match param: val = CompSlider.check_bounds(param, val)
case "RATIO":
val = util.check_bounds(val, (1, 8))
case "THRESHOLD":
val = util.check_bounds(val, (-40, -3))
case "ATTACK":
val = util.check_bounds(val, (0, 200))
case "RELEASE":
val = util.check_bounds(val, (0, 5000))
case "KNEE":
val = util.check_bounds(val, (0, 1))
setattr(self.window.vm.strip[index].comp, param.lower(), val) setattr(self.window.vm.strip[index].comp, param.lower(), val)
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val) popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
@ -318,17 +298,7 @@ class Popup:
else: else:
val -= 0.1 val -= 0.1
match param: val = CompSlider.check_bounds(param, val)
case "RATIO":
val = util.check_bounds(val, (1, 8))
case "THRESHOLD":
val = util.check_bounds(val, (-40, -3))
case "ATTACK":
val = util.check_bounds(val, (0, 200))
case "RELEASE":
val = util.check_bounds(val, (0, 5000))
case "KNEE":
val = util.check_bounds(val, (0, 1))
setattr(self.window.vm.strip[index].comp, param.lower(), val) setattr(self.window.vm.strip[index].comp, param.lower(), val)
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val) popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
@ -477,10 +447,140 @@ class Popup:
self.window.nvda.speak("on" if val else "off") self.window.nvda.speak("on" if val else "off")
case [[button], ["FOCUS", "IN"]]: case [[button], ["FOCUS", "IN"]]:
if button == "MAKEUP": if button == "MAKEUP":
self.window.nvda.speak(f"{button} {'on' if self.window.vm.strip[index].comp.makeup else 'off'}") self.window.nvda.speak(
f"{button} {'on' if self.window.vm.strip[index].comp.makeup else 'off'}"
)
else: else:
self.window.nvda.speak(button) self.window.nvda.speak(button)
case [_, ["KEY", "ENTER"]]: case [_, ["KEY", "ENTER"]]:
popup.find_element_with_focus().click() popup.find_element_with_focus().click()
self.logger.debug(f"parsed::{parsed_cmd}") self.logger.debug(f"parsed::{parsed_cmd}")
popup.close() popup.close()
def gate(self, index, title=None):
def _make_gate_frame() -> psg.Frame:
gate_layout = [
[LabelSliderAdvanced(self.window, index, param, GateSlider)]
for param in ("THRESHOLD", "DAMPING", "BPSIDECHAIN", "ATTACK", "HOLD", "RELEASE")
]
return psg.Frame("ADVANCED GATE", gate_layout)
layout = []
steps = (_make_gate_frame,)
for step in steps:
layout.append([step()])
layout.append([psg.Button("Exit", size=(8, 1))])
popup = psg.Window(title, layout, return_keyboard_events=False, finalize=True)
buttonmenu_opts = {"takefocus": 1, "highlightthickness": 1}
for param in ("THRESHOLD", "DAMPING", "BPSIDECHAIN", "ATTACK", "HOLD", "RELEASE"):
popup[f"GATE||SLIDER {param}"].Widget.config(**buttonmenu_opts)
popup[f"GATE||SLIDER {param}"].bind("<FocusIn>", "||FOCUS IN")
popup[f"GATE||SLIDER {param}"].bind("<FocusOut>", "||FOCUS OUT")
for event in ("KeyPress", "KeyRelease"):
event_id = event.removeprefix("Key").upper()
for direction in ("Left", "Right", "Up", "Down"):
popup[f"GATE||SLIDER {param}"].bind(
f"<{event}-{direction}>", f"||KEY {direction.upper()} {event_id}"
)
popup[f"GATE||SLIDER {param}"].bind(
f"<Shift-{event}-{direction}>", f"||KEY SHIFT {direction.upper()} {event_id}"
)
popup[f"GATE||SLIDER {param}"].bind(
f"<Control-{event}-{direction}>", f"||KEY CTRL {direction.upper()} {event_id}"
)
popup["Exit"].bind("<FocusIn>", "||FOCUS IN")
popup["Exit"].bind("<Return>", "||KEY ENTER")
while True:
event, values = popup.read()
self.logger.debug(f"event::{event}")
self.logger.debug(f"values::{values}")
if event in (psg.WIN_CLOSED, "Exit"):
break
match parsed_cmd := self.window.parser.match.parseString(event):
case [["GATE"], ["SLIDER", param]]:
setattr(self.window.vm.strip[index].gate, param.lower(), values[event])
case [["GATE"], ["SLIDER", param], ["FOCUS", "IN"]]:
self.window.nvda.speak(f"{param} {values[f'GATE||SLIDER {param}']}")
case [
["GATE"],
["SLIDER", param],
["KEY", "LEFT" | "RIGHT" | "UP" | "DOWN" as input_direction, "PRESS" | "RELEASE" as e],
]:
if e == "PRESS":
self.window.vm.event.pdirty = False
val = getattr(self.window.vm.strip[index].gate, param.lower())
match input_direction:
case "RIGHT" | "UP":
val += 1
case "LEFT" | "DOWN":
val -= 1
val = GateSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].gate, param.lower(), val)
popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "KNEE":
self.window.nvda.speak(str(round(val, 2)))
else:
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [
["GATE"],
["SLIDER", param],
["KEY", "CTRL", "LEFT" | "RIGHT" | "UP" | "DOWN" as input_direction, "PRESS" | "RELEASE" as e],
]:
if e == "PRESS":
self.window.vm.event.pdirty = False
val = getattr(self.window.vm.strip[index].gate, param.lower())
match input_direction:
case "RIGHT" | "UP":
val += 3
case "LEFT" | "DOWN":
val -= 3
val = GateSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].gate, param.lower(), val)
popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "KNEE":
self.window.nvda.speak(str(round(val, 2)))
else:
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [
["GATE"],
["SLIDER", param],
["KEY", "SHIFT", "LEFT" | "RIGHT" | "UP" | "DOWN" as input_direction, "PRESS" | "RELEASE" as e],
]:
if e == "PRESS":
self.window.vm.event.pdirty = False
val = getattr(self.window.vm.strip[index].gate, param.lower())
match input_direction:
case "RIGHT" | "UP":
val += 0.1
case "LEFT" | "DOWN":
val -= 0.1
val = GateSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].gate, param.lower(), val)
popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "KNEE":
self.window.nvda.speak(str(round(val, 2)))
else:
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [_, ["KEY", "ENTER"]]:
popup.find_element_with_focus().click()
self.logger.debug(f"parsed::{parsed_cmd}")
popup.close()

View File

@ -458,6 +458,8 @@ class NVDAVMWindow(psg.Window):
_, index = identifier.split() _, index = identifier.split()
if "SLIDER COMP" in partial: if "SLIDER COMP" in partial:
self.popup.compressor(int(index), title="Advanced Compressor") self.popup.compressor(int(index), title="Advanced Compressor")
elif "SLIDER GATE" in partial:
self.popup.gate(int(index), title="Advanced Gate")
# Menus # Menus
case [["Restart", "Audio", "Engine"], ["MENU"]]: case [["Restart", "Audio", "Engine"], ["MENU"]]: