Compare commits

..

16 Commits

Author SHA1 Message Date
bdd570738a fix version bump... 2023-09-29 01:18:57 +01:00
71b137a9c2 use self.kind 2023-09-29 01:16:48 +01:00
912eb8c14d number of patch composite buttons fixed
for banana and potato kinds

voicemeeter-api dependency bump

patch bump
2023-09-29 01:09:18 +01:00
9cd65737e5 bump to version 0.5.1
closes #16
2023-09-28 23:35:02 +01:00
4a6ca2a353 check against slider modes explicitly
add enter/exit slider mode debug logging
2023-09-28 22:43:06 +01:00
cc99b14e89 define space bind event for bus buttons 2023-09-28 21:35:05 +01:00
23458debaa initial busmode buttonmenu implementation
bump to 0.5.1a1
Issue #16
2023-09-28 19:44:42 +01:00
496cc35321 fixes comp output gain resolution 2023-09-28 18:18:26 +01:00
d758db9dee lint/format fixes 2023-09-28 16:32:52 +01:00
876de55ad2 Advanced Comp|Gate sliders implemented
bump to 0.5.0

closes #14
2023-09-27 23:34:07 +01:00
eab4b1c6a9 fixes unboundlocalerror 2023-09-27 13:36:48 +01:00
0aeb33608f implements reset binds
Advanced Comp|Gate section added to README.

bump to version 0.5.0b1
2023-09-26 23:51:34 +01:00
5befe72ca1 adds pdirty events for advanced comp/gate 2023-09-26 19:38:55 +01:00
abab560281 +10, +50 step binds added for gate
bump to version 0.5.0a2
Initial gate slider implementation
for Issue #14
2023-09-26 17:20:15 +01:00
6882adb47b gate as no Knee parameter 2023-09-26 15:56:12 +01:00
af602e087d GateSlider added to compound
Advanced Gate slider layout and events defined
needs more testing.
2023-09-26 15:53:43 +01:00
10 changed files with 573 additions and 153 deletions

View File

@@ -107,12 +107,30 @@ All sliders may be controlled in three different ways:
- `Shift + Left|Right arrow` to move a slider by 0.1 steps.
- `Control + Left|Right arrow` to move a slider by 3 steps.
To reset a slider back to its default value you may use `Control + Shift + R`.
To rename a strip/bus channel focus on the channel in question and press `F2`. Then enter the new channel name into the text input widget and press the `Ok` button.
Pressing the `OK` button with an empty text input will clear the label. In this case the label will be read as a default value for that channel. For example, if the leftmost Strip label were cleared, the screen reader will now read `Hardware Input 1`.
Pressing `Cancel` will close the popup window with no affect on the label.
#### `Advanced Compressor|Gate`
For potato version only, you may access advanced Compressor and Gate sliders. Simply focus any Gate or Compressor slider and press `Control + A`. This will open a popup window where you can navigate between the different sliders with `TAB`. Move the sliders with the same binds you would for normal sliders. However, there are a couple of extra binds for certain controls.
For Compressor Release you may use:
- `Alt + Left|Right arrow` to move the slider by 10 steps.
- `Alt + Control + Left|Right arrow` to move the slider by 50 steps.
For Gate BP Sidechain, Attack, Hold, Release you may use:
- `Alt + Left|Right arrow` to move the slider by 10 steps.
- `Alt + Control + Left|Right arrow` to move the slider by 50 steps.
To reset a slider back to its default value you may use `Control + Shift + R`.
#### `Menu`
A single menu item `Voicemeeter` can be opened using `Alt` and then `v`. The menu allows you to:

6
pdm.lock generated
View File

