12 Commits

Author SHA1 Message Date
5640f54e65 rethrow if not mdirty error code -9, immediately.
patch bump
2023-07-10 20:17:06 +01:00
4569e8c760 accept incoming change 2023-07-10 17:45:38 +01:00
5e39461966 2.2.0 section added to changelog
mino version bump
2023-07-10 16:20:59 +01:00
6de78a4037 check for error code -9 in clear_dirty()
re-raise error if not AttributeError
otherwise clear pdirty only

add -5,-6 response to ok in get_midi_message().
2023-07-10 16:20:13 +01:00
bafaa58507 extends error class
now accepts a custom message

fn_name and error code stored as class attributes
2023-07-10 15:36:38 +01:00
af368b4b0a patch bump 2023-07-10 15:18:11 +01:00
32527e37bd patch bump 2023-07-09 01:45:27 +01:00
c21b04e1a8 add version number to login logger.info string 2023-07-09 01:44:44 +01:00
76960f36d0 if a wrong user config is requested,
this error should be exposed to the consumer.

patch bump.
2023-07-08 07:57:39 +01:00
2849b37670 remove redundant if test 2023-07-04 19:52:55 +01:00
7732a26c40 issue where subprocess not inheriting virtual env
see SO python-subprocess-doesnt-inherit-virtual-environment
2023-07-04 19:52:24 +01:00
c1e23ab250 typo 2023-07-01 20:26:44 +01:00
9 changed files with 78 additions and 34 deletions

View File

@@ -11,6 +11,20 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
- [x]
## [2.2.0] - 2023-07-10
### Added
- CAPIError class now stores fn_name, error code and message as class attributes.
### Changed
- macrobutton capi calls now use error code -9 on AttributeError (using an old version of the API).
### Fixed
- call to `self.vm_get_midi_message` now wrapped by {CBindings}.call.
## [2.1.1] - 2023-07-01
### Added

View File

@@ -405,7 +405,7 @@ The following methods are available
The following properties are available
- `A1 - A5`: boolean
- `B1 - A3`: boolean
- `B1 - B3`: boolean
- `samplerate`: int, (22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000)
- `bitresolution`: int, (8, 16, 24, 32)
- `channel`: int, from 1 to 8

View File

@@ -63,8 +63,6 @@ class Parser:
def interactive_mode(parser):
while cmd := input("Please enter command (Press <Enter> to exit)\n"):
if not cmd:
break
if res := parser.parse((cmd,)):
print(res)

View File

@@ -18,6 +18,7 @@ class App:
def __enter__(self):
self.vm.init_thread()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.vm.end_thread()

View File

@@ -1,15 +1,13 @@
[tool.poetry]
name = "voicemeeter-api"
version = "2.1.1"
version = "2.2.1"
description = "A Python wrapper for the Voiceemeter API"
authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/onyx-and-iris/voicemeeter-api-python"
packages = [
{ include = "voicemeeterlib" },
]
packages = [{ include = "voicemeeterlib" }]
[tool.poetry.dependencies]
python = "^3.10"

View File

@@ -1,40 +1,41 @@
import subprocess
import sys
from pathlib import Path
def ex_dsl():
path = Path.cwd() / "examples" / "dsl" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "dsl" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_events():
path = Path.cwd() / "examples" / "events" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "events" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_gui():
path = Path.cwd() / "examples" / "gui" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "gui" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_levels():
path = Path.cwd() / "examples" / "levels" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "levels" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_midi():
path = Path.cwd() / "examples" / "midi" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "midi" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_obs():
path = Path.cwd() / "examples" / "obs" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "obs" / "."
subprocess.run([sys.executable, str(scriptpath)])
def ex_observer():
path = Path.cwd() / "examples" / "observer" / "."
subprocess.run(["py", str(path)])
scriptpath = Path.cwd() / "examples" / "observer" / "."
subprocess.run([sys.executable, str(scriptpath)])
def test():

View File

