mirror of
https://github.com/onyx-and-iris/voicemeeter-api-python.git
synced 2026-04-06 23:43:30 +00:00
Compare commits
24 Commits
add-to-bus
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 48614ab5fa | |||
|
|
99350f2c63 | ||
| 2a7a1c5d2a | |||
|
|
f0664f9cfb | ||
| e395e7a373 | |||
| 842feb2407 | |||
| 84b4426e44 | |||
| 0396892530 | |||
| 919dc0d325 | |||
| 0a81c458e2 | |||
| 9903ecca72 | |||
| 00ac5b1428 | |||
| 3d56ba99b6 | |||
| 58ec875521 | |||
| 4c6ec6d989 | |||
| feb6ee5821 | |||
| 15f0fcda69 | |||
| 738688a8a7 | |||
| 1509afd4f5 | |||
| 7232ba6248 | |||
| 1ff2017d51 | |||
|
|
fe1f4ee324 | ||
| 4953751c02 | |||
|
|
abbbf57982 |
53
.github/workflows/publish.yml
vendored
Normal file
53
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install Poetry
|
||||
run: |
|
||||
pip install poetry==2.3.1
|
||||
poetry --version
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
poetry install --only-root
|
||||
poetry build
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: ./dist
|
||||
|
||||
pypi-publish:
|
||||
needs: build
|
||||
name: Upload release to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/project/voicemeeter-api/
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
path: ./dist
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages-dir: ./dist
|
||||
19
.github/workflows/ruff.yml
vendored
Normal file
19
.github/workflows/ruff.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: Ruff
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ruff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
args: 'format --check --diff'
|
||||
@@ -5,3 +5,9 @@ repos:
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/python-poetry/poetry
|
||||
rev: '2.3.2'
|
||||
hooks:
|
||||
- id: poetry-check
|
||||
- id: poetry-lock
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -11,6 +11,16 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
|
||||
|
||||
- [x]
|
||||
|
||||
## [2.7.1] - 2025-06-15
|
||||
|
||||
### Added
|
||||
|
||||
- Strip.EQ Channel Cell commands added, see [Strip.EQ.Channel.Cell](https://github.com/onyx-and-iris/voicemeeter-api-python?tab=readme-ov-file#stripeqchannelcell)
|
||||
- They are only available for potato version.
|
||||
|
||||
- Bus.EQ Channel Cell commands added, see [Bus.EQ.Channel.Cell](https://github.com/onyx-and-iris/voicemeeter-api-python?tab=readme-ov-file#buseqchannelcell).
|
||||
- Added by [PR #16](https://github.com/onyx-and-iris/voicemeeter-api-python/pull/16)
|
||||
|
||||
## [2.6.0] - 2024-06-29
|
||||
|
||||
### Added
|
||||
|
||||
46
README.md
46
README.md
@@ -14,9 +14,9 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
|
||||
|
||||
## Tested against
|
||||
|
||||
- Basic 1.1.1.1
|
||||
- Banana 2.1.1.1
|
||||
- Potato 3.1.1.1
|
||||
- Basic 1.1.2.2
|
||||
- Banana 2.1.2.2
|
||||
- Potato 3.1.2.2
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -225,6 +225,24 @@ example:
|
||||
vm.strip[0].eq.ab = True
|
||||
```
|
||||
|
||||
##### Strip.EQ.Channel.Cell
|
||||
|
||||
The following properties are available.
|
||||
|
||||
- `on`: boolean
|
||||
- `type`: int, from 0 up to 6
|
||||
- `f`: float, from 20.0 up to 20_000.0
|
||||
- `gain`: float, from -36.0 up to 18.0
|
||||
- currently there is a bug with the remote API, only values -12 up to +12 are settable, this will be fixed in an upcoming patch.
|
||||
- `q`: float, from 0.3 up to 100
|
||||
|
||||
example:
|
||||
|
||||
```python
|
||||
vm.strip[0].eq.channel[0].cell[2].on = True
|
||||
vm.strip[1].eq.channel[0].cell[2].f = 5000
|
||||
```
|
||||
|
||||
Strip EQ parameters are defined for PhysicalStrips, potato version only.
|
||||
|
||||
##### Strip.Gainlayers
|
||||
@@ -259,7 +277,7 @@ Level properties will return -200.0 if no audio detected.
|
||||
|
||||
The following properties are available.
|
||||
|
||||
- `mono`: boolean
|
||||
- `mono`: int, from 0 up to 2
|
||||
- `mute`: boolean
|
||||
- `sel`: boolean
|
||||
- `gain`: float, from -60.0 to 12.0
|
||||
@@ -276,7 +294,7 @@ example:
|
||||
vm.bus[3].gain = 3.7
|
||||
print(vm.bus[0].label)
|
||||
|
||||
vm.bus[4].mono = True
|
||||
vm.bus[4].mono = 2
|
||||
```
|
||||
|
||||
##### Bus.EQ
|
||||
@@ -292,6 +310,24 @@ example:
|
||||
vm.bus[3].eq.on = True
|
||||
```
|
||||
|
||||
##### Bus.EQ.Channel.Cell
|
||||
|
||||
The following properties are available.
|
||||
|
||||
- `on`: boolean
|
||||
- `type`: int, from 0 up to 6
|
||||
- `f`: float, from 20.0 up to 20_000.0
|
||||
- `gain`: float, from -36.0 up to 18.0
|
||||
- currently there is a bug with the remote API, only values -12 up to +12 are settable, this will be fixed in an upcoming patch.
|
||||
- `q`: float, from 0.3 up to 100.0
|
||||
|
||||
example:
|
||||
|
||||
```python
|
||||
vm.bus[3].eq.channel[0].cell[2].on = True
|
||||
vm.bus[3].eq.channel[0].cell[2].f = 5000
|
||||
```
|
||||
|
||||
##### Bus.Modes
|
||||
|
||||
The following properties are available.
|
||||
|
||||
@@ -2,20 +2,49 @@ import time
|
||||
|
||||
import voicemeeterlib
|
||||
|
||||
|
||||
def main():
|
||||
KIND_ID = 'banana'
|
||||
BUS_INDEX = 0 # Index of the bus to edit, can be changed as needed
|
||||
CHANNEL_INDEX = 0 # Index of the channel to edit, can be changed as needed
|
||||
|
||||
with voicemeeterlib.api(KIND_ID) as vm:
|
||||
vm.bus[0].eq.on = True
|
||||
vm.bus[0].eq.channel[0].cell[0].on = True
|
||||
vm.bus[0].eq.channel[0].cell[0].f = 500
|
||||
vm.bus[0].eq.channel[0].cell[0].type = 3 # Should correspond to LPF
|
||||
print(f'Bus[{BUS_INDEX}].EQ.on: {vm.bus[BUS_INDEX].eq.on}')
|
||||
print(
|
||||
f'Bus[{BUS_INDEX}].EQ.channel[{CHANNEL_INDEX}].cell[0].on: {vm.bus[BUS_INDEX].eq.channel[CHANNEL_INDEX].cell[0].on}'
|
||||
)
|
||||
|
||||
print('Check sending commands (should affect your VM Banana window)')
|
||||
|
||||
vm.bus[BUS_INDEX].eq.on = True
|
||||
vm.bus[BUS_INDEX].eq.ab = 0 # corresponds to A EQ memory slot
|
||||
vm.bus[BUS_INDEX].mute = False
|
||||
|
||||
for j, cell in enumerate(vm.bus[BUS_INDEX].eq.channel[CHANNEL_INDEX].cell):
|
||||
cell.on = True
|
||||
cell.f = 500
|
||||
cell.gain = -10
|
||||
cell.type = 3 # Should correspond to LPF
|
||||
cell.q = 10
|
||||
|
||||
print(
|
||||
f'Channel {CHANNEL_INDEX}, Cell {j}: on={cell.on}, f={cell.f}, type={cell.type}, gain={cell.gain}, q={cell.q}'
|
||||
)
|
||||
|
||||
time.sleep(1) # Sleep to simulate processing time
|
||||
|
||||
cell.on = False
|
||||
cell.f = 50
|
||||
cell.gain = 0
|
||||
cell.type = 0
|
||||
cell.q = 3
|
||||
|
||||
print(
|
||||
f'Channel {CHANNEL_INDEX}, Cell {j}: on={cell.on}, f={cell.f}, type={cell.type} , gain={cell.gain}, q={cell.q}'
|
||||
)
|
||||
|
||||
vm.bus[BUS_INDEX].eq.on = False
|
||||
|
||||
time.sleep(3)
|
||||
vm.bus[0].eq.on = False
|
||||
vm.bus[0].eq.channel[0].cell[0].on = False
|
||||
vm.bus[0].eq.channel[0].cell[0].f = 50
|
||||
vm.bus[0].eq.channel[0].cell[0].type = 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
42
poetry.lock
generated
42
poetry.lock
generated
@@ -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.2.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
@@ -55,7 +55,7 @@ description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.11\""
|
||||
markers = "python_version == \"3.10\""
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
@@ -66,21 +66,16 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.16.1"
|
||||
version = "3.20.3"
|
||||
description = "A platform independent file lock."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
|
||||
{file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
|
||||
{file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"},
|
||||
{file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
|
||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
|
||||
typing = ["typing-extensions (>=4.12.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
@@ -243,7 +238,7 @@ description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main", "dev"]
|
||||
markers = "python_version < \"3.11\""
|
||||
markers = "python_version == \"3.10\""
|
||||
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_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
@@ -309,37 +304,38 @@ test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
version = "4.15.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.9+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
markers = "python_version < \"3.11\""
|
||||
markers = "python_version == \"3.10\""
|
||||
files = [
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
|
||||
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.28.1"
|
||||
version = "20.36.1"
|
||||
description = "Virtual Python Environment builder"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"},
|
||||
{file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"},
|
||||
{file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"},
|
||||
{file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
distlib = ">=0.3.7,<1"
|
||||
filelock = ">=3.12.2,<4"
|
||||
filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""}
|
||||
platformdirs = ">=3.9.1,<5"
|
||||
typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
|
||||
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv-pyenv"
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
[project]
|
||||
name = "voicemeeter-api"
|
||||
version = "2.6.1"
|
||||
version = "2.7.2"
|
||||
description = "A Python wrapper for the Voiceemeter API"
|
||||
authors = [
|
||||
{name = "Onyx and Iris",email = "code@onyxandiris.online"}
|
||||
]
|
||||
authors = [{ name = "Onyx and Iris", email = "code@onyxandiris.online" }]
|
||||
license = { text = "MIT" }
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
|
||||
]
|
||||
dependencies = ["tomli (>=2.0.1,<3.0) ; python_version < '3.11'"]
|
||||
|
||||
[tool.poetry]
|
||||
packages = [{ include = "voicemeeterlib" }]
|
||||
|
||||
[tool.poetry.requires-plugins]
|
||||
poethepoet = "^0.32.1"
|
||||
poethepoet = ">=0.42.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^8.3.4"
|
||||
@@ -37,6 +33,7 @@ levels.script = "scripts:ex_levels"
|
||||
midi.script = "scripts:ex_midi"
|
||||
obs.script = "scripts:ex_obs"
|
||||
observer.script = "scripts:ex_observer"
|
||||
eqedit.script = "scripts:ex_eqedit"
|
||||
test-basic.script = "scripts:test_basic"
|
||||
test-banana.script = "scripts:test_banana"
|
||||
test-potato.script = "scripts:test_potato"
|
||||
@@ -124,7 +121,4 @@ docstring-code-line-length = "dynamic"
|
||||
max-complexity = 10
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"__init__.py" = [
|
||||
"E402",
|
||||
"F401",
|
||||
]
|
||||
"__init__.py" = ["E402", "F401"]
|
||||
|
||||
@@ -37,6 +37,11 @@ def ex_observer():
|
||||
subprocess.run([sys.executable, str(scriptpath)])
|
||||
|
||||
|
||||
def ex_eqedit():
|
||||
scriptpath = Path.cwd() / 'examples' / 'eq_edit' / '.'
|
||||
subprocess.run([sys.executable, str(scriptpath)])
|
||||
|
||||
|
||||
def test_basic():
|
||||
subprocess.run(['tox'], env=os.environ.copy() | {'KIND': 'basic'})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption(
|
||||
"--run-slow",
|
||||
action="store_true",
|
||||
'--run-slow',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help="Run slow tests",
|
||||
help='Run slow tests',
|
||||
)
|
||||
|
||||
@@ -10,37 +10,37 @@ class TestUserConfigs:
|
||||
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
vm.apply_config("example")
|
||||
vm.apply_config('example')
|
||||
|
||||
def test_it_tests_vm_config_string(self):
|
||||
assert "PhysStrip" in vm.strip[data.phys_in].label
|
||||
assert "VirtStrip" in vm.strip[data.virt_in].label
|
||||
assert "PhysBus" in vm.bus[data.phys_out].label
|
||||
assert "VirtBus" in vm.bus[data.virt_out].label
|
||||
assert 'PhysStrip' in vm.strip[data.phys_in].label
|
||||
assert 'VirtStrip' in vm.strip[data.virt_in].label
|
||||
assert 'PhysBus' in vm.bus[data.phys_out].label
|
||||
assert 'VirtBus' in vm.bus[data.virt_out].label
|
||||
|
||||
def test_it_tests_vm_config_bool(self):
|
||||
assert vm.strip[0].A1 == True
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Skip test if kind is not potato",
|
||||
data.name != 'potato',
|
||||
reason='Skip test if kind is not potato',
|
||||
)
|
||||
def test_it_tests_vm_config_bool_strip_eq_on(self):
|
||||
assert vm.strip[data.phys_in].eq.on == True
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "banana",
|
||||
reason="Skip test if kind is not banana",
|
||||
data.name != 'banana',
|
||||
reason='Skip test if kind is not banana',
|
||||
)
|
||||
def test_it_tests_vm_config_bool_bus_eq_ab(self):
|
||||
assert vm.bus[data.phys_out].eq.ab == True
|
||||
|
||||
@pytest.mark.skipif(
|
||||
"not config.getoption('--run-slow')",
|
||||
reason="Only run when --run-slow is given",
|
||||
reason='Only run when --run-slow is given',
|
||||
)
|
||||
def test_it_tests_vm_config_busmode(self):
|
||||
assert vm.bus[data.phys_out].mode.get() == "composite"
|
||||
assert vm.bus[data.phys_out].mode.get() == 'composite'
|
||||
|
||||
def test_it_tests_vm_config_bass_med_high(self):
|
||||
assert vm.strip[data.virt_in].bass == -3.2
|
||||
|
||||
@@ -3,7 +3,7 @@ import re
|
||||
import pytest
|
||||
|
||||
import voicemeeterlib
|
||||
from tests import data, vm
|
||||
from tests import vm
|
||||
|
||||
|
||||
class TestErrors:
|
||||
@@ -14,36 +14,36 @@ class TestErrors:
|
||||
voicemeeterlib.error.VMError,
|
||||
match="Unknown Voicemeeter kind 'unknown_kind'",
|
||||
):
|
||||
voicemeeterlib.api("unknown_kind")
|
||||
voicemeeterlib.api('unknown_kind')
|
||||
|
||||
def test_it_tests_an_unknown_parameter(self):
|
||||
with pytest.raises(
|
||||
voicemeeterlib.error.CAPIError,
|
||||
match="VBVMR_SetParameterFloat returned -3",
|
||||
match='VBVMR_SetParameterFloat returned -3',
|
||||
) as exc_info:
|
||||
vm.set("unknown.parameter", 1)
|
||||
vm.set('unknown.parameter', 1)
|
||||
|
||||
e = exc_info.value
|
||||
assert e.code == -3
|
||||
assert e.fn_name == "VBVMR_SetParameterFloat"
|
||||
assert e.fn_name == 'VBVMR_SetParameterFloat'
|
||||
|
||||
def test_it_tests_an_unknown_config_name(self):
|
||||
EXPECTED_MSG = (
|
||||
"No config with name 'unknown' is loaded into memory",
|
||||
f"Known configs: {list(vm.configs.keys())}",
|
||||
f'Known configs: {list(vm.configs.keys())}',
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
voicemeeterlib.error.VMError, match=re.escape("\n".join(EXPECTED_MSG))
|
||||
voicemeeterlib.error.VMError, match=re.escape('\n'.join(EXPECTED_MSG))
|
||||
):
|
||||
vm.apply_config("unknown")
|
||||
vm.apply_config('unknown')
|
||||
|
||||
def test_it_tests_an_invalid_config_key(self):
|
||||
CONFIG = {
|
||||
"strip-0": {"A1": True, "B1": True, "gain": -6.0},
|
||||
"bus-0": {"mute": True, "eq": {"on": True}},
|
||||
"unknown-0": {"state": True},
|
||||
"vban-out-1": {"name": "streamname"},
|
||||
'strip-0': {'A1': True, 'B1': True, 'gain': -6.0},
|
||||
'bus-0': {'mute': True, 'eq': {'on': True}},
|
||||
'unknown-0': {'state': True},
|
||||
'vban-out-1': {'name': 'streamname'},
|
||||
}
|
||||
with pytest.raises(ValueError, match="invalid config key 'unknown-0'"):
|
||||
vm.apply(CONFIG)
|
||||
|
||||
@@ -7,17 +7,17 @@ class TestRemoteFactories:
|
||||
__test__ = True
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "basic",
|
||||
reason="Skip test if kind is not basic",
|
||||
data.name != 'basic',
|
||||
reason='Skip test if kind is not basic',
|
||||
)
|
||||
def test_it_tests_vm_remote_attrs_for_basic(self):
|
||||
assert hasattr(vm, "strip")
|
||||
assert hasattr(vm, "bus")
|
||||
assert hasattr(vm, "command")
|
||||
assert hasattr(vm, "button")
|
||||
assert hasattr(vm, "vban")
|
||||
assert hasattr(vm, "device")
|
||||
assert hasattr(vm, "option")
|
||||
assert hasattr(vm, 'strip')
|
||||
assert hasattr(vm, 'bus')
|
||||
assert hasattr(vm, 'command')
|
||||
assert hasattr(vm, 'button')
|
||||
assert hasattr(vm, 'vban')
|
||||
assert hasattr(vm, 'device')
|
||||
assert hasattr(vm, 'option')
|
||||
|
||||
assert len(vm.strip) == 3
|
||||
assert len(vm.bus) == 2
|
||||
@@ -25,19 +25,19 @@ class TestRemoteFactories:
|
||||
assert len(vm.vban.instream) == 6 and len(vm.vban.outstream) == 5
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "banana",
|
||||
reason="Skip test if kind is not banana",
|
||||
data.name != 'banana',
|
||||
reason='Skip test if kind is not banana',
|
||||
)
|
||||
def test_it_tests_vm_remote_attrs_for_banana(self):
|
||||
assert hasattr(vm, "strip")
|
||||
assert hasattr(vm, "bus")
|
||||
assert hasattr(vm, "command")
|
||||
assert hasattr(vm, "button")
|
||||
assert hasattr(vm, "vban")
|
||||
assert hasattr(vm, "device")
|
||||
assert hasattr(vm, "option")
|
||||
assert hasattr(vm, "recorder")
|
||||
assert hasattr(vm, "patch")
|
||||
assert hasattr(vm, 'strip')
|
||||
assert hasattr(vm, 'bus')
|
||||
assert hasattr(vm, 'command')
|
||||
assert hasattr(vm, 'button')
|
||||
assert hasattr(vm, 'vban')
|
||||
assert hasattr(vm, 'device')
|
||||
assert hasattr(vm, 'option')
|
||||
assert hasattr(vm, 'recorder')
|
||||
assert hasattr(vm, 'patch')
|
||||
|
||||
assert len(vm.strip) == 5
|
||||
assert len(vm.bus) == 5
|
||||
@@ -45,20 +45,20 @@ class TestRemoteFactories:
|
||||
assert len(vm.vban.instream) == 10 and len(vm.vban.outstream) == 9
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Skip test if kind is not potato",
|
||||
data.name != 'potato',
|
||||
reason='Skip test if kind is not potato',
|
||||
)
|
||||
def test_it_tests_vm_remote_attrs_for_potato(self):
|
||||
assert hasattr(vm, "strip")
|
||||
assert hasattr(vm, "bus")
|
||||
assert hasattr(vm, "command")
|
||||
assert hasattr(vm, "button")
|
||||
assert hasattr(vm, "vban")
|
||||
assert hasattr(vm, "device")
|
||||
assert hasattr(vm, "option")
|
||||
assert hasattr(vm, "recorder")
|
||||
assert hasattr(vm, "patch")
|
||||
assert hasattr(vm, "fx")
|
||||
assert hasattr(vm, 'strip')
|
||||
assert hasattr(vm, 'bus')
|
||||
assert hasattr(vm, 'command')
|
||||
assert hasattr(vm, 'button')
|
||||
assert hasattr(vm, 'vban')
|
||||
assert hasattr(vm, 'device')
|
||||
assert hasattr(vm, 'option')
|
||||
assert hasattr(vm, 'recorder')
|
||||
assert hasattr(vm, 'patch')
|
||||
assert hasattr(vm, 'fx')
|
||||
|
||||
assert len(vm.strip) == 8
|
||||
assert len(vm.bus) == 8
|
||||
|
||||
@@ -3,19 +3,18 @@ import pytest
|
||||
from tests import data, vm
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [False, True])
|
||||
@pytest.mark.parametrize('value', [False, True])
|
||||
class TestSetAndGetBoolHigher:
|
||||
__test__ = True
|
||||
|
||||
"""strip tests, physical and virtual"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
'index,param',
|
||||
[
|
||||
(data.phys_in, "mute"),
|
||||
(data.phys_in, "mono"),
|
||||
(data.virt_in, "mc"),
|
||||
(data.virt_in, "mono"),
|
||||
(data.phys_in, 'mute'),
|
||||
(data.phys_in, 'mono'),
|
||||
(data.virt_in, 'mc'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_bool_params(self, index, param, value):
|
||||
@@ -25,14 +24,14 @@ class TestSetAndGetBoolHigher:
|
||||
""" strip EQ tests, physical """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Skip test if kind is not potato",
|
||||
data.name != 'potato',
|
||||
reason='Skip test if kind is not potato',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
'index,param',
|
||||
[
|
||||
(data.phys_in, "on"),
|
||||
(data.phys_in, "ab"),
|
||||
(data.phys_in, 'on'),
|
||||
(data.phys_in, 'ab'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_eq_bool_params(self, index, param, value):
|
||||
@@ -43,10 +42,10 @@ class TestSetAndGetBoolHigher:
|
||||
""" bus tests, physical and virtual """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
'index,param',
|
||||
[
|
||||
(data.phys_out, "mute"),
|
||||
(data.virt_out, "sel"),
|
||||
(data.phys_out, 'mute'),
|
||||
(data.virt_out, 'sel'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_bus_bool_params(self, index, param, value):
|
||||
@@ -57,10 +56,10 @@ class TestSetAndGetBoolHigher:
|
||||
""" bus EQ tests, physical and virtual """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
'index,param',
|
||||
[
|
||||
(data.phys_out, "on"),
|
||||
(data.virt_out, "ab"),
|
||||
(data.phys_out, 'on'),
|
||||
(data.virt_out, 'ab'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_bus_eq_bool_params(self, index, param, value):
|
||||
@@ -71,16 +70,16 @@ class TestSetAndGetBoolHigher:
|
||||
""" bus modes tests, physical and virtual """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "basic",
|
||||
reason="Skip test if kind is not basic",
|
||||
data.name != 'basic',
|
||||
reason='Skip test if kind is not basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
'index,param',
|
||||
[
|
||||
(data.phys_out, "normal"),
|
||||
(data.phys_out, "amix"),
|
||||
(data.virt_out, "normal"),
|
||||
(data.virt_out, "composite"),
|
||||
(data.phys_out, 'normal'),
|
||||
(data.phys_out, 'amix'),
|
||||
(data.virt_out, 'normal'),
|
||||
(data.virt_out, 'composite'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_busmode_basic_bool_params(self, index, param, value):
|
||||
@@ -88,18 +87,18 @@ class TestSetAndGetBoolHigher:
|
||||
assert getattr(vm.bus[index].mode, param) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
'index,param',
|
||||
[
|
||||
(data.phys_out, "normal"),
|
||||
(data.phys_out, "amix"),
|
||||
(data.phys_out, "rearonly"),
|
||||
(data.virt_out, "normal"),
|
||||
(data.virt_out, "upmix41"),
|
||||
(data.virt_out, "composite"),
|
||||
(data.phys_out, 'normal'),
|
||||
(data.phys_out, 'amix'),
|
||||
(data.phys_out, 'rearonly'),
|
||||
(data.virt_out, 'normal'),
|
||||
(data.virt_out, 'upmix41'),
|
||||
(data.virt_out, 'composite'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_busmode_bool_params(self, index, param, value):
|
||||
@@ -109,8 +108,8 @@ class TestSetAndGetBoolHigher:
|
||||
""" macrobutton tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
[(data.button_lower, "state"), (data.button_upper, "trigger")],
|
||||
'index,param',
|
||||
[(data.button_lower, 'state'), (data.button_upper, 'trigger')],
|
||||
)
|
||||
def test_it_sets_and_gets_macrobutton_bool_params(self, index, param, value):
|
||||
setattr(vm.button[index], param, value)
|
||||
@@ -119,8 +118,8 @@ class TestSetAndGetBoolHigher:
|
||||
""" vban instream tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
[(data.vban_in, "on")],
|
||||
'index,param',
|
||||
[(data.vban_in, 'on')],
|
||||
)
|
||||
def test_it_sets_and_gets_vban_instream_bool_params(self, index, param, value):
|
||||
setattr(vm.vban.instream[index], param, value)
|
||||
@@ -129,8 +128,8 @@ class TestSetAndGetBoolHigher:
|
||||
""" vban outstream tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param",
|
||||
[(data.vban_out, "on")],
|
||||
'index,param',
|
||||
[(data.vban_out, 'on')],
|
||||
)
|
||||
def test_it_sets_and_gets_vban_outstream_bool_params(self, index, param, value):
|
||||
setattr(vm.vban.outstream[index], param, value)
|
||||
@@ -139,8 +138,8 @@ class TestSetAndGetBoolHigher:
|
||||
""" command tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("lock")],
|
||||
'param',
|
||||
[('lock')],
|
||||
)
|
||||
def test_it_sets_command_bool_params(self, param, value):
|
||||
setattr(vm.command, param, value)
|
||||
@@ -148,12 +147,12 @@ class TestSetAndGetBoolHigher:
|
||||
""" recorder tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("A1"), ("B2")],
|
||||
'param',
|
||||
[('A1'), ('B2')],
|
||||
)
|
||||
def test_it_sets_and_gets_recorder_bool_params(self, param, value):
|
||||
assert hasattr(vm.recorder, param)
|
||||
@@ -161,12 +160,12 @@ class TestSetAndGetBoolHigher:
|
||||
assert getattr(vm.recorder, param) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("loop")],
|
||||
'param',
|
||||
[('loop')],
|
||||
)
|
||||
def test_it_sets_recorder_bool_params(self, param, value):
|
||||
assert hasattr(vm.recorder, param)
|
||||
@@ -176,12 +175,12 @@ class TestSetAndGetBoolHigher:
|
||||
""" recoder.mode tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("loop"), ("recbus")],
|
||||
'param',
|
||||
[('loop'), ('recbus')],
|
||||
)
|
||||
def test_it_sets_recorder_mode_bool_params(self, param, value):
|
||||
assert hasattr(vm.recorder.mode, param)
|
||||
@@ -191,11 +190,11 @@ class TestSetAndGetBoolHigher:
|
||||
""" recorder.armstrip """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index",
|
||||
'index',
|
||||
[
|
||||
(data.phys_out),
|
||||
(data.virt_out),
|
||||
@@ -207,11 +206,11 @@ class TestSetAndGetBoolHigher:
|
||||
""" recorder.armbus """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index",
|
||||
'index',
|
||||
[
|
||||
(data.phys_out),
|
||||
(data.virt_out),
|
||||
@@ -223,12 +222,12 @@ class TestSetAndGetBoolHigher:
|
||||
""" fx tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Skip test if kind is not potato",
|
||||
data.name != 'potato',
|
||||
reason='Skip test if kind is not potato',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("reverb"), ("reverb_ab"), ("delay"), ("delay_ab")],
|
||||
'param',
|
||||
[('reverb'), ('reverb_ab'), ('delay'), ('delay_ab')],
|
||||
)
|
||||
def test_it_sets_and_gets_fx_bool_params(self, param, value):
|
||||
setattr(vm.fx, param, value)
|
||||
@@ -237,12 +236,12 @@ class TestSetAndGetBoolHigher:
|
||||
""" patch tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("postfadercomposite")],
|
||||
'param',
|
||||
[('postfadercomposite')],
|
||||
)
|
||||
def test_it_sets_and_gets_patch_bool_params(self, param, value):
|
||||
setattr(vm.patch, param, value)
|
||||
@@ -251,12 +250,12 @@ class TestSetAndGetBoolHigher:
|
||||
""" patch.insert tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, param",
|
||||
[(data.insert_lower, "on"), (data.insert_higher, "on")],
|
||||
'index, param',
|
||||
[(data.insert_lower, 'on'), (data.insert_higher, 'on')],
|
||||
)
|
||||
def test_it_sets_and_gets_patch_insert_bool_params(self, index, param, value):
|
||||
setattr(vm.patch.insert[index], param, value)
|
||||
@@ -265,8 +264,8 @@ class TestSetAndGetBoolHigher:
|
||||
""" option tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[("monitoronsel")],
|
||||
'param',
|
||||
[('monitoronsel')],
|
||||
)
|
||||
def test_it_sets_and_gets_option_bool_params(self, param, value):
|
||||
setattr(vm.option, param, value)
|
||||
@@ -279,36 +278,49 @@ class TestSetAndGetIntHigher:
|
||||
"""strip tests, physical and virtual"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param,value",
|
||||
'index,param,value',
|
||||
[
|
||||
(data.phys_in, "limit", -40),
|
||||
(data.phys_in, "limit", 12),
|
||||
(data.virt_in, "k", 0),
|
||||
(data.virt_in, "k", 4),
|
||||
(data.phys_in, 'limit', -40),
|
||||
(data.phys_in, 'limit', 12),
|
||||
(data.virt_in - 1, 'k', 0),
|
||||
(data.virt_in - 1, 'k', 4),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_bool_params(self, index, param, value):
|
||||
def test_it_sets_and_gets_strip_int_params(self, index, param, value):
|
||||
setattr(vm.strip[index], param, value)
|
||||
assert getattr(vm.strip[index], param) == value
|
||||
|
||||
""" bus tests, physical """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'index,param,value',
|
||||
[
|
||||
(data.phys_out, 'mono', 0),
|
||||
(data.phys_out, 'mono', 2),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_bus_int_params(self, index, param, value):
|
||||
setattr(vm.bus[index], param, value)
|
||||
assert getattr(vm.bus[index], param) == value
|
||||
|
||||
""" vban outstream tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param,value",
|
||||
[(data.vban_out, "sr", 48000)],
|
||||
'index,param,value',
|
||||
[(data.vban_out, 'sr', 48000)],
|
||||
)
|
||||
def test_it_sets_and_gets_vban_outstream_bool_params(self, index, param, value):
|
||||
def test_it_sets_and_gets_vban_outstream_int_params(self, index, param, value):
|
||||
setattr(vm.vban.outstream[index], param, value)
|
||||
assert getattr(vm.vban.outstream[index], param) == value
|
||||
|
||||
""" patch.asio tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[
|
||||
(0, 1),
|
||||
(data.asio_in, 4),
|
||||
@@ -321,11 +333,11 @@ class TestSetAndGetIntHigher:
|
||||
""" patch.A2[i]-A5[i] tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[
|
||||
(0, 1),
|
||||
(data.asio_out, 4),
|
||||
@@ -340,11 +352,11 @@ class TestSetAndGetIntHigher:
|
||||
""" patch.composite tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[
|
||||
(0, 3),
|
||||
(0, data.channels),
|
||||
@@ -359,11 +371,11 @@ class TestSetAndGetIntHigher:
|
||||
""" option tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[
|
||||
(data.phys_out, 30),
|
||||
(data.phys_out, 500),
|
||||
@@ -376,16 +388,16 @@ class TestSetAndGetIntHigher:
|
||||
""" recorder tests """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name == "basic",
|
||||
reason="Skip test if kind is basic",
|
||||
data.name == 'basic',
|
||||
reason='Skip test if kind is basic',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"param,value",
|
||||
'param,value',
|
||||
[
|
||||
("samplerate", 32000),
|
||||
("samplerate", 96000),
|
||||
("bitresolution", 16),
|
||||
("bitresolution", 32),
|
||||
('samplerate', 32000),
|
||||
('samplerate', 96000),
|
||||
('bitresolution', 16),
|
||||
('bitresolution', 32),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_recorder_int_params(self, param, value):
|
||||
@@ -400,10 +412,10 @@ class TestSetAndGetFloatHigher:
|
||||
"""strip tests, physical and virtual"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,param,value",
|
||||
'index,param,value',
|
||||
[
|
||||
(data.phys_in, "gain", -3.6),
|
||||
(data.virt_in, "gain", 5.8),
|
||||
(data.phys_in, 'gain', -3.6),
|
||||
(data.virt_in, 'gain', 5.8),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_float_params(self, index, param, value):
|
||||
@@ -411,25 +423,25 @@ class TestSetAndGetFloatHigher:
|
||||
assert getattr(vm.strip[index], param) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[(data.phys_in, 2), (data.phys_in, 2), (data.virt_in, 8), (data.virt_in, 8)],
|
||||
)
|
||||
def test_it_gets_prefader_levels_and_compares_length_of_array(self, index, value):
|
||||
assert len(vm.strip[index].levels.prefader) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[(data.phys_in, 2), (data.phys_in, 2), (data.virt_in, 8), (data.virt_in, 8)],
|
||||
)
|
||||
def test_it_gets_postmute_levels_and_compares_length_of_array(self, index, value):
|
||||
assert len(vm.strip[index].levels.postmute) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Only test if logged into Potato version",
|
||||
data.name != 'potato',
|
||||
reason='Only test if logged into Potato version',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, j, value",
|
||||
'index, j, value',
|
||||
[
|
||||
(data.phys_in, 0, -20.7),
|
||||
(data.virt_in, 3, -60),
|
||||
@@ -444,12 +456,12 @@ class TestSetAndGetFloatHigher:
|
||||
""" strip tests, physical """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
'index, param, value',
|
||||
[
|
||||
(data.phys_in, "pan_x", -0.6),
|
||||
(data.phys_in, "pan_x", 0.6),
|
||||
(data.phys_in, "color_y", 0.8),
|
||||
(data.phys_in, "fx_x", -0.6),
|
||||
(data.phys_in, 'pan_x', -0.6),
|
||||
(data.phys_in, 'pan_x', 0.6),
|
||||
(data.phys_in, 'color_y', 0.8),
|
||||
(data.phys_in, 'fx_x', -0.6),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_xy_params(self, index, param, value):
|
||||
@@ -458,14 +470,14 @@ class TestSetAndGetFloatHigher:
|
||||
assert getattr(vm.strip[index], param) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Only test if logged into Potato version",
|
||||
data.name != 'potato',
|
||||
reason='Only test if logged into Potato version',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
'index, param, value',
|
||||
[
|
||||
(data.phys_in, "reverb", -1.6),
|
||||
(data.phys_in, "postfx1", True),
|
||||
(data.phys_in, 'reverb', -1.6),
|
||||
(data.phys_in, 'postfx1', True),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_effects_params(self, index, param, value):
|
||||
@@ -474,14 +486,14 @@ class TestSetAndGetFloatHigher:
|
||||
assert getattr(vm.strip[index], param) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Only test if logged into Potato version",
|
||||
data.name != 'potato',
|
||||
reason='Only test if logged into Potato version',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
'index, param, value',
|
||||
[
|
||||
(data.phys_in, "gainin", -8.6),
|
||||
(data.phys_in, "knee", 0.5),
|
||||
(data.phys_in, 'gainin', -8.6),
|
||||
(data.phys_in, 'knee', 0.5),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_comp_params(self, index, param, value):
|
||||
@@ -490,14 +502,14 @@ class TestSetAndGetFloatHigher:
|
||||
assert getattr(vm.strip[index].comp, param) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Only test if logged into Potato version",
|
||||
data.name != 'potato',
|
||||
reason='Only test if logged into Potato version',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
'index, param, value',
|
||||
[
|
||||
(data.phys_in, "bpsidechain", 120),
|
||||
(data.phys_in, "hold", 3000),
|
||||
(data.phys_in, 'bpsidechain', 120),
|
||||
(data.phys_in, 'hold', 3000),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_gate_params(self, index, param, value):
|
||||
@@ -506,13 +518,13 @@ class TestSetAndGetFloatHigher:
|
||||
assert getattr(vm.strip[index].gate, param) == value
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Only test if logged into Potato version",
|
||||
data.name != 'potato',
|
||||
reason='Only test if logged into Potato version',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
'index, param, value',
|
||||
[
|
||||
(data.phys_in, "knob", -8.6),
|
||||
(data.phys_in, 'knob', -8.6),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_denoiser_params(self, index, param, value):
|
||||
@@ -522,13 +534,13 @@ class TestSetAndGetFloatHigher:
|
||||
""" strip tests, virtual """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
'index, param, value',
|
||||
[
|
||||
(data.virt_in, "pan_x", -0.6),
|
||||
(data.virt_in, "pan_x", 0.6),
|
||||
(data.virt_in, "treble", -1.6),
|
||||
(data.virt_in, "mid", 5.8),
|
||||
(data.virt_in, "bass", -8.1),
|
||||
(data.virt_in, 'pan_x', -0.6),
|
||||
(data.virt_in, 'pan_x', 0.6),
|
||||
(data.virt_in, 'treble', -1.6),
|
||||
(data.virt_in, 'mid', 5.8),
|
||||
(data.virt_in, 'bass', -8.1),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_eq_params(self, index, param, value):
|
||||
@@ -538,12 +550,12 @@ class TestSetAndGetFloatHigher:
|
||||
""" bus tests, physical and virtual """
|
||||
|
||||
@pytest.mark.skipif(
|
||||
data.name != "potato",
|
||||
reason="Only test if logged into Potato version",
|
||||
data.name != 'potato',
|
||||
reason='Only test if logged into Potato version',
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
[(data.phys_out, "returnreverb", 3.6), (data.virt_out, "returnfx1", 5.8)],
|
||||
'index, param, value',
|
||||
[(data.phys_out, 'returnreverb', 3.6), (data.virt_out, 'returnfx1', 5.8)],
|
||||
)
|
||||
def test_it_sets_and_gets_bus_effects_float_params(self, index, param, value):
|
||||
assert hasattr(vm.bus[index], param)
|
||||
@@ -551,30 +563,30 @@ class TestSetAndGetFloatHigher:
|
||||
assert getattr(vm.bus[index], param) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param, value",
|
||||
[(data.phys_out, "gain", -3.6), (data.virt_out, "gain", 5.8)],
|
||||
'index, param, value',
|
||||
[(data.phys_out, 'gain', -3.6), (data.virt_out, 'gain', 5.8)],
|
||||
)
|
||||
def test_it_sets_and_gets_bus_float_params(self, index, param, value):
|
||||
setattr(vm.bus[index], param, value)
|
||||
assert getattr(vm.bus[index], param) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index,value",
|
||||
'index,value',
|
||||
[(data.phys_out, 8), (data.virt_out, 8)],
|
||||
)
|
||||
def test_it_gets_prefader_levels_and_compares_length_of_array(self, index, value):
|
||||
def test_it_gets_bus_levels_and_compares_length_of_array(self, index, value):
|
||||
assert len(vm.bus[index].levels.all) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["test0", "test1"])
|
||||
@pytest.mark.parametrize('value', ['test0', 'test1'])
|
||||
class TestSetAndGetStringHigher:
|
||||
__test__ = True
|
||||
|
||||
"""strip tests, physical and virtual"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param",
|
||||
[(data.phys_in, "label"), (data.virt_in, "label")],
|
||||
'index, param',
|
||||
[(data.phys_in, 'label'), (data.virt_in, 'label')],
|
||||
)
|
||||
def test_it_sets_and_gets_strip_string_params(self, index, param, value):
|
||||
setattr(vm.strip[index], param, value)
|
||||
@@ -583,8 +595,8 @@ class TestSetAndGetStringHigher:
|
||||
""" bus tests, physical and virtual """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param",
|
||||
[(data.phys_out, "label"), (data.virt_out, "label")],
|
||||
'index, param',
|
||||
[(data.phys_out, 'label'), (data.virt_out, 'label')],
|
||||
)
|
||||
def test_it_sets_and_gets_bus_string_params(self, index, param, value):
|
||||
setattr(vm.bus[index], param, value)
|
||||
@@ -593,8 +605,8 @@ class TestSetAndGetStringHigher:
|
||||
""" vban instream tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param",
|
||||
[(data.vban_in, "name")],
|
||||
'index, param',
|
||||
[(data.vban_in, 'name')],
|
||||
)
|
||||
def test_it_sets_and_gets_vban_instream_string_params(self, index, param, value):
|
||||
setattr(vm.vban.instream[index], param, value)
|
||||
@@ -603,29 +615,29 @@ class TestSetAndGetStringHigher:
|
||||
""" vban outstream tests """
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param",
|
||||
[(data.vban_out, "name")],
|
||||
'index, param',
|
||||
[(data.vban_out, 'name')],
|
||||
)
|
||||
def test_it_sets_and_gets_vban_outstream_string_params(self, index, param, value):
|
||||
setattr(vm.vban.outstream[index], param, value)
|
||||
assert getattr(vm.vban.outstream[index], param) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [False, True])
|
||||
@pytest.mark.parametrize('value', [False, True])
|
||||
class TestSetAndGetMacroButtonHigher:
|
||||
__test__ = True
|
||||
|
||||
"""macrobutton tests"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, param",
|
||||
'index, param',
|
||||
[
|
||||
(0, "state"),
|
||||
(39, "stateonly"),
|
||||
(69, "trigger"),
|
||||
(22, "stateonly"),
|
||||
(45, "state"),
|
||||
(65, "trigger"),
|
||||
(0, 'state'),
|
||||
(39, 'stateonly'),
|
||||
(69, 'trigger'),
|
||||
(22, 'stateonly'),
|
||||
(45, 'state'),
|
||||
(65, 'trigger'),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_macrobutton_params(self, index, param, value):
|
||||
|
||||
@@ -9,12 +9,12 @@ class TestSetAndGetFloatLower:
|
||||
"""VBVMR_SetParameterFloat, VBVMR_GetParameterFloat"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param,value",
|
||||
'param,value',
|
||||
[
|
||||
(f"Strip[{data.phys_in}].Mute", 1),
|
||||
(f"Bus[{data.virt_out}].Eq.on", 1),
|
||||
(f"Strip[{data.phys_in}].Mute", 0),
|
||||
(f"Bus[{data.virt_out}].Eq.on", 0),
|
||||
(f'Strip[{data.phys_in}].Mute', 1),
|
||||
(f'Bus[{data.virt_out}].Eq.on', 1),
|
||||
(f'Strip[{data.phys_in}].Mute', 0),
|
||||
(f'Bus[{data.virt_out}].Eq.on', 0),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_mute_eq_float_params(self, param, value):
|
||||
@@ -22,11 +22,11 @@ class TestSetAndGetFloatLower:
|
||||
assert (round(vm.get(param))) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param,value",
|
||||
'param,value',
|
||||
[
|
||||
(f"Strip[{data.phys_in}].Comp", 5.3),
|
||||
(f"Strip[{data.virt_in}].Gain", -37.5),
|
||||
(f"Bus[{data.virt_out}].Gain", -22.7),
|
||||
(f'Strip[{data.phys_in}].Comp', 5.3),
|
||||
(f'Strip[{data.virt_in}].Gain', -37.5),
|
||||
(f'Bus[{data.virt_out}].Gain', -22.7),
|
||||
],
|
||||
)
|
||||
def test_it_sets_and_gets_comp_gain_float_params(self, param, value):
|
||||
@@ -34,29 +34,29 @@ class TestSetAndGetFloatLower:
|
||||
assert (round(vm.get(param), 1)) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", ["test0", "test1"])
|
||||
@pytest.mark.parametrize('value', ['test0', 'test1'])
|
||||
class TestSetAndGetStringLower:
|
||||
__test__ = True
|
||||
|
||||
"""VBVMR_SetParameterStringW, VBVMR_GetParameterStringW"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"param",
|
||||
[(f"Strip[{data.phys_out}].label"), (f"Bus[{data.virt_out}].label")],
|
||||
'param',
|
||||
[(f'Strip[{data.phys_out}].label'), (f'Bus[{data.virt_out}].label')],
|
||||
)
|
||||
def test_it_sets_and_gets_string_params(self, param, value):
|
||||
vm.set(param, value)
|
||||
assert vm.get(param, string=True) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value", [0, 1])
|
||||
@pytest.mark.parametrize('value', [0, 1])
|
||||
class TestMacroButtonsLower:
|
||||
__test__ = True
|
||||
|
||||
"""VBVMR_MacroButton_SetStatus, VBVMR_MacroButton_GetStatus"""
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, mode",
|
||||
'index, mode',
|
||||
[(33, 1), (49, 1)],
|
||||
)
|
||||
def test_it_sets_and_gets_macrobuttons_state(self, index, mode, value):
|
||||
@@ -64,7 +64,7 @@ class TestMacroButtonsLower:
|
||||
assert vm.get_buttonstatus(index, mode) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, mode",
|
||||
'index, mode',
|
||||
[(14, 2), (12, 2)],
|
||||
)
|
||||
def test_it_sets_and_gets_macrobuttons_stateonly(self, index, mode, value):
|
||||
@@ -72,7 +72,7 @@ class TestMacroButtonsLower:
|
||||
assert vm.get_buttonstatus(index, mode) == value
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"index, mode",
|
||||
'index, mode',
|
||||
[(50, 3), (65, 3)],
|
||||
)
|
||||
def test_it_sets_and_gets_macrobuttons_trigger(self, index, mode, value):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import abc
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from enum import IntEnum
|
||||
from math import log
|
||||
from typing import Union
|
||||
@@ -22,7 +22,7 @@ class Bus(IRemote):
|
||||
Defines concrete implementation for bus
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
pass
|
||||
|
||||
@@ -39,12 +39,12 @@ class Bus(IRemote):
|
||||
self.setter('mute', 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mono(self) -> bool:
|
||||
return self.getter('mono') == 1
|
||||
def mono(self) -> int:
|
||||
return int(self.getter('mono'))
|
||||
|
||||
@mono.setter
|
||||
def mono(self, val: bool):
|
||||
self.setter('mono', 1 if val else 0)
|
||||
def mono(self, val: int):
|
||||
self.setter('mono', val)
|
||||
|
||||
@property
|
||||
def sel(self) -> bool:
|
||||
@@ -95,16 +95,16 @@ class BusEQ(IRemote):
|
||||
|
||||
Returns a BusEQ class.
|
||||
"""
|
||||
kls = (cls,)
|
||||
return type(
|
||||
BusEQ_cls = type(
|
||||
'BusEQ',
|
||||
kls,
|
||||
(cls,),
|
||||
{
|
||||
'channel': tuple(
|
||||
BusEQCh.make(remote, i, j) for j in range(remote.kind.channels)
|
||||
BusEQCh.make(remote, i, j) for j in range(remote.kind.bus_channels)
|
||||
)
|
||||
},
|
||||
)
|
||||
return BusEQ_cls(remote, i)
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
@@ -135,16 +135,16 @@ class BusEQCh(IRemote):
|
||||
|
||||
Returns a BusEQCh class.
|
||||
"""
|
||||
kls = (cls,)
|
||||
return type(
|
||||
BusEQCh_cls = type(
|
||||
'BusEQCh',
|
||||
kls,
|
||||
(cls,),
|
||||
{
|
||||
'cell': tuple(
|
||||
BusEQChCell(remote, i, j, k) for k in range(remote.kind.cells)
|
||||
)
|
||||
},
|
||||
)
|
||||
return BusEQCh_cls(remote, i, j)
|
||||
|
||||
def __init__(self, remote, i, j):
|
||||
super().__init__(remote, i)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import ctypes as ct
|
||||
import logging
|
||||
from abc import ABCMeta
|
||||
from ctypes.wintypes import CHAR, FLOAT, LONG, WCHAR
|
||||
|
||||
from .error import CAPIError
|
||||
@@ -9,11 +8,10 @@ from .inst import libc
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CBindings(metaclass=ABCMeta):
|
||||
"""
|
||||
C bindings defined here.
|
||||
class CBindings:
|
||||
"""Class responsible for defining C function bindings.
|
||||
|
||||
Maps expected ctype argument and res types for each binding.
|
||||
Wrapper methods are provided for each C function to handle error checking and logging.
|
||||
"""
|
||||
|
||||
logger_cbindings = logger.getChild('CBindings')
|
||||
@@ -111,7 +109,8 @@ class CBindings(metaclass=ABCMeta):
|
||||
bind_get_midi_message.restype = LONG
|
||||
bind_get_midi_message.argtypes = [ct.POINTER(CHAR * 1024), LONG]
|
||||
|
||||
def call(self, func, *args, ok=(0,), ok_exp=None):
|
||||
def _call(self, func, *args, ok=(0,), ok_exp=None):
|
||||
"""Call a C function and handle errors."""
|
||||
try:
|
||||
res = func(*args)
|
||||
if ok_exp is None:
|
||||
@@ -123,3 +122,93 @@ class CBindings(metaclass=ABCMeta):
|
||||
except CAPIError as e:
|
||||
self.logger_cbindings.exception(f'{type(e).__name__}: {e}')
|
||||
raise
|
||||
|
||||
def login(self, **kwargs):
|
||||
"""Login to Voicemeeter API"""
|
||||
return self._call(self.bind_login, **kwargs)
|
||||
|
||||
def logout(self):
|
||||
"""Logout from Voicemeeter API"""
|
||||
return self._call(self.bind_logout)
|
||||
|
||||
def run_voicemeeter(self, value):
|
||||
"""Run Voicemeeter with specified type"""
|
||||
return self._call(self.bind_run_voicemeeter, value)
|
||||
|
||||
def get_voicemeeter_type(self, type_ref):
|
||||
"""Get Voicemeeter type"""
|
||||
return self._call(self.bind_get_voicemeeter_type, type_ref)
|
||||
|
||||
def get_voicemeeter_version(self, version_ref):
|
||||
"""Get Voicemeeter version"""
|
||||
return self._call(self.bind_get_voicemeeter_version, version_ref)
|
||||
|
||||
def is_parameters_dirty(self, **kwargs):
|
||||
"""Check if parameters are dirty"""
|
||||
return self._call(self.bind_is_parameters_dirty, **kwargs)
|
||||
|
||||
def macro_button_is_dirty(self, **kwargs):
|
||||
"""Check if macro button parameters are dirty"""
|
||||
if hasattr(self, 'bind_macro_button_is_dirty'):
|
||||
return self._call(self.bind_macro_button_is_dirty, **kwargs)
|
||||
raise AttributeError('macro_button_is_dirty not available')
|
||||
|
||||
def get_parameter_float(self, param_name, value_ref):
|
||||
"""Get float parameter value"""
|
||||
return self._call(self.bind_get_parameter_float, param_name, value_ref)
|
||||
|
||||
def set_parameter_float(self, param_name, value):
|
||||
"""Set float parameter value"""
|
||||
return self._call(self.bind_set_parameter_float, param_name, value)
|
||||
|
||||
def get_parameter_string_w(self, param_name, buffer_ref):
|
||||
"""Get string parameter value (Unicode)"""
|
||||
return self._call(self.bind_get_parameter_string_w, param_name, buffer_ref)
|
||||
|
||||
def set_parameter_string_w(self, param_name, value):
|
||||
"""Set string parameter value (Unicode)"""
|
||||
return self._call(self.bind_set_parameter_string_w, param_name, value)
|
||||
|
||||
def macro_button_get_status(self, id_, state_ref, mode):
|
||||
"""Get macro button status"""
|
||||
if hasattr(self, 'bind_macro_button_get_status'):
|
||||
return self._call(self.bind_macro_button_get_status, id_, state_ref, mode)
|
||||
raise AttributeError('macro_button_get_status not available')
|
||||
|
||||
def macro_button_set_status(self, id_, state, mode):
|
||||
"""Set macro button status"""
|
||||
if hasattr(self, 'bind_macro_button_set_status'):
|
||||
return self._call(self.bind_macro_button_set_status, id_, state, mode)
|
||||
raise AttributeError('macro_button_set_status not available')
|
||||
|
||||
def get_level(self, type_, index, value_ref):
|
||||
"""Get audio level"""
|
||||
return self._call(self.bind_get_level, type_, index, value_ref)
|
||||
|
||||
def input_get_device_number(self, **kwargs):
|
||||
"""Get number of input devices"""
|
||||
return self._call(self.bind_input_get_device_number, **kwargs)
|
||||
|
||||
def output_get_device_number(self, **kwargs):
|
||||
"""Get number of output devices"""
|
||||
return self._call(self.bind_output_get_device_number, **kwargs)
|
||||
|
||||
def input_get_device_desc_w(self, index, type_ref, name_ref, hwid_ref):
|
||||
"""Get input device description"""
|
||||
return self._call(
|
||||
self.bind_input_get_device_desc_w, index, type_ref, name_ref, hwid_ref
|
||||
)
|
||||
|
||||
def output_get_device_desc_w(self, index, type_ref, name_ref, hwid_ref):
|
||||
"""Get output device description"""
|
||||
return self._call(
|
||||
self.bind_output_get_device_desc_w, index, type_ref, name_ref, hwid_ref
|
||||
)
|
||||
|
||||
def get_midi_message(self, buffer_ref, length, **kwargs):
|
||||
"""Get MIDI message"""
|
||||
return self._call(self.bind_get_midi_message, buffer_ref, length, **kwargs)
|
||||
|
||||
def set_parameters(self, script):
|
||||
"""Set multiple parameters via script"""
|
||||
return self._call(self.bind_set_parameters, script)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from abc import abstractmethod
|
||||
import abc
|
||||
from typing import Union
|
||||
|
||||
from .iremote import IRemote
|
||||
@@ -7,19 +7,19 @@ from .iremote import IRemote
|
||||
class Adapter(IRemote):
|
||||
"""Adapter to the common interface."""
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def ins(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def outs(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def input(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def output(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
from abc import abstractmethod
|
||||
from enum import IntEnum
|
||||
from functools import cached_property
|
||||
from typing import Iterable
|
||||
@@ -137,11 +136,6 @@ class FactoryBase(Remote):
|
||||
def __str__(self) -> str:
|
||||
return f'Voicemeeter {self.kind}'
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def steps(self):
|
||||
pass
|
||||
|
||||
@cached_property
|
||||
def configs(self):
|
||||
self._configs = configs(self.kind.name)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import abc
|
||||
import logging
|
||||
import time
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IRemote(metaclass=ABCMeta):
|
||||
class IRemote(abc.ABC):
|
||||
"""
|
||||
Common interface between base class and extended (higher) classes
|
||||
|
||||
@@ -33,7 +33,7 @@ class IRemote(metaclass=ABCMeta):
|
||||
cmd += (f'.{param}',)
|
||||
return ''.join(cmd)
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def identifier(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ class KindMapClass(metaclass=SingletonType):
|
||||
asio: tuple
|
||||
insert: int
|
||||
composite: int
|
||||
channels: int
|
||||
strip_channels: int
|
||||
bus_channels: int
|
||||
cells: int
|
||||
|
||||
@property
|
||||
@@ -78,7 +79,8 @@ class BasicMap(KindMapClass):
|
||||
asio: tuple = (0, 0)
|
||||
insert: int = 0
|
||||
composite: int = 0
|
||||
channels: int = 0
|
||||
strip_channels: int = 0
|
||||
bus_channels: int = 0
|
||||
cells: int = 0
|
||||
|
||||
|
||||
@@ -90,7 +92,8 @@ class BananaMap(KindMapClass):
|
||||
asio: tuple = (6, 8)
|
||||
insert: int = 22
|
||||
composite: int = 8
|
||||
channels: int = 9
|
||||
strip_channels: int = 0
|
||||
bus_channels: int = 8
|
||||
cells: int = 6
|
||||
|
||||
|
||||
@@ -102,7 +105,8 @@ class PotatoMap(KindMapClass):
|
||||
asio: tuple = (10, 8)
|
||||
insert: int = 34
|
||||
composite: int = 8
|
||||
channels: int = 9
|
||||
strip_channels: int = 2
|
||||
bus_channels: int = 8
|
||||
cells: int = 6
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import abc
|
||||
import ctypes as ct
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from queue import Queue
|
||||
from typing import Iterable, Optional, Union
|
||||
|
||||
@@ -19,12 +19,13 @@ from .util import deep_merge, grouper, polling, script, timeout
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Remote(CBindings):
|
||||
"""Base class responsible for wrapping the C Remote API"""
|
||||
class Remote(abc.ABC):
|
||||
"""An abstract base class for Voicemeeter Remote API wrappers. Defines common methods and properties."""
|
||||
|
||||
DELAY = 0.001
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._bindings = CBindings()
|
||||
self.strip_mode = 0
|
||||
self.cache = {}
|
||||
self.midi = Midi()
|
||||
@@ -52,10 +53,10 @@ class Remote(CBindings):
|
||||
self.init_thread()
|
||||
return self
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self):
|
||||
"""Ensure subclasses override str magic method"""
|
||||
pass
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def steps(self):
|
||||
"""Steps required to build the interface for this Voicemeeter kind"""
|
||||
|
||||
def init_thread(self):
|
||||
"""Starts updates thread."""
|
||||
@@ -76,7 +77,7 @@ class Remote(CBindings):
|
||||
@timeout
|
||||
def login(self) -> None:
|
||||
"""Login to the API, initialize dirty parameters"""
|
||||
self.gui.launched = self.call(self.bind_login, ok=(0, 1)) == 0
|
||||
self.gui.launched = self._bindings.login(ok=(0, 1)) == 0
|
||||
if not self.gui.launched:
|
||||
self.logger.info(
|
||||
'Voicemeeter engine running but GUI not launched. Launching the GUI now.'
|
||||
@@ -89,20 +90,20 @@ class Remote(CBindings):
|
||||
value = KindId[kind_id.upper()].value
|
||||
if BITS == 64 and self.bits == 64:
|
||||
value += 3
|
||||
self.call(self.bind_run_voicemeeter, value)
|
||||
self._bindings.run_voicemeeter(value)
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
"""Returns the type of Voicemeeter installation (basic, banana, potato)."""
|
||||
type_ = ct.c_long()
|
||||
self.call(self.bind_get_voicemeeter_type, ct.byref(type_))
|
||||
self._bindings.get_voicemeeter_type(ct.byref(type_))
|
||||
return KindId(type_.value).name.lower()
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
"""Returns Voicemeeter's version as a string"""
|
||||
ver = ct.c_long()
|
||||
self.call(self.bind_get_voicemeeter_version, ct.byref(ver))
|
||||
self._bindings.get_voicemeeter_version(ct.byref(ver))
|
||||
return '{}.{}.{}.{}'.format(
|
||||
(ver.value & 0xFF000000) >> 24,
|
||||
(ver.value & 0x00FF0000) >> 16,
|
||||
@@ -113,13 +114,13 @@ class Remote(CBindings):
|
||||
@property
|
||||
def pdirty(self) -> bool:
|
||||
"""True iff UI parameters have been updated."""
|
||||
return self.call(self.bind_is_parameters_dirty, ok=(0, 1)) == 1
|
||||
return self._bindings.is_parameters_dirty(ok=(0, 1)) == 1
|
||||
|
||||
@property
|
||||
def mdirty(self) -> bool:
|
||||
"""True iff MB parameters have been updated."""
|
||||
try:
|
||||
return self.call(self.bind_macro_button_is_dirty, ok=(0, 1)) == 1
|
||||
return self._bindings.macro_button_is_dirty(ok=(0, 1)) == 1
|
||||
except AttributeError as e:
|
||||
self.logger.exception(f'{type(e).__name__}: {e}')
|
||||
raise CAPIError('VBVMR_MacroButton_IsDirty', -9) from e
|
||||
@@ -149,10 +150,10 @@ class Remote(CBindings):
|
||||
"""Gets a string or float parameter"""
|
||||
if is_string:
|
||||
buf = ct.create_unicode_buffer(512)
|
||||
self.call(self.bind_get_parameter_string_w, param.encode(), ct.byref(buf))
|
||||
self._bindings.get_parameter_string_w(param.encode(), ct.byref(buf))
|
||||
else:
|
||||
buf = ct.c_float()
|
||||
self.call(self.bind_get_parameter_float, param.encode(), ct.byref(buf))
|
||||
self._bindings.get_parameter_float(param.encode(), ct.byref(buf))
|
||||
return buf.value
|
||||
|
||||
def set(self, param: str, val: Union[str, float]) -> None:
|
||||
@@ -160,12 +161,11 @@ class Remote(CBindings):
|
||||
if isinstance(val, str):
|
||||
if len(val) >= 512:
|
||||
raise VMError('String is too long')
|
||||
self.call(
|
||||
self.bind_set_parameter_string_w, param.encode(), ct.c_wchar_p(val)
|
||||
)
|
||||
self._bindings.set_parameter_string_w(param.encode(), ct.c_wchar_p(val))
|
||||
else:
|
||||
self.call(
|
||||
self.bind_set_parameter_float, param.encode(), ct.c_float(float(val))
|
||||
self._bindings.set_parameter_float(
|
||||
param.encode(),
|
||||
ct.c_float(float(val)),
|
||||
)
|
||||
self.cache[param] = val
|
||||
|
||||
@@ -174,8 +174,7 @@ class Remote(CBindings):
|
||||
"""Gets a macrobutton parameter"""
|
||||
c_state = ct.c_float()
|
||||
try:
|
||||
self.call(
|
||||
self.bind_macro_button_get_status,
|
||||
self._bindings.macro_button_get_status(
|
||||
ct.c_long(id_),
|
||||
ct.byref(c_state),
|
||||
ct.c_long(mode),
|
||||
@@ -189,8 +188,7 @@ class Remote(CBindings):
|
||||
"""Sets a macrobutton parameter. Caches value"""
|
||||
c_state = ct.c_float(float(val))
|
||||
try:
|
||||
self.call(
|
||||
self.bind_macro_button_set_status,
|
||||
self._bindings.macro_button_set_status(
|
||||
ct.c_long(id_),
|
||||
c_state,
|
||||
ct.c_long(mode),
|
||||
@@ -204,8 +202,8 @@ class Remote(CBindings):
|
||||
"""Retrieves number of physical devices connected"""
|
||||
if direction not in ('in', 'out'):
|
||||
raise VMError('Expected a direction: in or out')
|
||||
func = getattr(self, f'bind_{direction}put_get_device_number')
|
||||
res = self.call(func, ok_exp=lambda r: r >= 0)
|
||||
func = getattr(self._bindings, f'{direction}put_get_device_number')
|
||||
res = func(ok_exp=lambda r: r >= 0)
|
||||
return res
|
||||
|
||||
def get_device_description(self, index: int, direction: str = None) -> tuple:
|
||||
@@ -215,9 +213,8 @@ class Remote(CBindings):
|
||||
type_ = ct.c_long()
|
||||
name = ct.create_unicode_buffer(256)
|
||||
hwid = ct.create_unicode_buffer(256)
|
||||
func = getattr(self, f'bind_{direction}put_get_device_desc_w')
|
||||
self.call(
|
||||
func,
|
||||
func = getattr(self._bindings, f'{direction}put_get_device_desc_w')
|
||||
func(
|
||||
ct.c_long(index),
|
||||
ct.byref(type_),
|
||||
ct.byref(name),
|
||||
@@ -228,9 +225,7 @@ class Remote(CBindings):
|
||||
def get_level(self, type_: int, index: int) -> float:
|
||||
"""Retrieves a single level value"""
|
||||
val = ct.c_float()
|
||||
self.call(
|
||||
self.bind_get_level, ct.c_long(type_), ct.c_long(index), ct.byref(val)
|
||||
)
|
||||
self._bindings.get_level(ct.c_long(type_), ct.c_long(index), ct.byref(val))
|
||||
return val.value
|
||||
|
||||
def _get_levels(self) -> Iterable:
|
||||
@@ -248,8 +243,7 @@ class Remote(CBindings):
|
||||
def get_midi_message(self):
|
||||
n = ct.c_long(1024)
|
||||
buf = ct.create_string_buffer(1024)
|
||||
res = self.call(
|
||||
self.bind_get_midi_message,
|
||||
res = self._bindings.get_midi_message(
|
||||
ct.byref(buf),
|
||||
n,
|
||||
ok=(-5, -6), # no data received from midi device
|
||||
@@ -272,7 +266,7 @@ class Remote(CBindings):
|
||||
"""Sets many parameters from a script"""
|
||||
if len(script) > 48000:
|
||||
raise ValueError('Script too large, max size 48kB')
|
||||
self.call(self.bind_set_parameters, script.encode())
|
||||
self._bindings.set_parameters(script.encode())
|
||||
time.sleep(self.DELAY * 5)
|
||||
|
||||
def apply(self, data: dict):
|
||||
@@ -339,7 +333,7 @@ class Remote(CBindings):
|
||||
def logout(self) -> None:
|
||||
"""Logout of the API"""
|
||||
time.sleep(0.1)
|
||||
self.call(self.bind_logout)
|
||||
self._bindings.logout()
|
||||
self.logger.info(f'{type(self).__name__}: Successfully logged out of {self}')
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback) -> None:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import abc
|
||||
import time
|
||||
from abc import abstractmethod
|
||||
from math import log
|
||||
from typing import Union
|
||||
|
||||
@@ -15,7 +15,7 @@ class Strip(IRemote):
|
||||
Defines concrete implementation for strip
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
pass
|
||||
|
||||
@@ -96,7 +96,7 @@ class PhysicalStrip(Strip):
|
||||
'comp': StripComp(remote, i),
|
||||
'gate': StripGate(remote, i),
|
||||
'denoiser': StripDenoiser(remote, i),
|
||||
'eq': StripEQ(remote, i),
|
||||
'eq': StripEQ.make(remote, i),
|
||||
'device': StripDevice.make(remote, i),
|
||||
},
|
||||
)
|
||||
@@ -268,6 +268,25 @@ class StripDenoiser(IRemote):
|
||||
|
||||
|
||||
class StripEQ(IRemote):
|
||||
@classmethod
|
||||
def make(cls, remote, i):
|
||||
"""
|
||||
Factory method for Strip EQ.
|
||||
|
||||
Returns a StripEQ class.
|
||||
"""
|
||||
STRIPEQ_cls = type(
|
||||
'StripEQ',
|
||||
(cls,),
|
||||
{
|
||||
'channel': tuple(
|
||||
StripEQCh.make(remote, i, j)
|
||||
for j in range(remote.kind.strip_channels)
|
||||
)
|
||||
},
|
||||
)
|
||||
return STRIPEQ_cls(remote, i)
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return f'Strip[{self.index}].eq'
|
||||
@@ -289,6 +308,85 @@ class StripEQ(IRemote):
|
||||
self.setter('ab', 1 if val else 0)
|
||||
|
||||
|
||||
class StripEQCh(IRemote):
|
||||
@classmethod
|
||||
def make(cls, remote, i, j):
|
||||
"""
|
||||
Factory method for Strip EQ channel.
|
||||
|
||||
Returns a StripEQCh class.
|
||||
"""
|
||||
StripEQCh_cls = type(
|
||||
'StripEQCh',
|
||||
(cls,),
|
||||
{
|
||||
'cell': tuple(
|
||||
StripEQChCell(remote, i, j, k) for k in range(remote.kind.cells)
|
||||
)
|
||||
},
|
||||
)
|
||||
return StripEQCh_cls(remote, i, j)
|
||||
|
||||
def __init__(self, remote, i, j):
|
||||
super().__init__(remote, i)
|
||||
self.channel_index = j
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return f'Strip[{self.index}].eq.channel[{self.channel_index}]'
|
||||
|
||||
|
||||
class StripEQChCell(IRemote):
|
||||
def __init__(self, remote, i, j, k):
|
||||
super().__init__(remote, i)
|
||||
self.channel_index = j
|
||||
self.cell_index = k
|
||||
|
||||
@property
|
||||
def identifier(self) -> str:
|
||||
return f'Strip[{self.index}].eq.channel[{self.channel_index}].cell[{self.cell_index}]'
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter('on') == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
self.setter('on', 1 if val else 0)
|
||||
|
||||
@property
|
||||
def type(self) -> int:
|
||||
return int(self.getter('type'))
|
||||
|
||||
@type.setter
|
||||
def type(self, val: int):
|
||||
self.setter('type', val)
|
||||
|
||||
@property
|
||||
def f(self) -> float:
|
||||
return round(self.getter('f'), 1)
|
||||
|
||||
@f.setter
|
||||
def f(self, val: float):
|
||||
self.setter('f', val)
|
||||
|
||||
@property
|
||||
def gain(self) -> float:
|
||||
return round(self.getter('gain'), 1)
|
||||
|
||||
@gain.setter
|
||||
def gain(self, val: float):
|
||||
self.setter('gain', val)
|
||||
|
||||
@property
|
||||
def q(self) -> float:
|
||||
return round(self.getter('q'), 1)
|
||||
|
||||
@q.setter
|
||||
def q(self, val: float):
|
||||
self.setter('q', val)
|
||||
|
||||
|
||||
class StripDevice(IRemote):
|
||||
@classmethod
|
||||
def make(cls, remote, i):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from abc import abstractmethod
|
||||
import abc
|
||||
|
||||
from . import kinds
|
||||
from .iremote import IRemote
|
||||
@@ -11,7 +11,7 @@ class VbanStream(IRemote):
|
||||
Defines concrete implementation for vban stream
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
@abc.abstractmethod
|
||||
def __str__(self):
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user