Compare commits

..

No commits in common. "dev" and "v2.7.2" have entirely different histories.
dev ... v2.7.2

12 changed files with 89 additions and 168 deletions

View File

@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: environment:
name: pypi name: pypi
url: https://pypi.org/project/voicemeeter-api/ url: https://pypi.org/project/vban-cmd/
permissions: permissions:
id-token: write id-token: write
steps: steps:

View File

@ -5,9 +5,3 @@ repos:
- id: check-yaml - id: check-yaml
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/python-poetry/poetry
rev: '2.3.2'
hooks:
- id: poetry-check
- id: poetry-lock

View File

@ -14,9 +14,9 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
## Tested against ## Tested against
- Basic 1.1.2.2 - Basic 1.1.1.1
- Banana 2.1.2.2 - Banana 2.1.1.1
- Potato 3.1.2.2 - Potato 3.1.1.1
## Requirements ## Requirements

42
poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
[[package]] [[package]]
name = "cachetools" name = "cachetools"
@ -55,7 +55,7 @@ description = "Backport of PEP 654 (exception groups)"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["dev"] groups = ["dev"]
markers = "python_version == \"3.10\"" markers = "python_version < \"3.11\""
files = [ files = [
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
@ -66,16 +66,21 @@ test = ["pytest (>=6)"]
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.20.3" version = "3.16.1"
description = "A platform independent file lock." description = "A platform independent file lock."
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1"}, {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
{file = "filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
] ]
[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]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "2.0.0" version = "2.0.0"
@ -238,7 +243,7 @@ description = "A lil' TOML parser"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main", "dev"] groups = ["main", "dev"]
markers = "python_version == \"3.10\"" markers = "python_version < \"3.11\""
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"},
@ -304,38 +309,37 @@ test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.3)", "pytest-mock (>=3.14)"]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.15.0" version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.9+" description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev"]
markers = "python_version == \"3.10\"" markers = "python_version < \"3.11\""
files = [ files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
] ]
[[package]] [[package]]
name = "virtualenv" name = "virtualenv"
version = "20.36.1" version = "20.28.1"
description = "Virtual Python Environment builder" description = "Virtual Python Environment builder"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["dev"] groups = ["dev"]
files = [ files = [
{file = "virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f"}, {file = "virtualenv-20.28.1-py3-none-any.whl", hash = "sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb"},
{file = "virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba"}, {file = "virtualenv-20.28.1.tar.gz", hash = "sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329"},
] ]
[package.dependencies] [package.dependencies]
distlib = ">=0.3.7,<1" distlib = ">=0.3.7,<1"
filelock = {version = ">=3.20.1,<4", markers = "python_version >= \"3.10\""} filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5" platformdirs = ">=3.9.1,<5"
typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""}
[package.extras] [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)"] 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) ; 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\""] 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)"]
[[package]] [[package]]
name = "virtualenv-pyenv" name = "virtualenv-pyenv"

View File

@ -1,5 +1,5 @@
import abc
import time import time
from abc import abstractmethod
from enum import IntEnum from enum import IntEnum
from math import log from math import log
from typing import Union from typing import Union
@ -22,7 +22,7 @@ class Bus(IRemote):
Defines concrete implementation for bus Defines concrete implementation for bus
""" """
@abc.abstractmethod @abstractmethod
def __str__(self): def __str__(self):
pass pass

View File

@ -1,5 +1,6 @@
import ctypes as ct import ctypes as ct
import logging import logging
from abc import ABCMeta
from ctypes.wintypes import CHAR, FLOAT, LONG, WCHAR from ctypes.wintypes import CHAR, FLOAT, LONG, WCHAR
from .error import CAPIError from .error import CAPIError
@ -8,10 +9,11 @@ from .inst import libc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class CBindings: class CBindings(metaclass=ABCMeta):
"""Class responsible for defining C function bindings. """
C bindings defined here.
Wrapper methods are provided for each C function to handle error checking and logging. Maps expected ctype argument and res types for each binding.
""" """
logger_cbindings = logger.getChild('CBindings') logger_cbindings = logger.getChild('CBindings')
@ -109,8 +111,7 @@ class CBindings:
bind_get_midi_message.restype = LONG bind_get_midi_message.restype = LONG
bind_get_midi_message.argtypes = [ct.POINTER(CHAR * 1024), 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: try:
res = func(*args) res = func(*args)
if ok_exp is None: if ok_exp is None:
@ -122,93 +123,3 @@ class CBindings:
except CAPIError as e: except CAPIError as e:
self.logger_cbindings.exception(f'{type(e).__name__}: {e}') self.logger_cbindings.exception(f'{type(e).__name__}: {e}')
raise 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)

View File

@ -1,4 +1,4 @@
import abc from abc import abstractmethod
from typing import Union from typing import Union
from .iremote import IRemote from .iremote import IRemote
@ -7,19 +7,19 @@ from .iremote import IRemote
class Adapter(IRemote): class Adapter(IRemote):
"""Adapter to the common interface.""" """Adapter to the common interface."""
@abc.abstractmethod @abstractmethod
def ins(self): def ins(self):
pass pass
@abc.abstractmethod @abstractmethod
def outs(self): def outs(self):
pass pass
@abc.abstractmethod @abstractmethod
def input(self): def input(self):
pass pass
@abc.abstractmethod @abstractmethod
def output(self): def output(self):
pass pass

View File

@ -1,4 +1,5 @@
import logging import logging
from abc import abstractmethod
from enum import IntEnum from enum import IntEnum
from functools import cached_property from functools import cached_property
from typing import Iterable from typing import Iterable
@ -136,6 +137,11 @@ class FactoryBase(Remote):
def __str__(self) -> str: def __str__(self) -> str:
return f'Voicemeeter {self.kind}' return f'Voicemeeter {self.kind}'
@property
@abstractmethod
def steps(self):
pass
@cached_property @cached_property
def configs(self): def configs(self):
self._configs = configs(self.kind.name) self._configs = configs(self.kind.name)

View File

@ -1,11 +1,11 @@
import abc
import logging import logging
import time import time
from abc import ABCMeta, abstractmethod
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class IRemote(abc.ABC): class IRemote(metaclass=ABCMeta):
""" """
Common interface between base class and extended (higher) classes Common interface between base class and extended (higher) classes
@ -33,7 +33,7 @@ class IRemote(abc.ABC):
cmd += (f'.{param}',) cmd += (f'.{param}',)
return ''.join(cmd) return ''.join(cmd)
@abc.abstractmethod @abstractmethod
def identifier(self): def identifier(self):
pass pass

View File