@@ -251,13 +251,13 @@ files = [
[[package]]
name = "voicemeeter-api"
version = "2.4.10"
version = "2.4.11"
requires_python = ">=3.10,<4.0"
summary = "A Python wrapper for the Voiceemeter API"
dependencies = [
"tomli<3.0.0,>=2.0.1; python_version < \"3.11\"",
]
files = [
{file = "voicemeeter_api-2.4.10-py3-none-any.whl", hash = "sha256:2f75acb7b472e56b6bd8d4f1141f32d948c55ef9b30d5a08e085a1c8e76e2464"},
{file = "voicemeeter_api-2.4.10.tar.gz", hash = "sha256:1d8dfc1e8922179f8b97c90b90b9ed051082018c6af5feb1d48250140a02d40c"},
{file = "voicemeeter_api-2.4.11-py3-none-any.whl", hash = "sha256:7a1b290d90c851204438c18e2d343e568c242fcd1e664c5b88d4019a553d44e1"},
{file = "voicemeeter_api-2.4.11.tar.gz", hash = "sha256:875591ad326a7a13ef141536cca83953edcda81da256191bab3844bac46a0e70"},
]

View File

@@ -1,6 +1,6 @@
[project]
name = "nvda_voicemeeter"
version = "0.4.2a1"
version = "0.5.2"
description = "A Voicemeeter app compatible with NVDA"
authors = [
{ name = "onyx-and-iris", email = "code@onyxandiris.online" },
@@ -8,7 +8,7 @@ authors = [
dependencies = [
"pysimplegui>=4.60.5",
"pyparsing>=3.1.1",
"voicemeeter-api>=2.4.10",
"voicemeeter-api>=2.4.11",
]
requires-python = ">=3.10,<3.12"
readme = "README.md"
@@ -35,16 +35,60 @@ shell = "build.ps1"
line-length = 119
[tool.ruff]
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
select = ["E", "F"]
# Avoid enforcing line-length violations (`E501`). Let Black deal with this.
ignore = ["E501"]
# Allow autofix for all enabled rules (when `--fix`) is provided.
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"]
select = [
"E",
"F",
]
ignore = [
"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",
]
unfixable = []
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
@@ -68,19 +112,15 @@ exclude = [
"node_modules",
"venv",
]
# Same as Black.
line-length = 119
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Assume Python 3.10
target-version = "py310"
[tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10
[tool.ruff.per-file-ignores]
"__init__.py" = ["E402", "F401"] # Ignore unused import and variable not accessed violations
"__init__.py" = [
"E402",
"F401",
]

View File

@@ -197,7 +197,7 @@ class Builder:
"""tab0 row3 represents patch composite"""
def add_physical_device_opts(layout):
outputs = util.get_patch_composite_list(self.vm.kind)
outputs = util.get_patch_composite_list(self.kind)
layout.append(
[
psg.ButtonMenu(
@@ -206,7 +206,7 @@ class Builder:
menu_def=["", outputs],
key=f"PATCH COMPOSITE||PC{i + 1}",
)
for i in range(self.kind.phys_out)
for i in range(self.kind.composite)
]
)
@@ -478,18 +478,26 @@ class Builder:
"""tab3 row represents bus composite toggle"""
def add_strip_outputs(layout):
params = ["MONO", "EQ", "MUTE", "MODE"]
if self.vm.kind.name == "basic":
params = ["MONO", "EQ", "MUTE"]
if self.kind.name == "basic":
params.remove("EQ")
label = {"MODE": "BUSMODE"}
busmodes = [util._bus_mode_map[mode] for mode in util.get_bus_modes(self.vm)]
layout.append(
[
psg.Button(
label.get(param, param.capitalize()),
size=(12 if param == "MODE" else 6, 2),
key=f"BUS {i}||{param}",
)
for param in params
*[
psg.Button(
param.capitalize(),
size=(6, 2),
key=f"BUS {i}||{param}",
)
for param in params
],
psg.ButtonMenu(
"BUSMODE",
size=(12, 2),
menu_def=["", busmodes],
key=f"BUS {i}||MODE",
),
]
)

View File

@@ -33,7 +33,7 @@ def get_nvdapath():
try:
NVDA_PATH = Path(get_nvdapath()) / "nvda.exe"
except FileNotFoundError as e:
except FileNotFoundError:
NVDA_PATH = ""

View File

@@ -1,5 +1,9 @@
from typing import Union
import PySimpleGUI as psg
from . import util
class LabelSlider(psg.Frame):
"""Compound Label Slider Strip element"""
@@ -46,6 +50,7 @@ class CompSlider(psg.Slider):
expand_x=True,
enable_events=True,
orientation="horizontal",
key=f"COMPRESSOR||SLIDER {param}",
**self.default_params(param),
)
@@ -57,61 +62,144 @@ class CompSlider(psg.Slider):
"default_value": self.vm.strip[self.index].comp.gainin,
"resolution": 0.1,
"disabled": True,
"key": f"COMPRESSOR||SLIDER {param}",
}
case "RATIO":
return {
"range": (1, 8),
"default_value": self.vm.strip[self.index].comp.ratio,
"resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
}
case "THRESHOLD":
return {
"range": (-40, -3),
"default_value": self.vm.strip[self.index].comp.threshold,
"resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
}
case "ATTACK":
return {
"range": (0, 200),
"default_value": self.vm.strip[self.index].comp.attack,
"resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
}
case "RELEASE":
return {
"range": (0, 5000),
"default_value": self.vm.strip[self.index].comp.release,
"resolution": 0.1,
"key": f"COMPRESSOR||SLIDER {param}",
}
case "KNEE":
return {
"range": (0, 1),
"default_value": self.vm.strip[self.index].comp.knee,
"resolution": 0.01,
"key": f"COMPRESSOR||SLIDER {param}",
}
case "OUTPUT GAIN":
return {
"range": (-24, 24),
"default_value": self.vm.strip[self.index].comp.gainout,
"resolution": 0.01,
"resolution": 0.1,
"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):
label_map = {
"DAMPING": "Damping Max",
"BPSIDECHAIN": "BP Sidechain",
}
layout = [
[
psg.Text(param.capitalize(), size=8),
CompSlider(parent.vm, index, param),
psg.Text(label_map.get(param, param.title()), size=10),
slider_cls(parent.vm, index, param),
]
]
super().__init__(None, layout=layout, border_width=0, pad=0, *args, **kwargs)

View File

@@ -5,7 +5,7 @@ def _make_hardware_ins_cache(vm) -> dict:
def _make_hardware_outs_cache(vm) -> dict:
hw_outs = {**{f"HARDWARE OUT||A{i + 1}": vm.bus[i].device.name for i in range(vm.kind.phys_out)}}
if vm.kind.name == "basic":
hw_outs |= {f"HARDWARE OUT||A2": vm.bus[1].device.name}
hw_outs |= {"HARDWARE OUT||A2": vm.bus[1].device.name}
return hw_outs

View File

@@ -4,7 +4,7 @@ from pathlib import Path
import PySimpleGUI as psg
from . import util
from .compound import LabelSliderCompressor
from .compound import CompSlider, GateSlider, LabelSliderAdvanced
logger = logging.getLogger(__name__)
@@ -157,10 +157,26 @@ class Popup:
self.logger.debug(f"parsed::{parsed_cmd}")
popup.close()
def on_pdirty(self):
if self.popup.Title == "Advanced Compressor":
for param in ("RATIO", "THRESHOLD", "ATTACK", "RELEASE", "KNEE"):
self.popup[f"COMPRESSOR||SLIDER {param}"].update(
value=getattr(self.window.vm.strip[self.index].comp, param.lower())
)
self.popup["COMPRESSOR||SLIDER INPUT GAIN"].update(value=self.window.vm.strip[self.index].comp.gainin)
self.popup["COMPRESSOR||SLIDER OUTPUT GAIN"].update(value=self.window.vm.strip[self.index].comp.gainout)
elif self.popup.Title == "Advanced Gate":
for param in ("THRESHOLD", "DAMPING", "BPSIDECHAIN", "ATTACK", "HOLD", "RELEASE"):
self.popup[f"GATE||SLIDER {param}"].update(
value=getattr(self.window.vm.strip[self.index].gate, param.lower())
)
def compressor(self, index, title=None):
self.index = index
def _make_comp_frame() -> psg.Frame:
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")
]
return psg.Frame("ADVANCED COMPRESSOR", comp_layout)
@@ -171,37 +187,39 @@ class Popup:
layout.append([step()])
layout.append([psg.Button("MAKEUP", size=(12, 1)), psg.Button("Exit", size=(8, 1))])
popup = psg.Window(title, layout, return_keyboard_events=False, finalize=True)
self.popup = psg.Window(title, layout, return_keyboard_events=False, finalize=True)
buttonmenu_opts = {"takefocus": 1, "highlightthickness": 1}
for param in ("INPUT GAIN", "RATIO", "THRESHOLD", "ATTACK", "RELEASE", "KNEE", "OUTPUT GAIN"):
popup[f"COMPRESSOR||SLIDER {param}"].Widget.config(**buttonmenu_opts)
popup[f"COMPRESSOR||SLIDER {param}"].bind("<FocusIn>", "||FOCUS IN")
popup[f"COMPRESSOR||SLIDER {param}"].bind("<FocusOut>", "||FOCUS OUT")
self.popup[f"COMPRESSOR||SLIDER {param}"].Widget.config(**buttonmenu_opts)
self.popup[f"COMPRESSOR||SLIDER {param}"].bind("<FocusIn>", "||FOCUS IN")
self.popup[f"COMPRESSOR||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"COMPRESSOR||SLIDER {param}"].bind(
self.popup[f"COMPRESSOR||SLIDER {param}"].bind(
f"<{event}-{direction}>", f"||KEY {direction.upper()} {event_id}"
)
popup[f"COMPRESSOR||SLIDER {param}"].bind(
self.popup[f"COMPRESSOR||SLIDER {param}"].bind(
f"<Shift-{event}-{direction}>", f"||KEY SHIFT {direction.upper()} {event_id}"
)
popup[f"COMPRESSOR||SLIDER {param}"].bind(
self.popup[f"COMPRESSOR||SLIDER {param}"].bind(
f"<Control-{event}-{direction}>", f"||KEY CTRL {direction.upper()} {event_id}"
)
if param == "RELEASE":
popup[f"COMPRESSOR||SLIDER {param}"].bind(
self.popup[f"COMPRESSOR||SLIDER {param}"].bind(
f"<Alt-{event}-{direction}>", f"||KEY ALT {direction.upper()} {event_id}"
)
popup[f"COMPRESSOR||SLIDER {param}"].bind(
self.popup[f"COMPRESSOR||SLIDER {param}"].bind(
f"<Control-Alt-{event}-{direction}>", f"||KEY CTRL ALT {direction.upper()} {event_id}"
)
popup["MAKEUP"].bind("<FocusIn>", "||FOCUS IN")
popup["MAKEUP"].bind("<Return>", "||KEY ENTER")
popup["Exit"].bind("<FocusIn>", "||FOCUS IN")
popup["Exit"].bind("<Return>", "||KEY ENTER")
self.popup[f"COMPRESSOR||SLIDER {param}"].bind("<Control-Shift-KeyPress-R>", "||KEY CTRL SHIFT R")
self.popup["MAKEUP"].bind("<FocusIn>", "||FOCUS IN")
self.popup["MAKEUP"].bind("<Return>", "||KEY ENTER")
self.popup["Exit"].bind("<FocusIn>", "||FOCUS IN")
self.popup["Exit"].bind("<Return>", "||KEY ENTER")
self.window.vm.observer.add(self.on_pdirty)
while True:
event, values = popup.read()
event, values = self.popup.read()
self.logger.debug(f"event::{event}")
self.logger.debug(f"values::{values}")
if event in (psg.WIN_CLOSED, "Exit"):
@@ -232,20 +250,10 @@ class Popup:
else:
val -= 1
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))
val = CompSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].comp, param.lower(), val)
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
if param == "KNEE":
self.window.nvda.speak(str(round(val, 2)))
else:
@@ -277,20 +285,10 @@ class Popup:
else:
val -= 3
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))
val = CompSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].comp, param.lower(), val)
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
if param == "KNEE":
self.window.nvda.speak(str(round(val, 2)))
else:
@@ -318,20 +316,10 @@ class Popup:
else:
val -= 0.1
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))
val = CompSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].comp, param.lower(), val)
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
if param == "KNEE":
self.window.nvda.speak(str(round(val, 2)))
else:
@@ -355,7 +343,7 @@ class Popup:
val = util.check_bounds(val, (0, 5000))
self.window.vm.strip[index].comp.release = val
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
@@ -376,7 +364,7 @@ class Popup:
val = util.check_bounds(val, (0, 5000))
self.window.vm.strip[index].comp.release = val
popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
@@ -412,7 +400,7 @@ class Popup:
self.window.vm.strip[index].comp.gainin = val
else:
self.window.vm.strip[index].comp.gainout = val
popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
@@ -439,7 +427,7 @@ class Popup:
self.window.vm.strip[index].comp.gainin = val
else:
self.window.vm.strip[index].comp.gainout = val
popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
@@ -466,21 +454,262 @@ class Popup:
self.window.vm.strip[index].comp.gainin = val
else:
self.window.vm.strip[index].comp.gainout = val
popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=val)
self.popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [
["COMPRESSOR"],
["SLIDER", "INPUT" | "OUTPUT" as direction, "GAIN"],
["KEY", "CTRL", "SHIFT", "R"],
]:
if direction == "INPUT":
self.window.vm.strip[index].comp.gainin = 0
else:
self.window.vm.strip[index].comp.gainout = 0
self.popup[f"COMPRESSOR||SLIDER {direction} GAIN"].update(value=0)
self.window.nvda.speak(str(0))
case [["COMPRESSOR"], ["SLIDER", param], ["KEY", "CTRL", "SHIFT", "R"]]:
match param:
case "RATIO":
val = 1
case "THRESHOLD":
val = -20
case "ATTACK":
val = 10
case "RELEASE":
val = 50
case "KNEE":
val = 0.5
setattr(self.window.vm.strip[index].comp, param.lower(), val)
self.popup[f"COMPRESSOR||SLIDER {param}"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
case ["MAKEUP"]:
val = not self.window.vm.strip[index].comp.makeup
self.window.vm.strip[index].comp.makeup = val
self.window.nvda.speak("on" if val else "off")
case [[button], ["FOCUS", "IN"]]:
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:
self.window.nvda.speak(button)
case [_, ["KEY", "ENTER"]]:
popup.find_element_with_focus().click()
self.popup.find_element_with_focus().click()
self.logger.debug(f"parsed::{parsed_cmd}")
popup.close()
self.window.vm.observer.remove(self.on_pdirty)
self.popup.close()
def gate(self, index, title=None):
self.index = index
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))])
self.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"):
self.popup[f"GATE||SLIDER {param}"].Widget.config(**buttonmenu_opts)
self.popup[f"GATE||SLIDER {param}"].bind("<FocusIn>", "||FOCUS IN")
self.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"):
self.popup[f"GATE||SLIDER {param}"].bind(
f"<{event}-{direction}>", f"||KEY {direction.upper()} {event_id}"
)
self.popup[f"GATE||SLIDER {param}"].bind(
f"<Shift-{event}-{direction}>", f"||KEY SHIFT {direction.upper()} {event_id}"
)
self.popup[f"GATE||SLIDER {param}"].bind(
f"<Control-{event}-{direction}>", f"||KEY CTRL {direction.upper()} {event_id}"
)
if param in ("BPSIDECHAIN", "ATTACK", "HOLD", "RELEASE"):
self.popup[f"GATE||SLIDER {param}"].bind(
f"<Alt-{event}-{direction}>", f"||KEY ALT {direction.upper()} {event_id}"
)
self.popup[f"GATE||SLIDER {param}"].bind(
f"<Control-Alt-{event}-{direction}>", f"||KEY CTRL ALT {direction.upper()} {event_id}"
)
self.popup[f"GATE||SLIDER {param}"].bind("<Control-Shift-KeyPress-R>", "||KEY CTRL SHIFT R")
self.popup["Exit"].bind("<FocusIn>", "||FOCUS IN")
self.popup["Exit"].bind("<Return>", "||KEY ENTER")
self.window.vm.observer.add(self.on_pdirty)
while True:
event, values = self.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"]]:
label_map = {
"DAMPING": "Damping Max",
"BPSIDECHAIN": "BP Sidechain",
}
self.window.nvda.speak(f"{label_map.get(param, 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)
self.popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "BPSIDECHAIN":
self.window.nvda.speak(str(int(val)))
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)
self.popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "BPSIDECHAIN":
self.window.nvda.speak(str(int(val)))
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)
self.popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "BPSIDECHAIN":
self.window.nvda.speak(str(int(val)))
else:
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [
["GATE"],
["SLIDER", "BPSIDECHAIN" | "ATTACK" | "HOLD" | "RELEASE" as param],
["KEY", "ALT", "LEFT" | "RIGHT" 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 += 10
case "LEFT" | "DOWN":
val -= 10
val = GateSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].gate, param.lower(), val)
self.popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "BPSIDECHAIN":
self.window.nvda.speak(str(int(val)))
else:
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [
["GATE"],
["SLIDER", "BPSIDECHAIN" | "ATTACK" | "HOLD" | "RELEASE" as param],
["KEY", "CTRL", "ALT", "LEFT" | "RIGHT" 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 += 50
case "LEFT" | "DOWN":
val -= 50
val = GateSlider.check_bounds(param, val)
setattr(self.window.vm.strip[index].gate, param.lower(), val)
self.popup[f"GATE||SLIDER {param}"].update(value=val)
if param == "BPSIDECHAIN":
self.window.nvda.speak(str(int(val)))
else:
self.window.nvda.speak(str(round(val, 1)))
else:
self.window.vm.event.pdirty = True
case [["GATE"], ["SLIDER", param], ["KEY", "CTRL", "SHIFT", "R"]]:
match param:
case "THRESHOLD":
val = -60
case "DAMPING":
val = -60
case "BPSIDECHAIN":
val = 100
case "ATTACK":
val = 0
case "HOLD":
val = 500
case "RELEASE":
val = 1000
setattr(self.window.vm.strip[index].gate, param.lower(), val)
self.popup[f"GATE||SLIDER {param}"].update(value=val)
self.window.nvda.speak(str(round(val, 1)))
case [[button], ["FOCUS", "IN"]]:
self.window.nvda.speak(button)
case [_, ["KEY", "ENTER"]]:
self.popup.find_element_with_focus().click()
self.logger.debug(f"parsed::{parsed_cmd}")
self.window.vm.observer.remove(self.on_pdirty)
self.popup.close()

View File

@@ -48,8 +48,11 @@ def get_patch_composite_list(kind) -> list:
for i in range(kind.phys_out):
[temp.append(f"IN#{i + 1} {channel}") for channel in ("Left", "Right")]
for i in range(kind.phys_out, kind.phys_out + kind.virt_out):
[temp.append(f"IN#{i + 1} {channel}") for channel in ("Left", "Right", "Center", "LFE", "SL", "SR", "BL", "BR")]
temp.append(f"BUS Channel")
[
temp.append(f"IN#{i + 1} {channel}")
for channel in ("Left", "Right", "Center", "LFE", "SL", "SR", "BL", "BR")
]
temp.append("BUS Channel")
return temp
@@ -108,6 +111,24 @@ def get_channel_identifier_list(vm) -> list:
return identifiers
_bus_mode_map = {
"normal": "Normal",
"amix": "Mix Down A",
"bmix": "Mix Down B",
"repeat": "Stereo Repeat",
"composite": "Composite",
"tvmix": "Up Mix TV",
"upmix21": "Up Mix 2.1",
"upmix41": "Up Mix 4.1",
"upmix61": "Up Mix 6.1",
"centeronly": "Center Only",
"lfeonly": "Low Frequency Effect Only",
"rearonly": "Rear Only",
}
_bus_mode_map_reversed = dict((reversed(item) for item in _bus_mode_map.items()))
def get_bus_modes(vm) -> list:
if vm.kind.name == "basic":
return [
@@ -157,3 +178,17 @@ def get_full_slider_params(i, kind) -> Iterable:
if kind.name == "basic":
params.remove("LIMIT")
return params
def get_slider_modes() -> Iterable:
return (
"GAIN MODE",
"BASS MODE",
"MID MODE",
"TREBLE MODE",
"AUDIBILITY MODE",
"COMP MODE",
"GATE MODE",
"DENOISER MODE",
"LIMIT MODE",
)

View File

@@ -45,9 +45,9 @@ class NVDAVMWindow(psg.Window):
for i in range(self.kind.phys_out):
self[f"HARDWARE OUT||A{i + 1}"].Widget.config(**buttonmenu_opts)
if self.kind.name == "basic":
self[f"HARDWARE OUT||A2"].Widget.config(**buttonmenu_opts)
self["HARDWARE OUT||A2"].Widget.config(**buttonmenu_opts)
if self.kind.name != "basic":
[self[f"PATCH COMPOSITE||PC{i + 1}"].Widget.config(**buttonmenu_opts) for i in range(self.kind.phys_out)]
[self[f"PATCH COMPOSITE||PC{i + 1}"].Widget.config(**buttonmenu_opts) for i in range(self.kind.composite)]
slider_opts = {"takefocus": 1, "highlightthickness": 1}
for i in range(self.kind.num_strip):
for param in util.get_slider_params(i, self.kind):
@@ -57,6 +57,7 @@ class NVDAVMWindow(psg.Window):
self[f"STRIP {i}||SLIDER LIMIT"].Widget.config(**slider_opts)
for i in range(self.kind.num_bus):
self[f"BUS {i}||SLIDER GAIN"].Widget.config(**slider_opts)
self[f"BUS {i}||MODE"].Widget.config(**buttonmenu_opts)
if self.kind.name != "basic":
for i in range(self.kind.phys_out):
self[f"ASIO CHECKBOX||IN{i + 1} 0"].Widget.config(state="readonly")
@@ -179,20 +180,20 @@ class NVDAVMWindow(psg.Window):
self.bind(f"<Alt-Control-{event}-{direction}>", f"ALT CTRL {direction.upper()}||{event_id}")
# Hardware In
for i in range(self.vm.kind.phys_in):
for i in range(self.kind.phys_in):
self[f"HARDWARE IN||{i + 1}"].bind("<FocusIn>", "||FOCUS IN")
self[f"HARDWARE IN||{i + 1}"].bind("<space>", "||KEY SPACE", propagate=False)
self[f"HARDWARE IN||{i + 1}"].bind("<Return>", "||KEY ENTER", propagate=False)
# Hardware Out
for i in range(self.vm.kind.phys_out):
for i in range(self.kind.phys_out):
self[f"HARDWARE OUT||A{i + 1}"].bind("<FocusIn>", "||FOCUS IN")
self[f"HARDWARE OUT||A{i + 1}"].bind("<space>", "||KEY SPACE", propagate=False)
self[f"HARDWARE OUT||A{i + 1}"].bind("<Return>", "||KEY ENTER", propagate=False)
if self.vm.kind.name == "basic":
self[f"HARDWARE OUT||A2"].bind("<FocusIn>", "||FOCUS IN")
self[f"HARDWARE OUT||A2"].bind("<space>", "||KEY SPACE", propagate=False)
self[f"HARDWARE OUT||A2"].bind("<Return>", "||KEY ENTER", propagate=False)
if self.kind.name == "basic":
self["HARDWARE OUT||A2"].bind("<FocusIn>", "||FOCUS IN")
self["HARDWARE OUT||A2"].bind("<space>", "||KEY SPACE", propagate=False)
self["HARDWARE OUT||A2"].bind("<Return>", "||KEY ENTER", propagate=False)
# Patch ASIO
if self.kind.name != "basic":
@@ -202,7 +203,7 @@ class NVDAVMWindow(psg.Window):
# Patch Composite
if self.kind.name != "basic":
for i in range(self.vm.kind.phys_out):
for i in range(self.kind.composite):
self[f"PATCH COMPOSITE||PC{i + 1}"].bind("<FocusIn>", "||FOCUS IN")
self[f"PATCH COMPOSITE||PC{i + 1}"].bind("<space>", "||KEY SPACE", propagate=False)
self[f"PATCH COMPOSITE||PC{i + 1}"].bind("<Return>", "||KEY ENTER", propagate=False)
@@ -260,13 +261,16 @@ class NVDAVMWindow(psg.Window):
self[f"STRIP {i}||SLIDER {param}"].bind("<Control-Shift-KeyPress-R>", "||KEY CTRL SHIFT R")
# Bus Params
params = ["MONO", "EQ", "MUTE", "MODE"]
if self.vm.kind.name == "basic":
params = ["MONO", "EQ", "MUTE"]
if self.kind.name == "basic":
params.remove("EQ")
for i in range(self.kind.num_bus):
for param in params:
self[f"BUS {i}||{param}"].bind("<FocusIn>", "||FOCUS IN")
self[f"BUS {i}||{param}"].bind("<Return>", "||KEY ENTER")
self[f"BUS {i}||MODE"].bind("<FocusIn>", "||FOCUS IN")
self[f"BUS {i}||MODE"].bind("<space>", "||KEY SPACE", propagate=False)
self[f"BUS {i}||MODE"].bind("<Return>", "||KEY ENTER", propagate=False)
# Bus Sliders
for i in range(self.kind.num_bus):
@@ -300,13 +304,15 @@ class NVDAVMWindow(psg.Window):
self.logger.debug(f"values::{values}")
if event in (psg.WIN_CLOSED, "Exit"):
break
elif event.endswith("MODE"):
elif event in util.get_slider_modes():
mode = event
self.nvda.speak(f"{mode} enabled")
self.logger.debug(f"entered slider mode {mode}")
continue
elif event == "ESCAPE":
if mode:
self.nvda.speak(f"{mode} disabled")
self.logger.debug(f"exited from slider mode {mode}")
mode = None
continue
@@ -423,7 +429,7 @@ class NVDAVMWindow(psg.Window):
identifier, partial = focus.Key.split("||")
_, index = identifier.split()
index = int(index)
data = self.popup.rename("Label", index, title=f"Rename", tab=tab)
data = self.popup.rename("Label", index, title="Rename", tab=tab)
if not data: # cancel was pressed
continue
match tab:
@@ -456,8 +462,12 @@ class NVDAVMWindow(psg.Window):
if focus := self.find_element_with_focus():
identifier, partial = focus.key.split("||")
_, index = identifier.split()
if "SLIDER COMP" in partial:
self.popup.compressor(int(index), title="Advanced Compressor")
match self.kind.name:
case "potato":
if "SLIDER COMP" in partial:
self.popup.compressor(int(index), title="Advanced Compressor")
elif "SLIDER GATE" in partial:
self.popup.gate(int(index), title="Advanced Gate")
# Menus
case [["Restart", "Audio", "Engine"], ["MENU"]]:
@@ -595,14 +605,18 @@ class NVDAVMWindow(psg.Window):
val = values[f"PATCH COMPOSITE||{key}"]
index = int(key[-1]) - 1
self.vm.patch.composite[index].set(util.get_patch_composite_list(self.kind).index(val) + 1)
self.TKroot.after(200, self.nvda.speak, f"PATCH COMPOSITE {key[-1]} set {val}")
self.TKroot.after(200, self.nvda.speak, val)
case [["PATCH", "COMPOSITE"], [key], ["FOCUS", "IN"]]:
if self.find_element_with_focus() is not None:
if values[f"PATCH COMPOSITE||{key}"]:
val = values[f"PATCH COMPOSITE||{key}"]
else:
index = int(key[-1]) - 1
val = util.get_patch_composite_list(self.kind)[self.vm.patch.composite[index].get() - 1]
comp_index = self.vm.patch.composite[index].get()
if self.kind.name == "banana":
if comp_index == 64: # bus channel
comp_index = 0
val = util.get_patch_composite_list(self.kind)[comp_index - 1]
self.nvda.speak(f"Patch COMPOSITE {key[-1]} {val}")
case [["PATCH", "COMPOSITE"], [key], ["KEY", "SPACE" | "ENTER"]]:
util.open_context_menu_for_buttonmenu(self, f"PATCH COMPOSITE||{key}")
@@ -960,6 +974,7 @@ class NVDAVMWindow(psg.Window):
self.vm.strip[int(index)].gain = 0
self[f"STRIP {index}||SLIDER {param}"].update(value=0)
case "COMP" | "GATE" | "DENOISER":
target = getattr(self.vm.strip[int(index)], param.lower())
setattr(target, "knob", 0)
self[f"STRIP {index}||SLIDER {param}"].update(value=0)
case "AUDIBILITY":
@@ -971,7 +986,7 @@ class NVDAVMWindow(psg.Window):
case "LIMIT":
self.vm.strip[int(index)].limit = 12
self[f"STRIP {index}||SLIDER {param}"].update(value=12)
self.nvda.speak(f"{param} {12 if param == 'LABEL' else 0}")
self.nvda.speak(f"{12 if param == 'LIMIT' else 0}")
# Bus Params
case [["BUS", index], [param]]:
@@ -997,40 +1012,27 @@ class NVDAVMWindow(psg.Window):
"on" if val else "off",
)
case "MODE":
bus_modes = util.get_bus_modes(self.vm)
next_index = bus_modes.index(val) + 1
if next_index == len(bus_modes):
next_index = 0
next_bus = bus_modes[next_index]
phonetic = {
"amix": "Mix Down A",
"bmix": "Mix Down B",
"repeat": "Stereo Repeat",
"tvmix": "Up Mix TV",
"upmix21": "Up Mix 2.1",
"upmix41": "Up Mix 4.1",
"upmix61": "Up Mix 6.1",
"centeronly": "Center Only",
"lfeonly": "Low Frequency Effect Only",
"rearonly": "Rear Only",
}
setattr(self.vm.bus[int(index)].mode, next_bus, True)
self.cache["bus"][event] = next_bus
chosen = util._bus_mode_map_reversed[values[event]]
setattr(self.vm.bus[int(index)].mode, chosen, True)
self.cache["bus"][event] = chosen
self.TKroot.after(
200,
self.nvda.speak,
phonetic.get(next_bus, next_bus),
util._bus_mode_map[chosen],
)
case [["BUS", index], [param], ["FOCUS", "IN"]]:
if self.find_element_with_focus() is not None:
label = self.cache["labels"][f"BUS {index}||LABEL"]
val = self.cache["bus"][f"BUS {index}||{param}"]
if param == "MODE":
self.nvda.speak(f"{label} bus {param} {val}")
self.nvda.speak(f"{label} bus {param} {util._bus_mode_map[val]}")
else:
self.nvda.speak(f"{label} bus {param} {'on' if val else 'off'}")
case [["BUS", index], [param], ["KEY", "ENTER"]]:
self.find_element_with_focus().click()
self.nvda.speak(f"{label} {param} {'on' if val else 'off'}")
case [["BUS", index], [param], ["KEY", "SPACE" | "ENTER"]]:
if param == "MODE":
util.open_context_menu_for_buttonmenu(self, f"BUS {index}||MODE")
else:
self.find_element_with_focus().click()
# Bus Sliders
case [["BUS", index], ["SLIDER", "GAIN"]]:
@@ -1104,7 +1106,7 @@ class NVDAVMWindow(psg.Window):
case [["BUS", index], ["SLIDER", "GAIN"], ["KEY", "CTRL", "SHIFT", "R"]]:
self.vm.bus[int(index)].gain = 0
self[f"BUS {index}||SLIDER GAIN"].update(value=0)
self.nvda.speak(str(val))
self.nvda.speak(str(0))
# Unknown
case _: