Compare commits

...

7 Commits

Author SHA1 Message Date
ed5f9ae45b fixes hardware out A2 for basic kind.
patch bump
2023-09-06 16:20:04 +01:00
962b61f60a fixes k, mc layout for banana kind 2023-09-06 15:27:55 +01:00
b2e03aace1 add settings tab screenshot
Installation section added to readme
2023-09-06 15:07:59 +01:00
3a52655811 add Voicemeeter link 2023-09-06 05:29:23 +01:00
178d6e451b add screenshots 2023-09-06 00:58:09 +01:00
fc8ae11555 add badges 2023-09-06 00:42:49 +01:00
e4afe35fb2 initial README 2023-09-06 00:33:59 +01:00
10 changed files with 115 additions and 15 deletions

View File

@@ -1 +1,90 @@
# example-package [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
# NVDA Voicemeeter
A remote control app for [Voicemeeter][voicemeeter], designed to be used with the [NVDA screen reader][nvda].
This is still an early release but it should be usable.
<img src="./img/settings.png" width=350 alt="Image of Voicemeeter NVDA app settings tab">
## Requirements
- [NVDA screen reader][nvda]
- [NVDA's Controller Client files][controller_client]
- Python 3.10 or greater
### Installation
First clone the source files from this repository
`git clone https://github.com/onyx-and-iris/nvda-voicemeeter.git`
Then download the [Controller Client][controller_client] and place the files into the directory `controllerClient`.
If you want to get started quickly and easily I have uploaded some compiled versions of the app in the [Releases][releases] section.
### Run
Once the repository is downloaded and the controller client files in place you can launch the GUI with the following `__main__.py`:
```python
import voicemeeterlib
import nvda_voicemeeter
KIND_ID = "potato"
with voicemeeterlib.api(KIND_ID, sync=True) as vm:
with nvda_voicemeeter.draw(KIND_ID, vm) as window:
window.run()
```
### `KIND_ID`
May be one of the following:
- `basic`
- `banana`
- `potato`
### Use
The app presents four tabs `Settings`, `Physical Strip`, `Virtual Strip` and `Buses`. Navigate between the tabs with `Control + TAB` and `Control + SHIFT + TAB`.
All controls within the tabs may be navigated between using `TAB`.
The following controls offer context menus accessed by pressing `SPACE` or `ENTER`:
- Hardware In
- Hardware Out
- Patch Composite
All other buttons can be triggered by pressing `SPACE` or `ENTER`.
To adjust Patch Asio Inputs to Strips and Patch Insert values use `UP` and `DOWN` arrows when in focus.
To rename a strip/bus channel navigate to the relevant tab, then press `F2`. This will open a popup window where you can set the channel index (with a spinbox) and set the new label using a text input box.
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.
A single menu item `Voicemeeter` can be opened using `Alt` and then `v`. The menu allows you to:
- Restart Voicemeeter audio engine
- Save/Load current settings (as an xml file)
- Set a config to load automatically on app startup.
The `Save Settings` option opens a popup window with two buttons, `Browse` and `Cancel`. Browse opens a Save As dialog, Cancel returns to the main app window.
`Load Settings` and `Load on Startup` both open an Open dialog box immediately.
### Issues
If you have any questions/suggestions feel free to raise an issue or open a new discussion.
[voicemeeter]: https://voicemeeter.com/
[nvda]: https://www.nvaccess.org/
[controller_client]: https://github.com/nvaccess/nvda/tree/master/extras/controllerClient
[releases]: https://github.com/onyx-and-iris/nvda-voicemeeter/releases

BIN
img/buses.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
img/physical_strip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
img/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
img/virtual_strip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

8
pdm.lock generated
View File

@@ -6,7 +6,7 @@ groups = ["default", "build", "lint", "test"]
cross_platform = true cross_platform = true
static_urls = false static_urls = false
lock_version = "4.3" lock_version = "4.3"
content_hash = "sha256:2aaf88f0abb701968bc22eb31fd189810850a505bb93553f67216e4d1d259750" content_hash = "sha256:9051151ac4ac0edefdfaf52faa91dbff89d8bbc942a6ffa61b6ec6fee502aa8a"
[[package]] [[package]]
name = "altgraph" name = "altgraph"
@@ -271,13 +271,13 @@ files = [
[[package]] [[package]]
name = "voicemeeter-api" name = "voicemeeter-api"
version = "2.4.8" version = "2.4.9"
requires_python = ">=3.10,<4.0" requires_python = ">=3.10,<4.0"
summary = "A Python wrapper for the Voiceemeter API" summary = "A Python wrapper for the Voiceemeter API"
dependencies = [ dependencies = [
"tomli<3.0.0,>=2.0.1; python_version < \"3.11\"", "tomli<3.0.0,>=2.0.1; python_version < \"3.11\"",
] ]
files = [ files = [
{file = "voicemeeter_api-2.4.8-py3-none-any.whl", hash = "sha256:a3ff9e6f7516d2adedde93f6976526b385d8ae6d86598bfe541a44f498f42ea6"}, {file = "voicemeeter_api-2.4.9-py3-none-any.whl", hash = "sha256:a09fd07fe3799cd5c880d491048da81d94e49aa97ec753aa1f9289acd27be965"},
{file = "voicemeeter_api-2.4.8.tar.gz", hash = "sha256:0d37a9f2af0f68087aa9c76a8cfb2ba44c6b75bb344e8dfa9fd18ad2c862730c"}, {file = "voicemeeter_api-2.4.9.tar.gz", hash = "sha256:47ad614a8b9ccb0b4e47acf65666ce0f0537a0890fffda9949e995bea70e679c"},
] ]

View File

@@ -1,18 +1,20 @@
[project] [project]
name = "nvda_voicemeeter" name = "nvda_voicemeeter"
version = "0.1.0" version = "0.1.1"
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" },
] ]
dependencies = [ dependencies = [
"pysimplegui>=4.60.5", "pysimplegui>=4.60.5",
"pyparsing>=3.1.1", "pyparsing>=3.1.1",
"voicemeeter-api>=2.4.8", "voicemeeter-api>=2.4.9",
] ]
requires-python = ">=3.10,<3.11" requires-python = ">=3.10,<3.11"
readme = "README.md" readme = "README.md"
license = {text = "MIT"}
[project.license]
text = "MIT"
[tool.pdm.dev-dependencies] [tool.pdm.dev-dependencies]
build = [ build = [
@@ -26,5 +28,5 @@ test = [
"psgdemos>=1.12.1", "psgdemos>=1.12.1",
] ]
[tool.pdm.scripts] [tool.pdm.scripts.build]
build = {shell = "build.ps1"} shell = "build.ps1"

View File

@@ -296,7 +296,7 @@ class Builder:
for j in range(self.kind.phys_out + self.kind.virt_out) for j in range(self.kind.phys_out + self.kind.virt_out)
] ]
) )
if i == self.kind.phys_in + self.kind.virt_in - 2: if i == self.kind.phys_in + 1:
layout.append( layout.append(
[ [
psg.Button("K", size=(6, 2), key=f"STRIP {i}||MONO"), psg.Button("K", size=(6, 2), key=f"STRIP {i}||MONO"),

View File

@@ -3,7 +3,10 @@ def _make_hardware_ins_cache(vm) -> dict:
def _make_hardware_outs_cache(vm) -> dict: def _make_hardware_outs_cache(vm) -> dict:
return {**{f"HARDWARE OUT||A{i + 1}": vm.bus[i].device.name for i in range(vm.kind.phys_out)}} 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}
return hw_outs
def _make_param_cache(vm, channel_type) -> dict: def _make_param_cache(vm, channel_type) -> dict:

View File

@@ -58,6 +58,8 @@ class NVDAVMWindow(psg.Window):
self[f"HARDWARE IN||{i + 1}"].Widget.config(**buttonmenu_opts) self[f"HARDWARE IN||{i + 1}"].Widget.config(**buttonmenu_opts)
for i in range(self.kind.phys_out): for i in range(self.kind.phys_out):
self[f"HARDWARE OUT||A{i + 1}"].Widget.config(**buttonmenu_opts) 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)
if self.kind.name != "basic": 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.phys_out)]
self["ASIO BUFFER"].Widget.config(**buttonmenu_opts) self["ASIO BUFFER"].Widget.config(**buttonmenu_opts)
@@ -138,6 +140,10 @@ class NVDAVMWindow(psg.Window):
self[f"HARDWARE OUT||A{i + 1}"].bind("<FocusIn>", "||FOCUS IN") 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("<space>", "||KEY SPACE", propagate=False)
self[f"HARDWARE OUT||A{i + 1}"].bind("<Return>", "||KEY ENTER", 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)
# Patch ASIO # Patch ASIO
if self.kind.name != "basic": if self.kind.name != "basic":
@@ -506,7 +512,7 @@ class NVDAVMWindow(psg.Window):
case "MONO": case "MONO":
if int(index) < self.kind.phys_in: if int(index) < self.kind.phys_in:
actual = param.lower() actual = param.lower()
elif int(index) == self.kind.phys_in + self.kind.virt_in - 2: elif int(index) == self.kind.phys_in + 1:
actual = "k" actual = "k"
else: else:
actual = "mc" actual = "mc"
@@ -536,7 +542,7 @@ class NVDAVMWindow(psg.Window):
case "MONO": case "MONO":
if int(index) < self.kind.phys_in: if int(index) < self.kind.phys_in:
actual = param.lower() actual = param.lower()
elif int(index) == self.kind.phys_in + self.kind.virt_in - 2: elif int(index) == self.kind.phys_in + 1:
actual = "k" actual = "k"
else: else:
actual = "mc" actual = "mc"