Compare commits

...

22 Commits

Author SHA1 Message Date
09f052b4a3 defines slider mode nav events 2023-09-20 10:40:09 +01:00
577f1fbdbb fixes limit resolution 2023-09-20 10:38:30 +01:00
f9614f44ac popups moved into popup.py 2023-09-20 10:01:48 +01:00
9b757b743b shortens the spoken feedback
for most of the controls.

needs more testing...
2023-09-19 23:19:39 +01:00
7f8f6f2b6c adds more navigation binds.
adds slider modes.
2023-09-19 22:22:44 +01:00
0c806b8e4c add guard clauses for nav binds 2023-09-18 23:07:09 +01:00
645361c731 bump to pre-release a1 2023-09-18 09:38:37 +01:00
6baa0000c8 Merge remote-tracking branch 'origin/dev' into add-nav-binds 2023-09-18 09:18:44 +01:00
d722154325 adds ctrl+shift+r slider reset bind
closes #9

patch bump
2023-09-18 09:12:12 +01:00
c94d763569 adds some quick navigation binds 2023-09-18 00:07:35 +01:00
ac14b7ad0d adds sliders to on_pdirty
bump to b2
2023-09-17 14:49:42 +01:00
78b7e01fa6 bump to 0.2.1.b1 2023-09-17 12:36:15 +01:00
42fad86905 defines bass,mid,treble ctrl events 2023-09-17 12:23:04 +01:00
6c54d28210 define focus out for bass,mid,treble 2023-09-17 12:09:38 +01:00
d958167ded fixes comp, gate, denoiser ctrl+up/down/left/right 2023-09-17 12:07:14 +01:00
0570138963 bass, mid, treble slider events defined 2023-09-17 11:44:15 +01:00
1fdb39a041 events defined for sliders
resolution fix for limit slider
2023-09-17 11:25:11 +01:00
3d7b33c8be fix events for sliders 2023-09-17 10:18:22 +01:00
cf5c8f81aa bind events added for sliders 2023-09-17 09:30:38 +01:00
c248808c7b LabelSlider compound element added
labels for sliders added.

all sliders now focusable.

todo: add events/binds for sliders
2023-09-16 23:13:56 +01:00
d93f078365 upd type annotations
cleanup global namespace.
2023-09-16 20:28:07 +01:00
e0f36b2411 start laying out parameter sliders. 2023-09-16 10:45:56 +01:00
6 changed files with 770 additions and 286 deletions

View File

