diff --git a/pdm.lock b/pdm.lock index e463c44..1183dfe 100644 --- a/pdm.lock +++ b/pdm.lock @@ -271,13 +271,13 @@ files = [ [[package]] name = "voicemeeter-api" -version = "2.4.9" +version = "2.4.10" 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.9-py3-none-any.whl", hash = "sha256:a09fd07fe3799cd5c880d491048da81d94e49aa97ec753aa1f9289acd27be965"}, - {file = "voicemeeter_api-2.4.9.tar.gz", hash = "sha256:47ad614a8b9ccb0b4e47acf65666ce0f0537a0890fffda9949e995bea70e679c"}, + {file = "voicemeeter_api-2.4.10-py3-none-any.whl", hash = "sha256:2f75acb7b472e56b6bd8d4f1141f32d948c55ef9b30d5a08e085a1c8e76e2464"}, + {file = "voicemeeter_api-2.4.10.tar.gz", hash = "sha256:1d8dfc1e8922179f8b97c90b90b9ed051082018c6af5feb1d48250140a02d40c"}, ] diff --git a/pyproject.toml b/pyproject.toml index 3f32e04..7d770f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ authors = [ dependencies = [ "pysimplegui>=4.60.5", "pyparsing>=3.1.1", - "voicemeeter-api>=2.4.9", + "voicemeeter-api>=2.4.10", ] requires-python = ">=3.10,<3.12" readme = "README.md" diff --git a/src/nvda_voicemeeter/compound.py b/src/nvda_voicemeeter/compound.py index 2f371df..884a8de 100644 --- a/src/nvda_voicemeeter/compound.py +++ b/src/nvda_voicemeeter/compound.py @@ -41,7 +41,13 @@ class CompSlider(psg.Slider): def __init__(self, vm, index, param): self.vm = vm self.index = index - super().__init__(**self.default_params(param)) + super().__init__( + disable_number_display=True, + expand_x=True, + enable_events=True, + orientation="horizontal", + **self.default_params(param), + ) def default_params(self, param): match param: @@ -50,10 +56,6 @@ class CompSlider(psg.Slider): "range": (-24, 24), "default_value": self.vm.strip[self.index].comp.gainin, "resolution": 0.1, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } case "RATIO": @@ -61,10 +63,6 @@ class CompSlider(psg.Slider): "range": (1, 8), "default_value": self.vm.strip[self.index].comp.ratio, "resolution": 0.1, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } case "THRESHOLD": @@ -72,10 +70,6 @@ class CompSlider(psg.Slider): "range": (-40, -3), "default_value": self.vm.strip[self.index].comp.threshold, "resolution": 0.1, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } case "ATTACK": @@ -83,10 +77,6 @@ class CompSlider(psg.Slider): "range": (0, 200), "default_value": self.vm.strip[self.index].comp.attack, "resolution": 0.1, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } case "RELEASE": @@ -94,10 +84,6 @@ class CompSlider(psg.Slider): "range": (0, 5000), "default_value": self.vm.strip[self.index].comp.release, "resolution": 0.1, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } case "KNEE": @@ -105,10 +91,6 @@ class CompSlider(psg.Slider): "range": (0, 1), "default_value": self.vm.strip[self.index].comp.knee, "resolution": 0.01, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } case "OUTPUT GAIN": @@ -116,10 +98,6 @@ class CompSlider(psg.Slider): "range": (-24, 24), "default_value": self.vm.strip[self.index].comp.gainout, "resolution": 0.01, - "disable_number_display": True, - "expand_x": True, - "enable_events": True, - "orientation": "horizontal", "key": f"COMPRESSOR||SLIDER {param}", } diff --git a/src/nvda_voicemeeter/popup.py b/src/nvda_voicemeeter/popup.py index 6bf3a31..a5c2e10 100644 --- a/src/nvda_voicemeeter/popup.py +++ b/src/nvda_voicemeeter/popup.py @@ -41,7 +41,7 @@ class Popup: filepath = values["Browse"] break self.window.nvda.speak(button) - case [[button], ["KEY", "ENTER"]]: + case [_, ["KEY", "ENTER"]]: popup.find_element_with_focus().click() self.logger.debug(f"parsed::{parsed_cmd}") popup.close() @@ -84,7 +84,7 @@ class Popup: match parsed_cmd := self.window.parser.match.parseString(event): case [[button], ["FOCUS", "IN"]]: self.window.nvda.speak(button) - case [[button], ["KEY", "ENTER"]]: + case [_, ["KEY", "ENTER"]]: popup.find_element_with_focus().click() case ["Ok"]: data = values @@ -152,7 +152,7 @@ class Popup: util.open_context_menu_for_buttonmenu(popup, f"BUFFER {driver}") case [[button], ["FOCUS", "IN"]]: self.window.nvda.speak(button) - case [[button], ["KEY", "ENTER"]]: + case [_, ["KEY", "ENTER"]]: popup.find_element_with_focus().click() self.logger.debug(f"parsed::{parsed_cmd}") popup.close() @@ -169,20 +169,319 @@ class Popup: steps = (_make_comp_frame,) for step in steps: layout.append([step()]) - layout.append([psg.Button("Auto Makeup", size=(12, 1)), psg.Button("Exit", size=(8, 1))]) + layout.append([psg.Button("MAKEUP", size=(12, 1)), psg.Button("Exit", size=(8, 1))]) - popup = psg.Window(title, layout, finalize=True) + 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("", "||FOCUS IN") + popup[f"COMPRESSOR||SLIDER {param}"].bind("", "||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( + f"<{event}-{direction}>", f"||KEY {direction.upper()} {event_id}" + ) + popup[f"COMPRESSOR||SLIDER {param}"].bind( + f"", f"||KEY SHIFT {direction.upper()} {event_id}" + ) + popup[f"COMPRESSOR||SLIDER {param}"].bind( + f"", f"||KEY CTRL {direction.upper()} {event_id}" + ) + if param == "RELEASE": + popup[f"COMPRESSOR||SLIDER {param}"].bind( + f"", f"||KEY ALT {direction.upper()} {event_id}" + ) + if param == "RELEASE": + popup[f"COMPRESSOR||SLIDER {param}"].bind( + f"", f"||KEY CTRL ALT {direction.upper()} {event_id}" + ) + popup["MAKEUP"].bind("", "||FOCUS IN") + popup["MAKEUP"].bind("", "||KEY ENTER") popup["Exit"].bind("", "||FOCUS IN") popup["Exit"].bind("", "||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 [[button], ["KEY", "ENTER"]]: + case [["COMPRESSOR"], ["SLIDER", param]]: + setattr(self.window.vm.strip[index].comp, param.lower(), values[event]) + case [["COMPRESSOR"], ["SLIDER", param], ["FOCUS", "IN"]]: + self.window.nvda.speak(f"{param} {values[f'COMPRESSOR||SLIDER {param}']}") + case [ + ["COMPRESSOR"], + ["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].comp, param.lower()) + + match input_direction: + case "RIGHT" | "UP": + if param == "KNEE": + val += 0.1 + else: + val += 1 + case "LEFT" | "DOWN": + if param == "KNEE": + val -= 0.1 + 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)) + + setattr(self.window.vm.strip[index].comp, param.lower(), val) + popup[f"COMPRESSOR||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 [ + ["COMPRESSOR"], + ["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].comp, param.lower()) + + match input_direction: + case "RIGHT" | "UP": + if param == "KNEE": + val += 0.3 + elif param == "RELEASE": + val += 5 + else: + val += 3 + case "LEFT" | "DOWN": + if param == "KNEE": + val -= 0.3 + elif param == "RELEASE": + val -= 5 + 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)) + + setattr(self.window.vm.strip[index].comp, param.lower(), val) + popup[f"COMPRESSOR||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 [ + ["COMPRESSOR"], + ["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].comp, param.lower()) + + match input_direction: + case "RIGHT" | "UP": + if param == "KNEE": + val += 0.01 + else: + val += 0.1 + case "LEFT" | "DOWN": + if param == "KNEE": + val -= 0.01 + 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)) + + setattr(self.window.vm.strip[index].comp, param.lower(), val) + popup[f"COMPRESSOR||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 [ + ["COMPRESSOR"], + ["SLIDER", "RELEASE"], + ["KEY", "ALT", "LEFT" | "RIGHT" as input_direction, "PRESS" | "RELEASE" as e], + ]: + if e == "PRESS": + self.window.vm.event.pdirty = False + val = self.window.vm.strip[index].comp.release + + match input_direction: + case "RIGHT" | "UP": + val += 10 + case "LEFT" | "DOWN": + val -= 10 + + val = util.check_bounds(val, (0, 5000)) + self.window.vm.strip[index].comp.release = val + popup[f"COMPRESSOR||SLIDER {param}"].update(value=val) + self.window.nvda.speak(str(round(val, 1))) + else: + self.window.vm.event.pdirty = True + case [ + ["COMPRESSOR"], + ["SLIDER", "RELEASE"], + ["KEY", "CTRL", "ALT", "LEFT" | "RIGHT" as input_direction, "PRESS" | "RELEASE" as e], + ]: + if e == "PRESS": + self.window.vm.event.pdirty = False + val = self.window.vm.strip[index].comp.release + + match input_direction: + case "RIGHT" | "UP": + val += 50 + case "LEFT" | "DOWN": + val -= 50 + + val = util.check_bounds(val, (0, 5000)) + self.window.vm.strip[index].comp.release = val + popup[f"COMPRESSOR||SLIDER {param}"].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"]]: + if direction == "INPUT": + self.window.vm.strip[index].comp.gainin = values[event] + else: + self.window.vm.strip[index].comp.gainout = values[event] + case [["COMPRESSOR"], ["SLIDER", "INPUT" | "OUTPUT" as direction, "GAIN"], ["FOCUS", "IN"]]: + label = f"{direction} GAIN" + self.window.nvda.speak(f"{label} {values[f'COMPRESSOR||SLIDER {label}']}") + case [ + ["COMPRESSOR"], + ["SLIDER", "INPUT" | "OUTPUT" as direction, "GAIN"], + ["KEY", "LEFT" | "RIGHT" | "UP" | "DOWN" as input_direction, "PRESS" | "RELEASE" as e], + ]: + if e == "PRESS": + self.window.vm.event.pdirty = False + if direction == "INPUT": + val = self.window.vm.strip[index].comp.gainin + else: + val = self.window.vm.strip[index].comp.gainout + + match input_direction: + case "RIGHT" | "UP": + val += 1 + case "LEFT" | "DOWN": + val -= 1 + + val = util.check_bounds(val, (-24, 24)) + if direction == "INPUT": + 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.window.nvda.speak(str(round(val, 1))) + else: + self.window.vm.event.pdirty = True + case [ + ["COMPRESSOR"], + ["SLIDER", "INPUT" | "OUTPUT" as direction, "GAIN"], + ["KEY", "CTRL", "LEFT" | "RIGHT" | "UP" | "DOWN" as input_direction, "PRESS" | "RELEASE" as e], + ]: + if e == "PRESS": + self.window.vm.event.pdirty = False + if direction == "INPUT": + val = self.window.vm.strip[index].comp.gainin + else: + val = self.window.vm.strip[index].comp.gainout + + match input_direction: + case "RIGHT" | "UP": + val += 3 + case "LEFT" | "DOWN": + val -= 3 + + val = util.check_bounds(val, (-24, 24)) + if direction == "INPUT": + 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.window.nvda.speak(str(round(val, 1))) + else: + self.window.vm.event.pdirty = True + case [ + ["COMPRESSOR"], + ["SLIDER", "INPUT" | "OUTPUT" as direction, "GAIN"], + ["KEY", "SHIFT", "LEFT" | "RIGHT" | "UP" | "DOWN" as input_direction, "PRESS" | "RELEASE" as e], + ]: + if e == "PRESS": + self.window.vm.event.pdirty = False + if direction == "INPUT": + val = self.window.vm.strip[index].comp.gainin + else: + val = self.window.vm.strip[index].comp.gainout + + match input_direction: + case "RIGHT" | "UP": + val += 0.1 + case "LEFT" | "DOWN": + val -= 0.1 + + val = util.check_bounds(val, (-24, 24)) + if direction == "INPUT": + 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.window.nvda.speak(str(round(val, 1))) + else: + self.window.vm.event.pdirty = True + + 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'}") + else: + self.window.nvda.speak(button) + case [_, ["KEY", "ENTER"]]: popup.find_element_with_focus().click() self.logger.debug(f"parsed::{parsed_cmd}") popup.close() diff --git a/src/nvda_voicemeeter/window.py b/src/nvda_voicemeeter/window.py index 975b19e..500aed7 100644 --- a/src/nvda_voicemeeter/window.py +++ b/src/nvda_voicemeeter/window.py @@ -311,7 +311,7 @@ class NVDAVMWindow(psg.Window): continue match parsed_cmd := self.parser.match.parseString(event): - # Slide mode + # Slider mode case [["ALT", "LEFT" | "RIGHT" | "UP" | "DOWN" as direction], ["PRESS" | "RELEASE" as e]]: if mode: self.write_event_value(f"SLIDER MODE {direction}||{e}", mode.split()[0]) @@ -446,6 +446,19 @@ class NVDAVMWindow(psg.Window): self[f"BUS {index}||LABEL"].update(value=label) self.cache["labels"][f"BUS {index}||LABEL"] = label + # Advanced popups (settings, comp, gate) + case ["CTRL-A"]: + match values["tabgroup"]: + case "tab||Settings": + self.write_event_value("ADVANCED SETTINGS", None) + case "tab||Physical Strip": + if values["tabgroup||Physical Strip"] == "tab||Physical Strip||sliders": + 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") + # Menus case [["Restart", "Audio", "Engine"], ["MENU"]]: self.perform_long_operation(self.vm.command.restart, "ENGINE RESTART||END") @@ -620,18 +633,6 @@ class NVDAVMWindow(psg.Window): self.write_event_value(f"INSERT CHECKBOX||{in_num} {channel}", val) # Advanced Settings - case ["CTRL-A"]: - match values["tabgroup"]: - case "tab||Settings": - self.write_event_value("ADVANCED SETTINGS", None) - case "tab||Physical Strip": - if values["tabgroup||Physical Strip"] == "tab||Physical Strip||sliders": - 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") - case ["ADVANCED SETTINGS"]: if values["tabgroup"] == "tab||Settings": self.popup.advanced_settings(title="Advanced Settings") @@ -810,7 +811,7 @@ class NVDAVMWindow(psg.Window): val = util.check_bounds(val, (-40, 12)) self.vm.strip[int(index)].limit = val self[f"STRIP {index}||SLIDER {param}"].update(value=val) - self.nvda.speak(str(val)) + self.nvda.speak(str(round(val, 1))) else: self.vm.event.pdirty = True case [ @@ -877,7 +878,10 @@ class NVDAVMWindow(psg.Window): val = util.check_bounds(val, (-40, 12)) self.vm.strip[int(index)].limit = val self[f"STRIP {index}||SLIDER {param}"].update(value=val) - self.nvda.speak(f"{param} {val}") + if param == "LIMIT": + self.nvda.speak(str(int(val))) + else: + self.nvda.speak(str(round(val, 1))) else: self.vm.event.pdirty = True case [ @@ -944,7 +948,10 @@ class NVDAVMWindow(psg.Window): val = util.check_bounds(val, (-40, 12)) self.vm.strip[int(index)].limit = val self[f"STRIP {index}||SLIDER {param}"].update(value=val) - self.nvda.speak(f"{param} {val}") + if param == "LIMIT": + self.nvda.speak(str(int(val))) + else: + self.nvda.speak(str(round(val, 1))) else: self.vm.event.pdirty = True case [["STRIP", index], ["SLIDER", param], ["KEY", "CTRL", "SHIFT", "R"]]: @@ -1053,7 +1060,7 @@ class NVDAVMWindow(psg.Window): val = util.check_bounds(val, (-60, 12)) self.vm.bus[int(index)].gain = val self[f"BUS {index}||SLIDER GAIN"].update(value=val) - self.nvda.speak(str(val)) + self.nvda.speak(str(round(val, 1))) else: self.vm.event.pdirty = True case [ @@ -1072,7 +1079,7 @@ class NVDAVMWindow(psg.Window): val = util.check_bounds(val, (-60, 12)) self.vm.bus[int(index)].gain = val self[f"BUS {index}||SLIDER GAIN"].update(value=val) - self.nvda.speak(str(val)) + self.nvda.speak(str(round(val, 1))) else: self.vm.event.pdirty = True case [ @@ -1091,7 +1098,7 @@ class NVDAVMWindow(psg.Window): val = util.check_bounds(val, (-60, 12)) self.vm.bus[int(index)].gain = val self[f"BUS {index}||SLIDER GAIN"].update(value=val) - self.nvda.speak(str(val)) + self.nvda.speak(str(round(val, 1))) else: self.vm.event.pdirty = True case [["BUS", index], ["SLIDER", "GAIN"], ["KEY", "CTRL", "SHIFT", "R"]]: