22 Commits

Author SHA1 Message Date
b0f634f1e8 split up taskfiles
add azure builds
2026-03-09 10:27:47 +00:00
5e5ae33e6a upd README 2026-03-09 05:46:11 +00:00
0d04a2f33e add support for 'host' in toml vban config
fix error with VBANCMDConnectionError error dialog
2026-03-09 05:46:04 +00:00
81a5497a32 add feedback for comp/gate sliders.
bus mode button now reads current mode from tkVar (more reliable).

bus mono button fixed (possible to set stereo reverse)
2026-03-08 21:13:30 +00:00
edc76db88e upd poe version 2026-02-27 20:43:24 +00:00
3b701a074d add path deps to dev.dependencies 2026-01-22 18:57:56 +00:00
66cabb68cf Merge pull request #19 from onyx-and-iris/dependabot/pip/setuptools-78.1.1
Bump setuptools from 75.8.0 to 78.1.1
2025-05-19 23:58:41 +01:00
dependabot[bot]
37d7e58704 Bump setuptools from 75.8.0 to 78.1.1
Bumps [setuptools](https://github.com/pypa/setuptools) from 75.8.0 to 78.1.1.
- [Release notes](https://github.com/pypa/setuptools/releases)
- [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst)
- [Commits](https://github.com/pypa/setuptools/compare/v75.8.0...v78.1.1)

---
updated-dependencies:
- dependency-name: setuptools
  dependency-version: 78.1.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 22:58:20 +00:00
293bccc5ba call task from poe
remove build.ps1 and scripts.py
2025-02-13 16:23:35 +00:00
1d8ffdc756 mark rewrite,restore tasks as internal 2025-02-12 18:26:43 +00:00
e4d87334cb reformat 2025-02-07 22:58:30 +00:00
ad3020809e merge commands 2025-02-07 16:37:00 +00:00
76c6630892 fix deferred task 2025-02-07 15:09:36 +00:00
cc46fc31f8 upd build script, format files/dirs the same as Taskfile 2025-02-07 15:09:21 +00:00
8657e8846a add Taskfile 2025-02-07 14:53:21 +00:00
43aad156a0 upd flags passed to rewriter 2025-02-07 14:53:07 +00:00
5101ff01f2 change --cleanup flag for --restore
run file through ruff formatter
2025-02-07 14:52:40 +00:00
c437ae5843 rename entry points 2025-01-29 15:55:24 +00:00
ae59ba30f9 add 1.9.8 section to CHANGELOG 2025-01-22 16:46:25 +00:00
a3fa227ac1 patch bump 2025-01-22 16:38:52 +00:00
b1b6c66828 reduce the time vban menus are re-enabled after a disconnect 2025-01-22 16:38:44 +00:00
cb00de36f0 add _internal/configs to config paths.
vm-compact dirs now override _internal/config

upd README TOML Files section
2025-01-22 16:30:06 +00:00
19 changed files with 495 additions and 313 deletions

View File

@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- [ ] - [ ]
## [1.9.8] - 2025-01-22
### Changed
- vm-compact config dirs now override _internal/configs (if using build from releases). See [TOML Files](https://github.com/onyx-and-iris/voicemeeter-compact?tab=readme-ov-file#toml-files) section in README.
- after disconnecting from a vban connection, vban menus are re-enabled after 500ms.
## [1.9.5] - 2024-07-03 ## [1.9.5] - 2024-07-03
### Changed ### Changed

View File

@@ -65,15 +65,18 @@ Set the kind of Voicemeeter, KIND_ID may be:
## TOML Files ## TOML Files
This is how your files should be organised. Wherever your `__main__.py` file is located (after install this can be any location), `configs` should be in the same location. If you've downloaded the binary from [Releases][releases] you can find configs included in the `_internal/configs` directory.
Directly inside of configs directory you may place an app.toml, vban.toml and a directory for each kind.
Inside each kind directory you may place as many custom toml configurations as you wish. You may override these configs by placing a directory `vm-compact` in one of the following locations:
- `user home directory / .config`
- `user home directory / Documents / Voicemeeter`
The contents should match the following directory structure:
. .
├── `__main__.py` ├── vm-compact
├── configs
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── app.toml &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── app.toml
@@ -111,7 +114,7 @@ Configure certain startup states for the app.
Configure a user config to load on app startup. Don't include the .toml extension in the config name. Configure a user config to load on app startup. Don't include the .toml extension in the config name.
- `theme` - `theme`
By default the app loads up the [Sun Valley light or dark theme][def] by @rdbende. You have the option to load up the app without any theme loaded. Simply set `enabled` to false and `mode` will take no effect. By default the app loads up the [Sun Valley light or dark theme][releases] by @rdbende. You have the option to load up the app without any theme loaded. Simply set `enabled` to false and `mode` will take no effect.
- `extends` - `extends`
Extending the app will show both strips and buses. In reduced mode only one or the other. This app will extend both horizontally and vertically, simply set `extends_horizontal` true or false accordingly. Extending the app will show both strips and buses. In reduced mode only one or the other. This app will extend both horizontally and vertically, simply set `extends_horizontal` true or false accordingly.
@@ -136,13 +139,13 @@ A valid `vban.toml` might look like this:
```toml ```toml
[connection-1] [connection-1]
kind = 'banana' kind = 'banana'
ip = '192.168.1.2' host = '192.168.1.2'
streamname = 'worklaptop' streamname = 'worklaptop'
port = 6980 port = 6980
[connection-2] [connection-2]
kind = 'potato' kind = 'potato'
ip = '192.168.1.3' host = '192.168.1.3'
streamname = 'streampc' streamname = 'streampc'
port = 6990 port = 6990
``` ```
@@ -164,4 +167,5 @@ User configs may be loaded at any time via the menu.
[Rdbende](https://github.com/rdbende) for creating the beautiful [Sun Valley theme][sv-theme]. [Rdbende](https://github.com/rdbende) for creating the beautiful [Sun Valley theme][sv-theme].
[sv-theme]: https://github.com/rdbende/Sun-Valley-ttk-theme [sv-theme]: https://github.com/rdbende/Sun-Valley-ttk-theme
[releases]: https://github.com/onyx-and-iris/voicemeeter-compact/releases

40
Taskfile.azure.yml Normal file
View File

@@ -0,0 +1,40 @@
version: '3'
tasks:
build:
desc: Build Azure artifacts
deps: [rewrite]
cmds:
- defer: { task: restore }
- for:
matrix:
KIND: [basic, banana, potato]
THEME: [azure-light, azure-dark]
cmd: poetry run pyinstaller --noconfirm --distpath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} spec/azure/{{.ITEM.THEME}}-{{.ITEM.KIND}}.spec
rewrite:
internal: true
desc: Run the source code rewriter
cmds:
- poetry run python tools/rewriter.py --rewrite --theme {{.THEME}}
restore:
internal: true
desc: Restore the backup files
cmds:
- poetry run python tools/rewriter.py --restore
compress:
desc: Compress Azure artifacts
cmds:
- for:
matrix:
KIND: [basic, banana, potato]
THEME: [azure-light, azure-dark]
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} -DestinationPath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}}.zip -Force"'
clean:
desc: Clean build and dist directories
cmds:
- |
{{.SHELL}} -Command "Remove-Item -Path build/azure-*,dist/azure-* -Recurse -Force"

40
Taskfile.forest.yml Normal file
View File

@@ -0,0 +1,40 @@
version: '3'
tasks:
build:
desc: Build Forest artifacts
deps: [rewrite]
cmds:
- defer: { task: restore }
- for:
matrix:
KIND: [basic, banana, potato]
THEME: [forest-light, forest-dark]
cmd: poetry run pyinstaller --noconfirm --distpath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} spec/forest/{{.ITEM.THEME}}-{{.ITEM.KIND}}.spec
rewrite:
internal: true
desc: Run the source code rewriter
cmds:
- poetry run python tools/rewriter.py --rewrite --theme {{.THEME}}
restore:
internal: true
desc: Restore the backup files
cmds:
- poetry run python tools/rewriter.py --restore
compress:
desc: Compress Forest artifacts
cmds:
- for:
matrix:
KIND: [basic, banana, potato]
THEME: [forest-light, forest-dark]
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/{{.ITEM.THEME}}-{{.ITEM.KIND}} -DestinationPath dist/{{.ITEM.THEME}}-{{.ITEM.KIND}}.zip -Force"'
clean:
desc: Clean build and dist directories
cmds:
- |
{{.SHELL}} -Command "Remove-Item -Path build/forest-*,dist/forest-* -Recurse -Force"