@@ -16,7 +16,7 @@ class CBindings(metaclass=ABCMeta):
Maps expected ctype argument and res types for each binding.
"""
logger_cbindings = logger.getChild("Cbindings")
logger_cbindings = logger.getChild("CBindings")
vm_login = libc.VBVMR_Login
vm_login.restype = LONG
@@ -116,10 +116,10 @@ class CBindings(metaclass=ABCMeta):
res = func(*args)
if ok_exp is None:
if res not in ok:
raise CAPIError(f"{func.__name__} returned {res}")
elif not ok_exp(res):
raise CAPIError(f"{func.__name__} returned {res}")
raise CAPIError(func.__name__, res)
elif not ok_exp(res) and res not in ok:
raise CAPIError(func.__name__, res)
return res
except CAPIError as e:
self.logger_cbindings.exception(f"{type(e).__name__}: {e}")
self.logger_cbindings.exception(str(e))
raise

View File

@@ -5,6 +5,15 @@ class InstallError(Exception):
class CAPIError(Exception):
"""Exception raised when the C-API returns error values"""
def __init__(self, fn_name, code, msg=None):
self.fn_name = fn_name
self.code = code
self.message = msg if msg else f"{fn_name} returned {code}"
super().__init__(self.message)
def __str__(self):
return f"{type(self).__name__}: {self.message}"
class VMError(Exception):
"""Exception raised when general errors occur"""

View File

@@ -70,7 +70,9 @@ class Remote(CBindings):
"Voicemeeter engine running but GUI not launched. Launching the GUI now."
)
self.run_voicemeeter(self.kind.name)
self.logger.info(f"{type(self).__name__}: Successfully logged into {self}")
self.logger.info(
f"{type(self).__name__}: Successfully logged into {self} version {self.version}"
)
self.clear_dirty()
def run_voicemeeter(self, kind_id: str) -> NoReturn:
@@ -114,8 +116,12 @@ class Remote(CBindings):
return self.call(self.vm_mdirty, ok=(0, 1)) == 1
except AttributeError as e:
self.logger.exception(f"{type(e).__name__}: {e}")
ERR_MSG = (
"no bind for VBVMR_MacroButton_IsDirty.",
"are you using an old version of the API?",
)
raise CAPIError(
"no bind for VBVMR_MacroButton_IsDirty. are you using an old version of the API?"
"VBVMR_MacroButton_IsDirty", -9, msg=" ".join(ERR_MSG)
) from e
@property
@@ -131,8 +137,10 @@ class Remote(CBindings):
try:
while self.pdirty or self.mdirty:
pass
except CAPIError:
self.logger.error("no bind for mdirty, clearing pdirty only")
except CAPIError as e:
if not (e.fn_name == "VBVMR_MacroButton_IsDirty" and e.code == -9):
raise
self.logger.error(f"{e} clearing pdirty only.")
while self.pdirty:
pass
@@ -172,8 +180,12 @@ class Remote(CBindings):
)
except AttributeError as e:
self.logger.exception(f"{type(e).__name__}: {e}")
ERR_MSG = (
"no bind for VBVMR_MacroButton_GetStatus.",
"are you using an old version of the API?",
)
raise CAPIError(
"no bind for VBVMR_MacroButton_GetStatus. are you using an old version of the API?"
"VBVMR_MacroButton_GetStatus", -9, msg=" ".join(ERR_MSG)
) from e
return int(state.value)
@@ -184,8 +196,12 @@ class Remote(CBindings):
self.call(self.vm_set_buttonstatus, ct.c_long(id), c_state, ct.c_long(mode))
except AttributeError as e:
self.logger.exception(f"{type(e).__name__}: {e}")
ERR_MSG = (
"no bind for VBVMR_MacroButton_SetStatus.",
"are you using an old version of the API?",
)
raise CAPIError(
"no bind for VBVMR_MacroButton_SetStatus. are you using an old version of the API?"
"VBVMR_MacroButton_SetStatus", -9, msg=" ".join(ERR_MSG)
) from e
self.cache[f"mb_{id}_{mode}"] = int(c_state.value)
@@ -238,7 +254,13 @@ class Remote(CBindings):
def get_midi_message(self):
n = ct.c_long(1024)
buf = ct.create_string_buffer(1024)
res = self.vm_get_midi_message(ct.byref(buf), n, ok_exp=lambda r: r >= 0)
res = self.call(
self.vm_get_midi_message,
ct.byref(buf),
n,
ok=(-5, -6), # no data received from midi device
ok_exp=lambda r: r >= 0,
)
if res > 0:
vals = tuple(
grouper(3, (int.from_bytes(buf[i], "little") for i in range(res)))
@@ -286,8 +308,9 @@ class Remote(CBindings):
try:
self.apply(self.configs[name])
self.logger.info(f"Profile '{name}' applied!")
except KeyError:
except KeyError as e:
self.logger.error(("\n").join(error_msg))
raise VMError(("\n").join(error_msg)) from e
def logout(self) -> NoReturn:
"""Wait for dirty parameters to clear, then logout of the API"""