@ -1,8 +1,8 @@
import abc
import ctypes as ct import ctypes as ct
import logging import logging
import threading import threading
import time import time
from abc import abstractmethod
from queue import Queue from queue import Queue
from typing import Iterable, Optional, Union from typing import Iterable, Optional, Union
@ -19,13 +19,12 @@ from .util import deep_merge, grouper, polling, script, timeout
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Remote(abc.ABC): class Remote(CBindings):
"""An abstract base class for Voicemeeter Remote API wrappers. Defines common methods and properties.""" """Base class responsible for wrapping the C Remote API"""
DELAY = 0.001 DELAY = 0.001
def __init__(self, **kwargs): def __init__(self, **kwargs):
self._bindings = CBindings()
self.strip_mode = 0 self.strip_mode = 0
self.cache = {} self.cache = {}
self.midi = Midi() self.midi = Midi()
@ -53,10 +52,10 @@ class Remote(abc.ABC):
self.init_thread() self.init_thread()
return self return self
@property @abstractmethod
@abc.abstractmethod def __str__(self):
def steps(self): """Ensure subclasses override str magic method"""
"""Steps required to build the interface for this Voicemeeter kind""" pass
def init_thread(self): def init_thread(self):
"""Starts updates thread.""" """Starts updates thread."""
@ -77,7 +76,7 @@ class Remote(abc.ABC):
@timeout @timeout
def login(self) -> None: def login(self) -> None:
"""Login to the API, initialize dirty parameters""" """Login to the API, initialize dirty parameters"""
self.gui.launched = self._bindings.login(ok=(0, 1)) == 0 self.gui.launched = self.call(self.bind_login, ok=(0, 1)) == 0
if not self.gui.launched: if not self.gui.launched:
self.logger.info( self.logger.info(
'Voicemeeter engine running but GUI not launched. Launching the GUI now.' 'Voicemeeter engine running but GUI not launched. Launching the GUI now.'
@ -90,20 +89,20 @@ class Remote(abc.ABC):
value = KindId[kind_id.upper()].value value = KindId[kind_id.upper()].value
if BITS == 64 and self.bits == 64: if BITS == 64 and self.bits == 64:
value += 3 value += 3
self._bindings.run_voicemeeter(value) self.call(self.bind_run_voicemeeter, value)
@property @property
def type(self) -> str: def type(self) -> str:
"""Returns the type of Voicemeeter installation (basic, banana, potato).""" """Returns the type of Voicemeeter installation (basic, banana, potato)."""
type_ = ct.c_long() type_ = ct.c_long()
self._bindings.get_voicemeeter_type(ct.byref(type_)) self.call(self.bind_get_voicemeeter_type, ct.byref(type_))
return KindId(type_.value).name.lower() return KindId(type_.value).name.lower()
@property @property
def version(self) -> str: def version(self) -> str:
"""Returns Voicemeeter's version as a string""" """Returns Voicemeeter's version as a string"""
ver = ct.c_long() ver = ct.c_long()
self._bindings.get_voicemeeter_version(ct.byref(ver)) self.call(self.bind_get_voicemeeter_version, ct.byref(ver))
return '{}.{}.{}.{}'.format( return '{}.{}.{}.{}'.format(
(ver.value & 0xFF000000) >> 24, (ver.value & 0xFF000000) >> 24,
(ver.value & 0x00FF0000) >> 16, (ver.value & 0x00FF0000) >> 16,
@ -114,13 +113,13 @@ class Remote(abc.ABC):
@property @property
def pdirty(self) -> bool: def pdirty(self) -> bool:
"""True iff UI parameters have been updated.""" """True iff UI parameters have been updated."""
return self._bindings.is_parameters_dirty(ok=(0, 1)) == 1 return self.call(self.bind_is_parameters_dirty, ok=(0, 1)) == 1
@property @property
def mdirty(self) -> bool: def mdirty(self) -> bool:
"""True iff MB parameters have been updated.""" """True iff MB parameters have been updated."""
try: try:
return self._bindings.macro_button_is_dirty(ok=(0, 1)) == 1 return self.call(self.bind_macro_button_is_dirty, ok=(0, 1)) == 1
except AttributeError as e: except AttributeError as e:
self.logger.exception(f'{type(e).__name__}: {e}') self.logger.exception(f'{type(e).__name__}: {e}')
raise CAPIError('VBVMR_MacroButton_IsDirty', -9) from e raise CAPIError('VBVMR_MacroButton_IsDirty', -9) from e
@ -150,10 +149,10 @@ class Remote(abc.ABC):
"""Gets a string or float parameter""" """Gets a string or float parameter"""
if is_string: if is_string:
buf = ct.create_unicode_buffer(512) buf = ct.create_unicode_buffer(512)
self._bindings.get_parameter_string_w(param.encode(), ct.byref(buf)) self.call(self.bind_get_parameter_string_w, param.encode(), ct.byref(buf))
else: else:
buf = ct.c_float() buf = ct.c_float()
self._bindings.get_parameter_float(param.encode(), ct.byref(buf)) self.call(self.bind_get_parameter_float, param.encode(), ct.byref(buf))
return buf.value return buf.value
def set(self, param: str, val: Union[str, float]) -> None: def set(self, param: str, val: Union[str, float]) -> None:
@ -161,11 +160,12 @@ class Remote(abc.ABC):
if isinstance(val, str): if isinstance(val, str):
if len(val) >= 512: if len(val) >= 512:
raise VMError('String is too long') raise VMError('String is too long')
self._bindings.set_parameter_string_w(param.encode(), ct.c_wchar_p(val)) self.call(
self.bind_set_parameter_string_w, param.encode(), ct.c_wchar_p(val)
)
else: else:
self._bindings.set_parameter_float( self.call(
param.encode(), self.bind_set_parameter_float, param.encode(), ct.c_float(float(val))
ct.c_float(float(val)),
) )
self.cache[param] = val self.cache[param] = val
@ -174,7 +174,8 @@ class Remote(abc.ABC):
"""Gets a macrobutton parameter""" """Gets a macrobutton parameter"""
c_state = ct.c_float() c_state = ct.c_float()
try: try:
self._bindings.macro_button_get_status( self.call(
self.bind_macro_button_get_status,
ct.c_long(id_), ct.c_long(id_),
ct.byref(c_state), ct.byref(c_state),
ct.c_long(mode), ct.c_long(mode),
@ -188,7 +189,8 @@ class Remote(abc.ABC):
"""Sets a macrobutton parameter. Caches value""" """Sets a macrobutton parameter. Caches value"""
c_state = ct.c_float(float(val)) c_state = ct.c_float(float(val))
try: try:
self._bindings.macro_button_set_status( self.call(
self.bind_macro_button_set_status,
ct.c_long(id_), ct.c_long(id_),
c_state, c_state,
ct.c_long(mode), ct.c_long(mode),
@ -202,8 +204,8 @@ class Remote(abc.ABC):
"""Retrieves number of physical devices connected""" """Retrieves number of physical devices connected"""
if direction not in ('in', 'out'): if direction not in ('in', 'out'):
raise VMError('Expected a direction: in or out') raise VMError('Expected a direction: in or out')
func = getattr(self._bindings, f'{direction}put_get_device_number') func = getattr(self, f'bind_{direction}put_get_device_number')
res = func(ok_exp=lambda r: r >= 0) res = self.call(func, ok_exp=lambda r: r >= 0)
return res return res
def get_device_description(self, index: int, direction: str = None) -> tuple: def get_device_description(self, index: int, direction: str = None) -> tuple:
@ -213,8 +215,9 @@ class Remote(abc.ABC):
type_ = ct.c_long() type_ = ct.c_long()
name = ct.create_unicode_buffer(256) name = ct.create_unicode_buffer(256)
hwid = ct.create_unicode_buffer(256) hwid = ct.create_unicode_buffer(256)
func = getattr(self._bindings, f'{direction}put_get_device_desc_w') func = getattr(self, f'bind_{direction}put_get_device_desc_w')
func( self.call(
func,
ct.c_long(index), ct.c_long(index),
ct.byref(type_), ct.byref(type_),
ct.byref(name), ct.byref(name),
@ -225,7 +228,9 @@ class Remote(abc.ABC):
def get_level(self, type_: int, index: int) -> float: def get_level(self, type_: int, index: int) -> float:
"""Retrieves a single level value""" """Retrieves a single level value"""
val = ct.c_float() val = ct.c_float()
self._bindings.get_level(ct.c_long(type_), ct.c_long(index), ct.byref(val)) self.call(
self.bind_get_level, ct.c_long(type_), ct.c_long(index), ct.byref(val)
)
return val.value return val.value
def _get_levels(self) -> Iterable: def _get_levels(self) -> Iterable:
@ -243,7 +248,8 @@ class Remote(abc.ABC):
def get_midi_message(self): def get_midi_message(self):
n = ct.c_long(1024) n = ct.c_long(1024)
buf = ct.create_string_buffer(1024) buf = ct.create_string_buffer(1024)
res = self._bindings.get_midi_message( res = self.call(
self.bind_get_midi_message,
ct.byref(buf), ct.byref(buf),
n, n,
ok=(-5, -6), # no data received from midi device ok=(-5, -6), # no data received from midi device
@ -266,7 +272,7 @@ class Remote(abc.ABC):
"""Sets many parameters from a script""" """Sets many parameters from a script"""
if len(script) > 48000: if len(script) > 48000:
raise ValueError('Script too large, max size 48kB') raise ValueError('Script too large, max size 48kB')
self._bindings.set_parameters(script.encode()) self.call(self.bind_set_parameters, script.encode())
time.sleep(self.DELAY * 5) time.sleep(self.DELAY * 5)
def apply(self, data: dict): def apply(self, data: dict):
@ -333,7 +339,7 @@ class Remote(abc.ABC):
def logout(self) -> None: def logout(self) -> None:
"""Logout of the API""" """Logout of the API"""
time.sleep(0.1) time.sleep(0.1)
self._bindings.logout() self.call(self.bind_logout)
self.logger.info(f'{type(self).__name__}: Successfully logged out of {self}') self.logger.info(f'{type(self).__name__}: Successfully logged out of {self}')
def __exit__(self, exc_type, exc_value, exc_traceback) -> None: def __exit__(self, exc_type, exc_value, exc_traceback) -> None:

View File

@ -1,5 +1,5 @@
import abc
import time import time
from abc import abstractmethod
from math import log from math import log
from typing import Union from typing import Union
@ -15,7 +15,7 @@ class Strip(IRemote):
Defines concrete implementation for strip Defines concrete implementation for strip
""" """
@abc.abstractmethod @abstractmethod
def __str__(self): def __str__(self):
pass pass

View File

@ -1,4 +1,4 @@
import abc from abc import abstractmethod
from . import kinds from . import kinds
from .iremote import IRemote from .iremote import IRemote
@ -11,7 +11,7 @@ class VbanStream(IRemote):
Defines concrete implementation for vban stream Defines concrete implementation for vban stream
""" """
@abc.abstractmethod @abstractmethod
def __str__(self): def __str__(self):
pass pass