24
Taskfile.sunvalley.yml Normal file
View File

@@ -0,0 +1,24 @@
version: '3'
tasks:
build:
desc: Build Sunvalley artifacts
cmds:
- for:
matrix:
KIND: [basic, banana, potato]
cmd: poetry run pyinstaller --noconfirm --distpath dist/sunvalley-{{.ITEM.KIND}} spec/sunvalley/sunvalley-{{.ITEM.KIND}}.spec
compress:
desc: Compress Sunvalley artifacts
cmds:
- for:
matrix:
KIND: [basic, banana, potato]
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/sunvalley-{{.ITEM.KIND}} -DestinationPath dist/sunvalley-{{.ITEM.KIND}}.zip -Force"'
clean:
desc: Clean build and dist directories
cmds:
- |
{{.SHELL}} -Command "Remove-Item -Path build/sunvalley-*,dist/sunvalley-* -Recurse -Force"

55
Taskfile.yml Normal file
View File

@@ -0,0 +1,55 @@
version: '3'
includes:
sunvalley:
taskfile: ./Taskfile.sunvalley.yml
vars:
THEME: sunvalley
forest:
taskfile: ./Taskfile.forest.yml
vars:
THEME: forest
azure:
taskfile: ./Taskfile.azure.yml
vars:
THEME: azure
vars:
SHELL: pwsh
tasks:
default:
desc: Prepare artifacts for release
cmds:
- task: release
release:
desc: Build and compress all artifacts
cmds:
- task: build
- task: compress
- echo "Release complete"
build:
desc: Build all artifacts
cmds:
- for:
matrix:
THEME: [sunvalley, forest, azure]
task: '{{.ITEM.THEME}}:build'
compress:
desc: Compress all artifacts
cmds:
- for:
matrix:
THEME: [sunvalley, forest, azure]
task: '{{.ITEM.THEME}}:compress'
clean:
desc: Clean up build and dist directories
cmds:
- for:
matrix:
THEME: [sunvalley, forest, azure]
task: '{{.ITEM.THEME}}:clean'

View File

@@ -1,41 +0,0 @@
param(
[Parameter(Mandatory = $true)]
[string]$prefix,
[string]$theme
)
function Format-SpecName {
param(
[string]$Kind
)
return @(
$prefix,
(& { if ($theme) { $theme } else { "" } }),
$Kind
).Where({ $_ -ne "" }) -Join "_"
}
function Compress-Builds {
$target = Join-Path -Path $PSScriptRoot -ChildPath "dist"
@("basic", "banana", "potato") | ForEach-Object {
$compressPath = Format-SpecName -Kind $_
Compress-Archive -Path (Join-Path -Path $target -ChildPath $compressPath) -DestinationPath (Join-Path -Path $target -ChildPath "${compressPath}.zip") -Force
}
}
function Get-Builds {
@("basic", "banana", "potato") | ForEach-Object {
$specName = Format-SpecName -Kind $_
Write-Host "building $specName"
poetry run pyinstaller --noconfirm --distpath (Join-Path -Path "dist" -ChildPath $specName) (Join-Path -Path "spec" -ChildPath "${specName}.spec")
}
}
function main {
Get-Builds
Compress-Builds
}
if ($MyInvocation.InvocationName -ne '.') { main }

View File

@@ -1,10 +1,9 @@
# load a specific profile on start (file name without .toml ext) # load a specific profile on start (file name without .toml ext)
# [configs] # [configs]
# config="example" # config="example"
# load with themes enabled? set the default mode # load with themes enabled?
[theme] [theme]
enabled = true enabled = true
mode = "light"
# load in extended mode? if so which orientation # load in extended mode? if so which orientation
[extends] [extends]
extended = true extended = true
@@ -22,4 +21,4 @@ size = 3
default = 0 default = 0
# show the navigation frame? # show the navigation frame?
[navigation] [navigation]
show = true show = false

View File

@@ -2,12 +2,12 @@
### set the ip then uncomment ### set the ip then uncomment
# [connection-1] # [connection-1]
# kind = 'banana' # kind = 'banana'
# ip = '<ip address 1>' # ip = 'localhost'
# streamname = 'Command1' # streamname = 'Command1'
# port = 6980 # port = 6980
# [connection-2] # [connection-2]
# kind = 'potato' # kind = 'potato'
# ip = '<ip address 2>' # ip = 'gamepc.local'
# streamname = 'Command1' # streamname = 'Command1'
# port = 6980 # port = 6980

52
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. # This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
[[package]] [[package]]
name = "altgraph" name = "altgraph"
@@ -147,24 +147,24 @@ files = [
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "75.8.0" version = "78.1.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["build"] groups = ["build"]
files = [ files = [
{file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"}, {file = "setuptools-78.1.1-py3-none-any.whl", hash = "sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561"},
{file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"}, {file = "setuptools-78.1.1.tar.gz", hash = "sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d"},
] ]
[package.extras] [package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
cover = ["pytest-cov"] cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"] enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"] type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
[[package]] [[package]]
name = "sv-ttk" name = "sv-ttk"
@@ -184,8 +184,8 @@ version = "2.2.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"] groups = ["main", "dev"]
markers = "python_version < \"3.11\"" markers = "python_version == \"3.10\""
files = [ files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -223,35 +223,39 @@ files = [
[[package]] [[package]]
name = "vban-cmd" name = "vban-cmd"
version = "2.5.0" version = "2.10.1"
description = "Python interface for the VBAN RT Packet Service (Sendtext)" description = "Python interface for the VBAN RT Packet Service (Sendtext)"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"] groups = ["main", "dev"]
files = [ files = []
{file = "vban_cmd-2.5.0-py3-none-any.whl", hash = "sha256:22a19037066487d464a61941a3b85a0331b498a9efb1bcacdc932e9d06c5bf87"}, develop = true
{file = "vban_cmd-2.5.0.tar.gz", hash = "sha256:691a852e5052e50103839b06a0a9d0746b90df3346545c2cf4f10b099d9666e4"},
]
[package.dependencies] [package.dependencies]
tomli = {version = ">=2.0.1,<3.0", markers = "python_version < \"3.11\""} tomli = {version = ">=2.0.1,<3.0", markers = "python_version < \"3.11\""}
[package.source]
type = "directory"
url = "../vban-cmd-python"
[[package]] [[package]]
name = "voicemeeter-api" name = "voicemeeter-api"
version = "2.6.1" version = "2.7.2"
description = "A Python wrapper for the Voiceemeter API" description = "A Python wrapper for the Voiceemeter API"
optional = false optional = false
python-versions = "<4.0,>=3.10" python-versions = ">=3.10"
groups = ["main"] groups = ["main", "dev"]
files = [ files = []
{file = "voicemeeter_api-2.6.1-py3-none-any.whl", hash = "sha256:8ae3bce0f9ad6bbad78f2f69f522b6fb2e229d314918a075ad83d4009aff7020"}, develop = true
{file = "voicemeeter_api-2.6.1.tar.gz", hash = "sha256:34d8672603ec66197f2d61fd8f038f46d8451759c81fbe222b00e7d3ccccd1f5"},
]
[package.dependencies] [package.dependencies]
tomli = {version = ">=2.0.1,<3.0", markers = "python_version < \"3.11\""} tomli = {version = ">=2.0.1,<3.0", markers = "python_version < \"3.11\""}
[package.source]
type = "directory"
url = "../voicemeeter-api-python"
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.10,<3.14" python-versions = ">=3.10,<3.14"
content-hash = "19c384acd36868a5bfdc3f3173f444858136603694c3f1134c0d30cd17157651" content-hash = "171da15ce55f47b4e651aade40ab21afd5ef589ff7ff26e51caf6840d25b98a1"

View File

@@ -1,34 +1,34 @@
[project] [project]
name = "voicemeeter-compact" name = "voicemeeter-compact"
version = "1.9.7" version = "1.9.8"
description = "A Compact Voicemeeter Remote App" description = "A Compact Voicemeeter Remote App"
authors = [ authors = [{ name = "Onyx and Iris", email = "code@onyxandiris.online" }]
{name = "Onyx and Iris",email = "code@onyxandiris.online"} license = { text = "MIT" }
]
license = {text = "MIT"}
readme = "README.md" readme = "README.md"
requires-python = ">=3.10,<3.14" requires-python = ">=3.10,<3.14"
dependencies = [ dependencies = [
"voicemeeter-api (>=2.6.1,<3.0.0)", "voicemeeter-api (>=2.7.2,<3.0.0)",
"vban-cmd (>=2.5.0,<3.0.0)", "vban-cmd (>=2.10.1,<3.0.0)",
"sv-ttk (>=2.6.0,<3.0.0)", "sv-ttk (>=2.6.0,<3.0.0)",
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'", "tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
] ]
[project.scripts] [project.scripts]
gui-basic = "vmcompact.gui.basic:run" gui-basic-compact = "vmcompact.gui.basic:run"
gui-banana = "vmcompact.gui.banana:run" gui-banana-compact = "vmcompact.gui.banana:run"
gui-potato = "vmcompact.gui.potato:run" gui-potato-compact = "vmcompact.gui.potato:run"
[tool.poetry] [tool.poetry]
packages = [{ include = "vmcompact" }] packages = [{ include = "vmcompact" }]
include = ["vmcompact/img/cat.ico"] include = ["vmcompact/img/cat.ico"]
[tool.poetry.requires-plugins] [tool.poetry.requires-plugins]
poethepoet = "^0.32.1" poethepoet = ">=0.42.0"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
ruff = "^0.9.1" ruff = "^0.9.1"
voicemeeter-api = { path = "../voicemeeter-api-python/", develop = true }
vban-cmd = { path = "../vban-cmd-python/", develop = true }
[tool.poetry.group.build.dependencies] [tool.poetry.group.build.dependencies]
pyinstaller = "^6.11.1" pyinstaller = "^6.11.1"
@@ -38,9 +38,14 @@ requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poe.tasks] [tool.poe.tasks]
build_sunvalley.script = "scripts:build_sunvalley" build-sunvalley = "task build-sunvalley"
build_forest.script = "scripts:build_forest" build-forest = "task build-forest"
build_all.script = "scripts:build_all" release = [
{ ref = "build-sunvalley" },
{ ref = "build-forest" },
{ cmd = "task compress-sunvalley" },
{ cmd = "task compress-forest" },
]
[tool.ruff] [tool.ruff]
exclude = [ exclude = [
@@ -120,7 +125,4 @@ docstring-code-line-length = "dynamic"
max-complexity = 10 max-complexity = 10
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"__init__.py" = [ "__init__.py" = ["E402", "F401"]
"E402",
"F401",
]

View File

@@ -1,24 +0,0 @@
import subprocess
import sys
from pathlib import Path
def build_sunvalley():
buildscript = Path.cwd() / "build.ps1"
subprocess.run(["powershell", str(buildscript), "sv"])
def build_forest():
rewriter = Path.cwd() / "tools" / "rewriter.py"
subprocess.run([sys.executable, str(rewriter), "-r"])
buildscript = Path.cwd() / "build.ps1"
for theme in ("light", "dark"):
subprocess.run(["powershell", str(buildscript), "fst", theme])
subprocess.run([sys.executable, str(rewriter), "-c"])
def build_all():
steps = (build_sunvalley, build_forest)
[step() for step in steps]

View File

@@ -4,11 +4,11 @@ from pathlib import Path
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("vm-compact-rewriter") logger = logging.getLogger('vm-compact-rewriter')
PACKAGE_DIR = Path(__file__).parent.parent / "vmcompact" PACKAGE_DIR = Path(__file__).parent.parent / 'vmcompact'
SRC_DIR = Path(__file__).parent / "src" SRC_DIR = Path(__file__).parent / 'src'
def write_outs(output, outs: tuple): def write_outs(output, outs: tuple):
@@ -16,235 +16,256 @@ def write_outs(output, outs: tuple):
output.write(out) output.write(out)
def rewrite_app(): def rewrite_app(theme):
app_logger = logger.getChild("app") app_logger = logger.getChild('app')
app_logger.info("rewriting app.py") app_logger.info('rewriting app.py')
infile = Path(SRC_DIR) / "app.bk" infile = Path(SRC_DIR) / 'app.bk'
outfile = Path(PACKAGE_DIR) / "app.py" outfile = Path(PACKAGE_DIR) / 'app.py'
with open(infile, "r") as input: with open(infile, 'r') as input:
with open(outfile, "w") as output: with open(outfile, 'w') as output:
for line in input: for line in input:
match line: match line:
# App init() # App init()
case " def __init__(self, vmr):\n": case ' def __init__(self, vmr):\n':
output.write(" def __init__(self, vmr, theme):\n") output.write(' def __init__(self, vmr, theme):\n')
case " self._vmr = vmr\n": case ' self._vmr = vmr\n':
write_outs( write_outs(
output, output,
( (
" self._vmr = vmr\n", ' self._vmr = vmr\n',
" self._theme = theme\n", ' self._theme = theme\n',
' self._theme_name = theme.split("-")[0]\n',
' self._theme_type = theme.split("-")[-1]\n',
' tcldir = Path.cwd() / "theme"\n', ' tcldir = Path.cwd() / "theme"\n',
" if not tcldir.is_dir():\n", ' if not tcldir.is_dir():\n',
' tcldir = Path.cwd() / "_internal" / "theme"\n', ' tcldir = Path.cwd() / "_internal" / "theme"\n',
' self.tk.call("source", tcldir.resolve() / f"forest-{self._theme}.tcl")\n', ' match self._theme_name:\n',
' case "forest":\n',
' self.tk.call("source", tcldir.resolve() / f"{self._theme}.tcl")\n',
' case "azure":\n',
' self.tk.call("source", tcldir.resolve() / f"{self._theme_name}.tcl")\n',
), ),
) )
# def connect() # def connect()
case "def connect(kind_id: str, vmr) -> App:\n": case 'def connect(kind_id: str, vmr) -> App:\n':
output.write( output.write('def connect(kind_id: str, vmr, theme) -> App:\n')
'def connect(kind_id: str, vmr, theme="light") -> App:\n' case ' return VMMIN_cls(vmr)\n':
) output.write(' return VMMIN_cls(vmr, theme)\n')
case " return VMMIN_cls(vmr)\n":
output.write(" return VMMIN_cls(vmr, theme)\n")
case _: case _:
output.write(line) output.write(line)
def rewrite_builders(): def rewrite_builders(theme):
builders_logger = logger.getChild("builders") builders_logger = logger.getChild('builders')
builders_logger.info("rewriting builders.py") builders_logger.info('rewriting builders.py')
infile = Path(SRC_DIR) / "builders.bk" infile = Path(SRC_DIR) / 'builders.bk'
outfile = Path(PACKAGE_DIR) / "builders.py" outfile = Path(PACKAGE_DIR) / 'builders.py'
with open(infile, "r") as input: with open(infile, 'r') as input:
with open(outfile, "w") as output: with open(outfile, 'w') as output:
ignore_next_lines = 0 ignore_next_lines = 0
for line in input: for line in input:
if ignore_next_lines > 0: if ignore_next_lines > 0:
builders_logger.info(f"ignoring: {line}") builders_logger.info(f'ignoring: {line}')
ignore_next_lines -= 1 ignore_next_lines -= 1
continue continue
match line: match line:
# loading themes # loading themes
case "import sv_ttk\n": case 'import sv_ttk\n':
output.write("#import sv_ttk\n") output.write('#import sv_ttk\n')
case " self.app.resizable(False, False)\n": case ' self.app.resizable(False, False)\n':
write_outs( if theme.startswith('forest'):
output, write_outs(
( output,
" self.app.resizable(False, False)\n" (
" if _configuration.themes_enabled:\n", ' self.app.resizable(False, False)\n'
' ttk.Style().theme_use(f"forest-{self.app._theme}")\n', ' if _configuration.themes_enabled:\n',
' self.logger.info(f"Forest Theme applied")\n', ' ttk.Style().theme_use(self.app._theme)\n',
), ' self.logger.info(f"{self.app._theme} Theme applied")\n',
) ),
)
elif theme.startswith('azure'):
write_outs(
output,
(
' self.app.resizable(False, False)\n'
' if _configuration.themes_enabled:\n',
' self.app.tk.call("set_theme", self.app._theme_type)\n',
' self.logger.info(f"Azure {self.app._theme_type} Theme applied")\n',
),
)
ignore_next_lines = 6 ignore_next_lines = 6
# setting navframe button widths # setting navframe button widths
case " variable=self.navframe.submix,\n": case ' variable=self.navframe.submix,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.navframe.submix,\n" ' variable=self.navframe.submix,\n'
" width=8,\n", ' width=8,\n',
), ),
) )
case " variable=self.navframe.channel,\n": case ' variable=self.navframe.channel,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.navframe.channel,\n" ' variable=self.navframe.channel,\n'
" width=8,\n", ' width=8,\n',
), ),
) )
case " variable=self.navframe.extend,\n": case ' variable=self.navframe.extend,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.navframe.extend,\n" ' variable=self.navframe.extend,\n'
" width=8,\n", ' width=8,\n',
), ),
) )
case " variable=self.navframe.info,\n": case ' variable=self.navframe.info,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.navframe.info,\n" ' variable=self.navframe.info,\n'
" width=8,\n", ' width=8,\n',
), ),
) )
# set channelframe button widths # set channelframe button widths
case " variable=self.labelframe.mute,\n": case ' variable=self.labelframe.mute,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.labelframe.mute,\n" ' variable=self.labelframe.mute,\n'
" width=7,\n", ' width=7,\n',
), ),
) )
case " variable=self.labelframe.conf,\n": case ' variable=self.labelframe.conf,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.labelframe.conf,\n" ' variable=self.labelframe.conf,\n'
" width=7,\n", ' width=7,\n',
), ),
) )
case " variable=self.labelframe.on,\n": case ' variable=self.labelframe.on,\n':
write_outs( write_outs(
output, output,
( (
" variable=self.labelframe.on,\n" ' variable=self.labelframe.on,\n'
" width=7,\n", ' width=7,\n',
), ),
) )
# set stripconfigframe button widths # set stripconfigframe button widths
case " self.configframe.phys_out_params.index(param)\n": case ' self.configframe.phys_out_params.index(param)\n':
write_outs( write_outs(
output, output,
( (
" self.configframe.phys_out_params.index(param)\n", ' self.configframe.phys_out_params.index(param)\n',
" ],\n", ' ],\n',
" width=6,\n", ' width=6,\n',
), ),
) )
ignore_next_lines = 1 ignore_next_lines = 1
case " self.configframe.virt_out_params.index(param)\n": case ' self.configframe.virt_out_params.index(param)\n':
write_outs( write_outs(
output, output,
( (
" self.configframe.virt_out_params.index(param)\n", ' self.configframe.virt_out_params.index(param)\n',
" ],\n", ' ],\n',
" width=6,\n", ' width=6,\n',
), ),
) )
ignore_next_lines = 1 ignore_next_lines = 1
# This does both strip and bus param vars buttons # This does both strip and bus param vars buttons
case " variable=self.configframe.param_vars[i],\n": case ' variable=self.configframe.param_vars[i],\n':
write_outs( write_outs(
output, output,
( (
" variable=self.configframe.param_vars[i],\n", ' variable=self.configframe.param_vars[i],\n',
" width=6,\n", ' width=6,\n',
), ),
) )
case _: case _:
if "Toggle.TButton" in line: if 'Toggle.TButton' in line:
output.write(line.replace("Toggle.TButton", "ToggleButton")) if theme.startswith('forest'):
output.write(
line.replace('Toggle.TButton', 'ToggleButton')
)
elif theme.startswith('azure'):
output.write(
line.replace(
'Toggle.TButton', 'Switch.TCheckbutton'
)
)
else: else:
output.write(line) output.write(line)
def rewrite_menu(): def rewrite_menu(theme):
menu_logger = logger.getChild("menu") menu_logger = logger.getChild('menu')
menu_logger.info("rewriting menu.py") menu_logger.info('rewriting menu.py')
infile = Path(SRC_DIR) / "menu.bk" infile = Path(SRC_DIR) / 'menu.bk'
outfile = Path(PACKAGE_DIR) / "menu.py" outfile = Path(PACKAGE_DIR) / 'menu.py'
with open(infile, "r") as input: with open(infile, 'r') as input:
with open(outfile, "w") as output: with open(outfile, 'w') as output:
ignore_next_lines = 0 ignore_next_lines = 0
for line in input: for line in input:
if ignore_next_lines > 0: if ignore_next_lines > 0:
menu_logger.info(f"ignoring: {line}") menu_logger.info(f'ignoring: {line}')
ignore_next_lines -= 1 ignore_next_lines -= 1
continue continue
match line: match line:
case "import sv_ttk\n": case 'import sv_ttk\n':
output.write("#import sv_ttk\n") output.write('#import sv_ttk\n')
case " # layout/themes\n": case ' # layout/themes\n':
ignore_next_lines = 14 ignore_next_lines = 14
case _: case _:
output.write(line) output.write(line)
def prepare_for_build(): def prepare_for_build(theme):
################# MOVE FILES FROM PACKAGE DIR INTO SRC DIR ######################### ################# MOVE FILES FROM PACKAGE DIR INTO SRC DIR #########################
for file in ( for file in (
PACKAGE_DIR / "app.py", PACKAGE_DIR / 'app.py',
PACKAGE_DIR / "builders.py", PACKAGE_DIR / 'builders.py',
PACKAGE_DIR / "menu.py", PACKAGE_DIR / 'menu.py',
): ):
if file.exists(): if file.exists():
logger.debug(f"moving {str(file)}") logger.debug(f'moving {str(file)}')
file.rename(SRC_DIR / f"{file.stem}.bk") file.rename(SRC_DIR / f'{file.stem}.bk')
###################### RUN THE FILE REWRITER FOR EACH *.BK ######################### ###################### RUN THE FILE REWRITER FOR EACH *.BK #########################
steps = ( for step in (rewrite_app, rewrite_builders, rewrite_menu):
rewrite_app, step(theme)
rewrite_builders,
rewrite_menu,
)
[step() for step in steps]
def cleanup(): def cleanup():
########################## RESTORE *.BK FILES ##################################### ########################## RESTORE *.BK FILES #####################################
for file in ( for file in (
PACKAGE_DIR / "app.py", PACKAGE_DIR / 'app.py',
PACKAGE_DIR / "builders.py", PACKAGE_DIR / 'builders.py',
PACKAGE_DIR / "menu.py", PACKAGE_DIR / 'menu.py',
): ):
file.unlink() file.unlink()
for file in ( for file in (
SRC_DIR / "app.bk", SRC_DIR / 'app.bk',
SRC_DIR / "builders.bk", SRC_DIR / 'builders.bk',
SRC_DIR / "menu.bk", SRC_DIR / 'menu.bk',
): ):
file.rename(PACKAGE_DIR / f"{file.stem}.py") file.rename(PACKAGE_DIR / f'{file.stem}.py')
if __name__ == "__main__": if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("-r", "--rewrite", action="store_true") parser.add_argument('--rewrite', action='store_true')
parser.add_argument("-c", "--cleanup", action="store_true") parser.add_argument('--theme', type=str, default='forest')
parser.add_argument('--restore', action='store_true')
args = parser.parse_args() args = parser.parse_args()
if args.rewrite: if args.rewrite:
logger.info("preparing files for build") logger.info('preparing files for build')
prepare_for_build() prepare_for_build(args.theme)
elif args.cleanup: elif args.restore:
logger.info("cleaning up files") logger.info('cleaning up files')
cleanup() cleanup()