@@ -1,6 +1,6 @@
[project]
name = "nvda_voicemeeter"
version = "0.2.1a3"
version = "0.2.2a1"
description = "A Voicemeeter app compatible with NVDA"
authors = [
{ name = "onyx-and-iris", email = "code@onyxandiris.online" },

View File

@@ -1,13 +1,7 @@
import PySimpleGUI as psg
from .util import (
get_asio_checkbox_index,
get_input_device_list,
get_insert_checkbox_index,
get_output_device_list,
get_patch_composite_list,
get_tabs_labels,
)
from . import util
from .compound import LabelSlider
class Builder:
@@ -91,7 +85,7 @@ class Builder:
return psg.Tab(identifier, [[tabgroup]], key=f"tab||{identifier}")
tabs = []
for tab in get_tabs_labels():
for tab in util.get_tabs_labels():
tabs.append(_make_tabs(tab))
tab_group = psg.TabGroup([tabs], change_submits=True, enable_events=True, key="tabgroup")
@@ -116,7 +110,7 @@ class Builder:
"""tab0 row0 represents hardware ins"""
def add_physical_device_opts(layout):
devices = get_input_device_list(self.vm)
devices = util.get_input_device_list(self.vm)
devices.append("- remove device selection -")
layout.append(
[
@@ -147,7 +141,7 @@ class Builder:
psg.ButtonMenu(
f"A{i + 1}",
size=(6, 3),
menu_def=["", get_output_device_list(i, self.vm)],
menu_def=["", util.get_output_device_list(i, self.vm)],
key=f"HARDWARE OUT||A{i + 1}",
)
for i in range(num_outs)
@@ -167,7 +161,7 @@ class Builder:
[
psg.Spin(
nums,
initial_value=self.window.cache["asio"][f"ASIO CHECKBOX||{get_asio_checkbox_index(0, i)}"],
initial_value=self.window.cache["asio"][f"ASIO CHECKBOX||{util.get_asio_checkbox_index(0, i)}"],
size=2,
enable_events=True,
key=f"ASIO CHECKBOX||IN{i} 0",
@@ -178,7 +172,7 @@ class Builder:
[
psg.Spin(
nums,
initial_value=self.window.cache["asio"][f"ASIO CHECKBOX||{get_asio_checkbox_index(1, i)}"],
initial_value=self.window.cache["asio"][f"ASIO CHECKBOX||{util.get_asio_checkbox_index(1, i)}"],
size=2,
enable_events=True,
key=f"ASIO CHECKBOX||IN{i} 1",
@@ -199,7 +193,7 @@ class Builder:
"""tab0 row3 represents patch composite"""
def add_physical_device_opts(layout):
outputs = get_patch_composite_list(self.vm.kind)
outputs = util.get_patch_composite_list(self.vm.kind)
layout.append(
[
psg.ButtonMenu(
@@ -226,7 +220,7 @@ class Builder:
[
psg.Checkbox(
text=channel,
default=self.vm.patch.insert[get_insert_checkbox_index(self.kind, j, i)].on,
default=self.vm.patch.insert[util.get_insert_checkbox_index(self.kind, j, i)].on,
enable_events=True,
key=f"INSERT CHECKBOX||IN{i} {j}",
)
@@ -239,7 +233,7 @@ class Builder:
[
psg.Checkbox(
text=channel,
default=self.vm.patch.insert[get_insert_checkbox_index(self.kind, j, i)].on,
default=self.vm.patch.insert[util.get_insert_checkbox_index(self.kind, j, i)].on,
enable_events=True,
key=f"INSERT CHECKBOX||IN{i} {j}",
)
@@ -314,6 +308,7 @@ class Builder:
def add_gain_slider(layout):
layout.append(
[
psg.Text("Gain"),
psg.Slider(
range=(-60, 12),
default_value=self.vm.strip[i].gain,
@@ -323,13 +318,36 @@ class Builder:
enable_events=True,
orientation="horizontal",
key=f"STRIP {i}||SLIDER GAIN",
)
),
]
)
outputs = []
[step(outputs) for step in (add_gain_slider,)]
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL||SLIDER")
def add_param_sliders(layout):
layout.append([LabelSlider(self.window, i, param) for param in util.get_slider_params(i, self.vm)])
def add_limit_slider(layout):
layout.append(
[
psg.Text("Limit"),
psg.Slider(
range=(-40, 12),
default_value=self.vm.strip[i].limit,
resolution=1,
disable_number_display=True,
expand_x=True,
enable_events=True,
orientation="horizontal",
key=f"STRIP {i}||SLIDER LIMIT",
),
]
)
layout = []
steps = (add_gain_slider, add_param_sliders)
if self.kind.name in ("banana", "potato"):
steps += (add_limit_slider,)
[step(layout) for step in steps]
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], layout, key=f"STRIP {i}||LABEL||SLIDER")
def make_tab1_slider_rows(self) -> psg.Frame:
layout = [[self.make_tab1_slider_row(i)] for i in range(self.kind.phys_in)]
@@ -382,6 +400,7 @@ class Builder:
def add_gain_slider(layout):
layout.append(
[
psg.Text("Gain"),
psg.Slider(
range=(-60, 12),
default_value=self.vm.strip[i].gain,
@@ -391,13 +410,45 @@ class Builder:
enable_events=True,
orientation="horizontal",
key=f"STRIP {i}||SLIDER GAIN",
)
),
]
)
outputs = []
[step(outputs) for step in (add_gain_slider,)]
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL||SLIDER")
def add_param_sliders(layout):
if self.kind.name in ("basic", "banana"):
for param in util.get_slider_params(i, self.vm):
layout.append([LabelSlider(self.window, i, param, range_=(-12, 12))])
else:
layout.append(
[
LabelSlider(self.window, i, param, range_=(-12, 12))
for param in util.get_slider_params(i, self.vm)
]
)
def add_limit_slider(layout):
layout.append(
[
psg.Text("Limit"),
psg.Slider(
range=(-40, 12),
default_value=self.vm.strip[i].limit,
resolution=1,
disable_number_display=True,
expand_x=True,
enable_events=True,
orientation="horizontal",
key=f"STRIP {i}||SLIDER LIMIT",
),
]
)
layout = []
steps = (add_gain_slider, add_param_sliders)
if self.kind.name in ("banana", "potato"):
steps += (add_limit_slider,)
[step(layout) for step in steps]
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], layout, key=f"STRIP {i}||LABEL||SLIDER")
def make_tab2_slider_rows(self) -> psg.Frame:
layout = [

View File

@@ -0,0 +1,35 @@
import PySimpleGUI as psg
class LabelSlider(psg.Frame):
"""Compound Label Slider element"""
def __init__(self, parent, i, param, range_=(0, 10), *args, **kwargs):
self.parent = parent
if param in ("AUDIBILITY", "DENOISER"):
size = 7
else:
size = 4
layout = [
[
psg.Text(param.capitalize(), size=size),
psg.Slider(
range=range_,
default_value=self.default_value(i, param),
resolution=0.1,
disable_number_display=True,
size=(12, 16),
expand_x=True,
enable_events=True,
orientation="horizontal",
key=f"STRIP {i}||SLIDER {param}",
),
]
]
super().__init__(None, layout=layout, border_width=0, pad=0, *args, **kwargs)
def default_value(self, i, param):
target = getattr(self.parent.vm.strip[i], param.lower())
if param in ("COMP", "GATE", "DENOISER"):
return target.knob
return target

View File

@@ -0,0 +1,162 @@
import logging
from pathlib import Path
import PySimpleGUI as psg
from . import util
logger = logging.getLogger(__name__)
class Popup:
def __init__(self, window):
self.window = window
self.logger = logger.getChild(type(self).__name__)
def save_as(self, message, title=None, initial_folder=None):
layout = [
[psg.Text(message)],
[
psg.FileSaveAs("Browse", initial_folder=str(initial_folder), file_types=(("XML", ".xml"),)),
psg.Button("Cancel"),
],
]
popup = psg.Window(title, layout, finalize=True)
popup["Browse"].bind("<FocusIn>", "||FOCUS IN")
popup["Browse"].bind("<Return>", "||KEY ENTER")
popup["Cancel"].bind("<FocusIn>", "||FOCUS IN")
popup["Cancel"].bind("<Return>", "||KEY ENTER")
filepath = None
while True:
event, values = popup.read()
self.logger.debug(f"event::{event}")
self.logger.debug(f"values::{values}")
if event in (psg.WIN_CLOSED, "Cancel"):
break
match parsed_cmd := self.window.parser.match.parseString(event):
case [[button], ["FOCUS", "IN"]]:
if values["Browse"]:
filepath = values["Browse"]
break
self.window.nvda.speak(button)
case [[button], ["KEY", "ENTER"]]:
popup.find_element_with_focus().click()
self.logger.debug(f"parsed::{parsed_cmd}")
popup.close()
if filepath:
return Path(filepath)
def rename(self, message, title=None, tab=None):
if tab == "Physical Strip":
upper = self.window.kind.phys_in + 1
elif tab == "Virtual Strip":
upper = self.window.kind.virt_in + 1
elif tab == "Buses":
upper = self.window.kind.num_bus + 1
layout = [
[psg.Text(message)],
[
[
psg.Spin(
list(range(1, upper)), initial_value=1, size=2, enable_events=True, key=f"Index", readonly=True
),
psg.Input(key="Edit"),
],
[psg.Button("Ok"), psg.Button("Cancel")],
],
]
popup = psg.Window(title, layout, finalize=True)
popup["Index"].bind("<FocusIn>", "||FOCUS IN")
popup["Edit"].bind("<FocusIn>", "||FOCUS IN")
popup["Ok"].bind("<FocusIn>", "||FOCUS IN")
popup["Ok"].bind("<Return>", "||KEY ENTER")
popup["Cancel"].bind("<FocusIn>", "||FOCUS IN")
popup["Cancel"].bind("<Return>", "||KEY ENTER")
data = {}
while True:
event, values = popup.read()
self.logger.debug(f"event::{event}")
self.logger.debug(f"values::{values}")
if event in (psg.WIN_CLOSED, "Cancel"):
break
match parsed_cmd := self.window.parser.match.parseString(event):
case ["Index"]:
val = values["Index"]
self.window.nvda.speak(f"Index {val}")
case [[button], ["FOCUS", "IN"]]:
if button == "Index":
val = values["Index"]
self.window.nvda.speak(f"Index {val}")
else:
self.window.nvda.speak(button)
case [[button], ["KEY", "ENTER"]]:
popup.find_element_with_focus().click()
case ["Ok"]:
data = values
break
self.logger.debug(f"parsed::{parsed_cmd}")
popup.close()
return data
def advanced_settings(self, title):
def _make_buffering_frame() -> psg.Frame:
buffer = [
[
psg.ButtonMenu(
driver,
size=(14, 2),
menu_def=["", util.get_asio_samples_list(driver)],
key=f"BUFFER {driver}",
)
for driver in ("MME", "WDM", "KS", "ASIO")
],
]
return psg.Frame("BUFFERING", buffer)
layout = []
steps = (_make_buffering_frame,)
for step in steps:
layout.append([step()])
layout.append([psg.Button("Exit", size=(8, 2))])
popup = psg.Window(title, layout, finalize=True)
buttonmenu_opts = {"takefocus": 1, "highlightthickness": 1}
for driver in ("MME", "WDM", "KS", "ASIO"):
popup[f"BUFFER {driver}"].Widget.config(**buttonmenu_opts)
popup[f"BUFFER {driver}"].bind("<FocusIn>", "||FOCUS IN")
popup[f"BUFFER {driver}"].bind("<space>", "||KEY SPACE", propagate=False)
popup[f"BUFFER {driver}"].bind("<Return>", "||KEY ENTER", propagate=False)
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.parser.match.parseString(event):
case ["BUFFER MME" | "BUFFER WDM" | "BUFFER KS" | "BUFFER ASIO"]:
if values[event] == "Default":
if "MME" in event:
val = 1024
elif "WDM" in event or "KS" in event:
val = 512
else:
val = 0
else:
val = int(values[event])
driver = event.split()[1]
self.vm.set(f"option.buffer.{driver.lower()}", val)
self.TKroot.after(200, self.window.nvda.speak, f"{driver} BUFFER {val if val else 'default'}")
case [["BUFFER", driver], ["FOCUS", "IN"]]:
val = int(self.vm.get(f"option.buffer.{driver.lower()}"))
self.window.nvda.speak(f"{driver} BUFFER {val if val else 'default'}")
case [["BUFFER", driver], ["KEY", "SPACE" | "ENTER"]]:
util.open_context_menu_for_buttonmenu(popup, f"BUFFER {driver}")
case [[button], ["FOCUS", "IN"]]:
self.window.nvda.speak(button)
case [[button], ["KEY", "ENTER"]]:
popup.find_element_with_focus().click()
self.logger.debug(f"parsed::{parsed_cmd}")
popup.close()

View File

@@ -1,3 +1,6 @@
from typing import Iterable
def get_asio_checkbox_index(channel, num) -> int:
if channel == 0:
return 2 * num - 2
@@ -8,8 +11,7 @@ def get_insert_checkbox_index(kind, channel, num) -> int:
if num <= kind.phys_in:
if channel == 0:
return 2 * num - 2
else:
return 2 * num - 1
return 2 * num - 1
return (2 * kind.phys_in) + (8 * (num - kind.phys_in - 1)) + channel
@@ -130,10 +132,21 @@ def get_bus_modes(vm) -> list:
]
def check_bounds(val, bounds):
def check_bounds(val, bounds: tuple) -> int | float:
lower, upper = bounds
if val > upper:
val = upper
elif val < lower:
val = lower
return val
def get_slider_params(i, vm) -> Iterable:
if i < vm.kind.phys_in:
if vm.kind.name == "basic":
return ("AUDIBILITY",)
if vm.kind.name == "banana":
return ("COMP", "GATE")
if vm.kind.name == "potato":
return ("COMP", "GATE", "DENOISER")
return ("BASS", "MID", "TREBLE")

File diff suppressed because it is too large Load Diff