View File

@@ -227,7 +227,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
"""Adds a progress bar widget to a single label frame""" """Adds a progress bar widget to a single label frame"""
self.labelframe.pb = ttk.Progressbar( self.labelframe.pb = ttk.Progressbar(
self.labelframe, self.labelframe,
maximum=72, maximum=72, # Range: 0 = -60dB, 72 = +12dB (72dB total range)
orient='vertical', orient='vertical',
mode='determinate', mode='determinate',
variable=self.labelframe.level, variable=self.labelframe.level,
@@ -362,15 +362,15 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
tk.BooleanVar() for _ in self.configframe.virt_out_params tk.BooleanVar() for _ in self.configframe.virt_out_params
] ]
self.configframe.params = ('mono', 'solo') self.configframe.bool_params = ('mono', 'solo')
self.configframe.param_vars = list( self.configframe.bool_param_vars = list(
tk.BooleanVar() for _ in self.configframe.params tk.BooleanVar() for _ in self.configframe.bool_params
) )
if self.configframe.parent.kind.name in ('banana', 'potato'): if self.configframe.parent.kind.name in ('banana', 'potato'):
if self.configframe.index == self.configframe.phys_in: if self.configframe.index == self.configframe.phys_in:
self.configframe.params = list( self.configframe.params = list(
map(lambda x: x.replace('mono', 'mc'), self.configframe.params) map(lambda x: x.replace('mono', 'mc'), self.configframe.bool_params)
) )
if self.configframe.parent.kind.name == 'banana': if self.configframe.parent.kind.name == 'banana':
pass pass
@@ -388,7 +388,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
== self.configframe.phys_in + self.configframe.virt_in - 1 == self.configframe.phys_in + self.configframe.virt_in - 1
): ):
self.configframe.params = list( self.configframe.params = list(
map(lambda x: x.replace('mono', 'mc'), self.configframe.params) map(
lambda x: x.replace('mono', 'mc'),
self.configframe.bool_params,
)
) )
def create_comp_slider(self): def create_comp_slider(self):
@@ -542,9 +545,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
self.configframe.pause_updates, self.configframe.toggle_p, param self.configframe.pause_updates, self.configframe.toggle_p, param
), ),
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}', style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}',
variable=self.configframe.param_vars[i], variable=self.configframe.bool_param_vars[i],
) )
for i, param in enumerate(self.configframe.params) for i, param in enumerate(self.configframe.bool_params)
] ]
[ [
button.grid( button.grid(
@@ -574,10 +577,22 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
"lfeonly": "LFE Only", "lfeonly": "LFE Only",
"rearonly": "Rear Only", "rearonly": "Rear Only",
} }
self.configframe.bus_mode_map_reverse = {v: k for k, v in self.configframe.bus_mode_map.items()}
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys()) self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
# fmt: on # fmt: on
self.configframe.params = ('mono', 'eq.on', 'eq.ab') self.configframe.int_params = ('mono',)
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params] self.configframe.int_param_vars = [
tk.IntVar(value=getattr(self.configframe.target, param))
for param in self.configframe.int_params
]
self.configframe.mono_modes = ['mono: off', 'mono: on', 'stereo reverse']
self.configframe.bus_mono_label_text = tk.StringVar(
value=self.configframe.mono_modes[self.configframe.target.mono]
)
self.configframe.bool_params = ('eq.on', 'eq.ab')
self.configframe.bool_param_vars = [
tk.BooleanVar() for _ in self.configframe.bool_params
]
self.configframe.bus_mode_label_text = tk.StringVar( self.configframe.bus_mode_label_text = tk.StringVar(
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()] value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
) )
@@ -602,6 +617,20 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
), ),
) )
def create_bus_mono_button(self):
self.configframe.mono_button = ttk.Button(
self.configframe, textvariable=self.configframe.bus_mono_label_text
)
self.configframe.mono_button.bind(
'<Button-1>',
partial(self.configframe.pause_updates, self.configframe.rotate_mono_right),
)
self.configframe.mono_button.bind(
'<Button-3>',
partial(self.configframe.pause_updates, self.configframe.rotate_mono_left),
)
self.configframe.mono_button.grid(column=0, row=1, sticky=(tk.W))
def create_param_buttons(self): def create_param_buttons(self):
param_buttons = [ param_buttons = [
ttk.Checkbutton( ttk.Checkbutton(
@@ -611,13 +640,13 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
self.configframe.pause_updates, self.configframe.toggle_p, param self.configframe.pause_updates, self.configframe.toggle_p, param
), ),
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}', style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}',
variable=self.configframe.param_vars[i], variable=self.configframe.bool_param_vars[i],
) )
for i, param in enumerate(self.configframe.params) for i, param in enumerate(self.configframe.bool_params)
] ]
[ [
button.grid( button.grid(
column=i, column=i + 1,
row=1, row=1,
) )
for i, button in enumerate(param_buttons) for i, button in enumerate(param_buttons)

View File

@@ -197,14 +197,22 @@ class Strip(ChannelLabelFrame):
def upd_levels(self): def upd_levels(self):
""" """
Updates level values. Updates level values using direct dB values.
""" """
if self.index < self.parent.parent.kind.num_strip: if self.index < self.parent.parent.kind.num_strip:
if self.target.levels.is_updated: if self.target.levels.is_updated:
val = max(self.target.levels.prefader) val = max(self.target.levels.prefader)
self.level.set( if val < -72:
(0 if self.mute.get() else 72 + val - 12 + self.gain.get()) if self.level.get() != 0:
) self.level.set(0)
return
# Convert dB to progressbar: -60dB=0, 0dB=60, +12dB=72
if self.mute.get():
level_display = 0
else:
level_db = val + self.gain.get()
level_display = max(0, min(72, level_db + 60))
self.level.set(level_display)
class Bus(ChannelLabelFrame): class Bus(ChannelLabelFrame):
@@ -223,9 +231,18 @@ class Bus(ChannelLabelFrame):
def upd_levels(self): def upd_levels(self):
if self.index < self.parent.parent.kind.num_bus: if self.index < self.parent.parent.kind.num_bus:
if self.target.levels.is_updated or self.level.get() != -118: if self.target.levels.is_updated:
val = max(self.target.levels.all) val = max(self.target.levels.all)
self.level.set((0 if self.mute.get() else 72 + val - 12)) if val < -72:
if self.level.get() != 0:
self.level.set(0)
return
# Convert dB to progressbar: -60dB=0, 0dB=60, +12dB=72
if self.mute.get():
level_display = 0
else:
level_display = max(0, min(72, val + 60))
self.level.set(level_display)
class ChannelFrame(ttk.Frame): class ChannelFrame(ttk.Frame):

View File

@@ -98,7 +98,7 @@ class Config(ttk.Frame):
self.slider_vars[self.slider_params.index(param)].set(val) self.slider_vars[self.slider_params.index(param)].set(val)
def toggle_p(self, param): def toggle_p(self, param):
val = self.param_vars[self.params.index(param)].get() val = self.bool_param_vars[self.bool_params.index(param)].get()
self.setter(param, val) self.setter(param, val)
if not _configuration.themes_enabled: if not _configuration.themes_enabled:
self.styletable.configure( self.styletable.configure(
@@ -177,15 +177,14 @@ class StripConfig(Config):
for i, param in enumerate(self.virt_out_params) for i, param in enumerate(self.virt_out_params)
] ]
[ [
self.param_vars[i].set(self.getter(param)) self.bool_param_vars[i].set(self.getter(param))
for i, param in enumerate(self.params) for i, param in enumerate(self.bool_params)
]
[
self.slider_vars[i].set(self.getter(param))
for i, param in enumerate(self.slider_params)
if self.index < self.phys_in
] ]
if not _base_values.vban_connected: # slider vars not defined in RT Packet
[
self.slider_vars[i].set(self.getter(param))
for i, param in enumerate(self.slider_params)
if self.index < self.phys_in
]
if not _configuration.themes_enabled: if not _configuration.themes_enabled:
[ [
@@ -207,7 +206,7 @@ class StripConfig(Config):
f'{param}.TButton', f'{param}.TButton',
background=f'{"green" if self.param_vars[i].get() else "white"}', background=f'{"green" if self.param_vars[i].get() else "white"}',
) )
for i, param in enumerate(self.params) for i, param in enumerate(self.bool_params)
] ]
@@ -238,53 +237,56 @@ class BusConfig(Config):
self.builder.create_bus_mode_button() self.builder.create_bus_mode_button()
def make_row_1(self): def make_row_1(self):
self.builder.create_bus_mono_button()
self.builder.create_param_buttons() self.builder.create_param_buttons()
def current_bus_mode(self): def current_bus_mode(self):
return self.target.mode.get() return self.target.mode.get()
def rotate_bus_modes_right(self, *args): def rotate_bus_modes_right(self, *args):
current_mode = self.current_bus_mode() current_mode = self.bus_mode_map_reverse[self.bus_mode_label_text.get()]
next = self.bus_modes.index(current_mode) + 1 current_index = self.bus_modes.index(current_mode)
if next < len(self.bus_modes): next_index = (current_index + 1) % len(self.bus_modes)
setattr( next_mode = self.bus_modes[next_index]
self.target.mode,
self.bus_modes[next], setattr(self.target.mode, next_mode, True)
True, self.bus_mode_label_text.set(self.bus_mode_map[next_mode])
)
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[next]])
else:
self.target.mode.normal = True
self.bus_mode_label_text.set('Normal')
def rotate_bus_modes_left(self, *args): def rotate_bus_modes_left(self, *args):
current_mode = self.current_bus_mode() current_mode = self.bus_mode_map_reverse[self.bus_mode_label_text.get()]
prev = self.bus_modes.index(current_mode) - 1 current_index = self.bus_modes.index(current_mode)
if prev < 0: prev_index = (current_index - 1) % len(self.bus_modes)
self.target.mode.rearonly = True prev_mode = self.bus_modes[prev_index]
self.bus_mode_label_text.set('Rear Only')
else: setattr(self.target.mode, prev_mode, True)
setattr( self.bus_mode_label_text.set(self.bus_mode_map[prev_mode])
self.target.mode,
self.bus_modes[prev], def rotate_mono_right(self, *args):
True, current_val = self.mono_modes.index(self.bus_mono_label_text.get())
) next_val = (current_val + 1) % 3
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[prev]]) self.bus_mono_label_text.set(self.mono_modes[next_val])
self.setter('mono', next_val)
def rotate_mono_left(self, *args):
current_val = self.mono_modes.index(self.bus_mono_label_text.get())
next_val = (current_val - 1) % 3
self.bus_mono_label_text.set(self.mono_modes[next_val])
self.setter('mono', next_val)
def teardown(self): def teardown(self):
self.builder.teardown() self.builder.teardown()
def sync(self): def sync(self):
[ [
self.param_vars[i].set(self.getter(param)) self.bool_param_vars[i].set(self.getter(param))
for i, param in enumerate(self.params) for i, param in enumerate(self.bool_params)
] ]
self.bus_mode_label_text.set(self.bus_mode_map[self.current_bus_mode()]) self.bus_mode_label_text.set(self.bus_mode_map[self.current_bus_mode()])
if not _configuration.themes_enabled: if not _configuration.themes_enabled:
[ [
self.styletable.configure( self.styletable.configure(
f'{param}.TButton', f'{param}.TButton',
background=f'{"green" if self.param_vars[i].get() else "white"}', background=f'{"green" if self.bool_param_vars[i].get() else "white"}',
) )
for i, param in enumerate(self.params) for i, param in enumerate(self.bool_params)
] ]

View File

@@ -12,14 +12,14 @@ configuration = {}
def get_configpath(): def get_configpath():
configpaths = [ for pn in (
Path.home() / '.config' / 'vm-compact',
Path.home() / 'Documents' / 'Voicemeeter' / 'vm-compact',
Path.cwd() / '_internal' / 'configs',
Path.cwd() / 'configs', Path.cwd() / 'configs',
Path.home() / '.config' / 'vm-compact' / 'configs', ):
Path.home() / 'Documents' / 'Voicemeeter' / 'configs', if pn.exists():
] return pn
for configpath in configpaths:
if configpath.exists():
return configpath
if configpath := get_configpath(): if configpath := get_configpath():
@@ -60,7 +60,7 @@ _defaults = {
'submixes': { 'submixes': {
'default': 0, 'default': 0,
}, },
'navigation': {'show': True}, 'navigation': {'show': False},
} }

View File

@@ -161,17 +161,18 @@ class GainLayer(ttk.LabelFrame):
""" """
Updates level values. Updates level values.
""" """
if self.parent.target.strip[self.index].levels.is_updated: if self.parent.target.strip[self.index].levels.is_updated:
val = max(self.parent.target.strip[self.index].levels.prefader) val = max(self.parent.target.strip[self.index].levels.prefader)
self.level.set( # Convert dB to progressbar: -60dB=0, 0dB=60, +12dB=72
( if (
0 self.parent.parent.strip_frame.strips[self.index].mute.get()
if self.parent.parent.strip_frame.strips[self.index].mute.get() or not self.on.get()
or not self.on.get() ):
else 72 + val - 12 + self.gain.get() level_display = 0
) else:
) level_db = val + self.gain.get()
level_display = max(0, min(72, level_db + 60))
self.level.set(level_display)
def grid_configure(self): def grid_configure(self):
self.grid(padx=_configuration.channel_xpadding, sticky=(tk.N, tk.S)) self.grid(padx=_configuration.channel_xpadding, sticky=(tk.N, tk.S))

View File

@@ -357,15 +357,17 @@ class Menus(tk.Menu):
opts = {} opts = {}
opts |= self.vban_config[f'connection-{i + 1}'] opts |= self.vban_config[f'connection-{i + 1}']
kind_id = opts.pop('kind') kind_id = opts.pop('kind')
if 'ip' in opts:
opts['host'] = opts.pop('ip')
self.vban = vban_cmd.api(kind_id, **opts) self.vban = vban_cmd.api(kind_id, **opts)
# login to vban interface # login to vban interface
try: try:
self.logger.info(f'Attempting vban connection to {opts.get("ip")}') self.logger.info(f'Attempting vban connection to {opts.get("host")}')
self.vban.login() self.vban.login()
except VBANCMDConnectionError as e: except VBANCMDConnectionError as e:
self.vban.logout() self.vban.logout()
msg = ( msg = (
f'Timeout attempting to establish connection to {opts.get("ip")}', f'Timeout attempting to establish connection to {opts.get("host")}',
'Please check your connection settings', 'Please check your connection settings',
) )
messagebox.showerror('Connection Error', '\n'.join(msg)) messagebox.showerror('Connection Error', '\n'.join(msg))
@@ -421,7 +423,7 @@ class Menus(tk.Menu):
del self.parent.__dict__['userconfigs'] del self.parent.__dict__['userconfigs']
self.menu_setup() self.menu_setup()
self.after(15000, self.enable_vban_menus) self.after(50, self.enable_vban_menus)
def documentation(self): def documentation(self):
webbrowser.open_new(r'https://voicemeeter.com/') webbrowser.open_new(r'https://voicemeeter.com/')