9 Commits

Author SHA1 Message Date
3a70a4c578 upd docs 2024-02-15 18:35:34 +00:00
8b1b2c7f79 log value given as well as value expected 2024-02-15 16:58:18 +00:00
1e5e458105 log OOB as warnings
patch bump

closes #8
2024-02-15 15:19:05 +00:00
e05460e998 implement module level loggers
class loggers are now child loggers

minor version bump

closes #7
2024-02-15 15:15:30 +00:00
d27824d1cf should an incorrect kind be passed to entry point, raise XAirRemoteError
remove the print statement

patch bump
2024-02-15 13:05:54 +00:00
764195a452 remove unused opts dict in geq_prop 2024-02-14 22:47:12 +00:00
b295fee6e1 lint fixes
fix {DCA}.name setter

removed unused imports

patch bump
2024-02-14 22:06:28 +00:00
06be2f2831 fix date 2024-02-14 21:39:58 +00:00
2d0c0f91f0 upd CHANGELOG
bump to 2.2.4
2024-02-14 21:38:56 +00:00
14 changed files with 141 additions and 96 deletions

View File

@@ -9,8 +9,28 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
## [Unreleased]
- [x] Send class mixed into Strip, AuxRtn, FxRtn. May now be accessed with {Class}.send
- [x] Sends example added
- [ ]
## [2.3.1] - 2024-02-15
### Changed
- Module level loggers implemented
- class loggers are now child loggers
- Passing an incorrect kind_id to the entry point now raises an XAirRemoteError.
- Passing a value out of bounds to a setter now logs a warning instead of raising an exception.
- Send class added to README.
## [2.2.4] - 2024-02-14
### Added
- Send class mixed into Strip, AuxRtn, FxRtn. May now be accessed with {Class}.send
- Sends example added
### Changed
- delay kwarg now applies to getters. See [Issue #6](https://github.com/onyx-and-iris/xair-api-python/issues/6).
## [2.2.0] - 2022-11-08

View File

@@ -121,7 +121,7 @@ Contains the subclasses:
### `Strip`
Contains the subclasses:
(`Config`, `Preamp`, `Gate`, `Dyn`, `Insert`, `GEQ`, `EQ`, `Mix`, `Group`, `Automix`)
(`Config`, `Preamp`, `Gate`, `Dyn`, `Insert`, `GEQ`, `EQ`, `Mix`, `Group`, `Automix`, `Send`)
### `Bus`
@@ -136,12 +136,12 @@ Contains the subclasses:
### `FXRtn`
Contains the subclasses:
(`Config`, `Preamp`, `EQ`, `Mix`, `Group`)
(`Config`, `Preamp`, `EQ`, `Mix`, `Group`, `Send`)
### `AuxRtn`
Contains the subclasses:
(`Config`, `Preamp`, `EQ`, `Mix`, `Group`)
(`Config`, `Preamp`, `EQ`, `Mix`, `Group`, `Send`)
### `Subclasses`
@@ -288,6 +288,12 @@ tuple containing a class for each mute group
for example: `config.mute_group[0].on = True`
### `Send`
- `level`: float, -inf to 10.0
for example: `mixer.strip[10].send[3].level = -16.5`
### XAirRemote class (lower level)
Send an OSC command directly to the mixer

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "xair-api"
version = "2.2.4a0"
version = "2.3.1"
description = "Remote control Behringer X-Air | Midas MR mixers through OSC"
authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT"

View File

@@ -25,13 +25,13 @@ class FxRtn(IFxRtn):
class MainStereo(ILR):
@property
def address(self) -> str:
return f"/main/st"
return "/main/st"
class MainMono(ILR):
@property
def address(self) -> str:
return f"/main/m"
return "/main/m"
class Matrix(ILR):

View File

@@ -1,8 +1,10 @@
import abc
import logging
from .errors import XAirRemoteError
from .meta import mute_prop
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
from .shared import EQ, GEQ, Config, Dyn, Group, Insert, Mix
logger = logging.getLogger(__name__)
class IBus(abc.ABC):
@@ -11,6 +13,7 @@ class IBus(abc.ABC):
def __init__(self, remote, index: int):
self._remote = remote
self.index = index + 1
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str):
return self._remote.query(f"{self.address}/{param}")

View File

@@ -1,15 +1,18 @@
import abc
import logging
from . import kinds, util
from .errors import XAirRemoteError
from .meta import bool_prop
logger = logging.getLogger(__name__)
class IConfig(abc.ABC):
"""Abstract Base Class for config"""
def __init__(self, remote):
self._remote = remote
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str):
return self._remote.query(f"{self.address}/{param}")
@@ -33,8 +36,8 @@ class Config(IConfig):
Returns a Config class of a kind.
"""
LINKS_cls = _make_links_mixins[remote.kind.id_]
MUTEGROUP_cls = type(f"MuteGroup", (Config.MuteGroup, cls), {})
MONITOR_cls = type(f"ConfigMonitor", (Config.Monitor, cls), {})
MUTEGROUP_cls = type("MuteGroup", (Config.MuteGroup, cls), {})
MONITOR_cls = type("ConfigMonitor", (Config.Monitor, cls), {})
CONFIG_cls = type(
f"Config{remote.kind}",
(cls, LINKS_cls),
@@ -47,7 +50,7 @@ class Config(IConfig):
@property
def address(self) -> str:
return f"/config"
return "/config"
@property
def amixenable(self) -> bool:
@@ -105,7 +108,7 @@ class Config(IConfig):
@source.setter
def source(self, val: int):
self.setter(f"source", val)
self.setter("source", val)
@property
def sourcetrim(self) -> float:
@@ -114,7 +117,9 @@ class Config(IConfig):
@sourcetrim.setter
def sourcetrim(self, val: float):
if not -18 <= val <= 18:
raise XAirRemoteError("expected value in range -18.0 to 18.0")
self.logger.warning(
f"sourcetrim got {val}, expected value in range -18.0 to 18.0"
)
self.setter("sourcetrim", util.lin_set(-18, 18, val))
@property
@@ -140,7 +145,9 @@ class Config(IConfig):
@dimgain.setter
def dimgain(self, val: int):
if not -40 <= val <= 0:
raise XAirRemoteError("expected value in range -40 to 0")
self.logger.warning(
f"dimgain got {val}, expected value in range -40 to 0"
)
self.setter("dimatt", util.lin_set(-40, 0, val))
@property

View File

@@ -1,6 +1,7 @@
import abc
import logging
from .errors import XAirRemoteError
logger = logging.getLogger(__name__)
class IDCA(abc.ABC):
@@ -9,6 +10,7 @@ class IDCA(abc.ABC):
def __init__(self, remote, index: int):
self._remote = remote
self.index = index + 1
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str) -> tuple:
return self._remote.query(f"{self.address}/{param}")
@@ -50,7 +52,7 @@ class DCA(IDCA):
@name.setter
def name(self, val: str):
self.setter("config/name")[0]
self.setter("config/name", val)
@property
def color(self) -> int:

View File

@@ -1,8 +1,10 @@
import abc
import logging
from .errors import XAirRemoteError
from .meta import mute_prop
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
from .shared import Config, Group, Mix
logger = logging.getLogger(__name__)
class IFX(abc.ABC):
@@ -11,6 +13,7 @@ class IFX(abc.ABC):
def __init__(self, remote, index: int):
self._remote = remote
self.index = index + 1
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str):
return self._remote.query(f"{self.address}/{param}")

View File

@@ -1,9 +1,11 @@
import abc
import logging
from typing import Optional
from .errors import XAirRemoteError
from .meta import mute_prop
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
from .shared import EQ, GEQ, Config, Dyn, Insert, Mix
logger = logging.getLogger(__name__)
class ILR(abc.ABC):
@@ -13,6 +15,7 @@ class ILR(abc.ABC):
self._remote = remote
if index is not None:
self.index = index + 1
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str):
return self._remote.query(f"{self.address}/{param}")
@@ -61,4 +64,4 @@ class LR(ILR):
@property
def address(self) -> str:
return f"/lr"
return "/lr"

View File

@@ -1,4 +1,3 @@
from .errors import XAirRemoteError
from .util import lin_get, lin_set
@@ -51,13 +50,6 @@ def float_prop(param):
def geq_prop(param):
# fmt: off
opts = {
"1k": 1000, "1k25": 1250, "1k6": 1600, "2k": 2000, "3k15": 3150, "4k": 4000,
"5k": 5000, "6k3": 6300, "8k": 8000, "10k": 10000, "12k5": 12500, "16k": 16000,
"20k": 20000,
}
# fmt: on
param = param.replace("_", ".")
def fget(self) -> float:
@@ -65,7 +57,9 @@ def geq_prop(param):
def fset(self, val):
if not -15 <= val <= 15:
raise XAirRemoteError("expected value in range -15.0 to 15.0")
self.logger.warning(
f"slider_{param} got {val}, expected value in range -15.0 to 15.0"
)
self.setter(param, lin_set(-15, 15, val))
return property(fget, fset)

View File

@@ -1,21 +1,11 @@
import abc
import logging
from typing import Optional
from .errors import XAirRemoteError
from .meta import mute_prop
from .shared import (
EQ,
GEQ,
Automix,
Config,
Dyn,
Gate,
Group,
Insert,
Mix,
Preamp,
Send,
)
from .shared import EQ, Config, Group, Mix, Preamp, Send
logger = logging.getLogger(__name__)
class IRtn(abc.ABC):
@@ -25,6 +15,7 @@ class IRtn(abc.ABC):
self._remote = remote
if index is not None:
self.index = index + 1
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str):
return self._remote.query(f"{self.address}/{param}")

View File

@@ -1,7 +1,6 @@
from typing import Optional, Union
from . import util
from .errors import XAirRemoteError
from .meta import geq_prop
"""
@@ -61,7 +60,9 @@ class Preamp:
@usbtrim.setter
def usbtrim(self, val: float):
if not -18 <= val <= 18:
raise XAirRemoteError("expected value in range -18.0 to 18.0")
self.logger.warning(
f"usbtrim got {val}, expected value in range -18.0 to 18.0"
)
self.setter("rtntrim", util.lin_set(-18, 18, val))
@property
@@ -95,7 +96,9 @@ class Preamp:
@highpassfilter.setter
def highpassfilter(self, val: int):
if not 20 <= val <= 400:
raise XAirRemoteError("expected value in range 20 to 400")
self.logger.warning(
f"highpassfilter got {val}, expected value in range 20 to 400"
)
self.setter("hpf", util.log_set(20, 400, val))
@@ -122,7 +125,7 @@ class Gate:
def mode(self, val: str):
opts = ("gate", "exp2", "exp3", "exp4", "duck")
if val not in opts:
raise XAirRemoteError(f"expected one of {opts}")
self.logger.warning(f"mode got {val}, expected one of {opts}")
self.setter("mode", opts.index(val))
@property
@@ -132,7 +135,9 @@ class Gate:
@threshold.setter
def threshold(self, val: float):
if not -80 <= val <= 0:
raise XAirRemoteError("expected value in range -80.0 to 0.0")
self.logger.warning(
f"threshold got {val}, expected value in range -80.0 to 0.0"
)
self.setter("thr", util.lin_set(-80, 0, val))
@property
@@ -142,7 +147,7 @@ class Gate:
@range.setter
def range(self, val: int):
if not 3 <= val <= 60:
raise XAirRemoteError("expected value in range 3 to 60")
self.logger.warning(f"range got {val}, expected value in range 3 to 60")
self.setter("range", util.lin_set(3, 60, val))
@property
@@ -152,7 +157,7 @@ class Gate:
@attack.setter
def attack(self, val: int):
if not 0 <= val <= 120:
raise XAirRemoteError("expected value in range 0 to 120")
self.logger.warning(f"attack got {val}, expected value in range 0 to 120")
self.setter("attack", util.lin_set(0, 120, val))
@property
@@ -163,7 +168,9 @@ class Gate:
@hold.setter
def hold(self, val: float):
if not 0.02 <= val <= 2000:
raise XAirRemoteError("expected value in range 0.02 to 2000.0")
self.logger.warning(
f"hold got {val}, expected value in range 0.02 to 2000.0"
)
self.setter("hold", util.log_set(0.02, 2000, val))
@property
@@ -173,7 +180,7 @@ class Gate:
@release.setter
def release(self, val: int):
if not 5 <= val <= 4000:
raise XAirRemoteError("expected value in range 5 to 4000")
self.logger.warning(f"release got {val}, expected value in range 5 to 4000")
self.setter("release", util.log_set(5, 4000, val))
@property
@@ -208,7 +215,9 @@ class Gate:
@filterfreq.setter
def filterfreq(self, val: Union[float, int]):
if not 20 <= val <= 20000:
raise XAirRemoteError("expected value in range 20 to 20000")
self.logger.warning(
f"filterfreq got {val}, expected value in range 20 to 20000"
)
self.setter("filter/f", util.log_set(20, 20000, val))
@@ -235,7 +244,7 @@ class Dyn:
def mode(self, val: str):
opts = ("comp", "exp")
if val not in opts:
raise XAirRemoteError(f"expected one of {opts}")
self.logger.warning(f"mode got {val}, expected one of {opts}")
self.setter("mode", opts.index(val))
@property
@@ -247,7 +256,7 @@ class Dyn:
def det(self, val: str):
opts = ("peak", "rms")
if val not in opts:
raise XAirRemoteError(f"expected one of {opts}")
self.logger.warning(f"det got {val}, expected one of {opts}")
self.setter("det", opts.index(val))
@property
@@ -259,7 +268,7 @@ class Dyn:
def env(self, val: str):
opts = ("lin", "log")
if val not in opts:
raise XAirRemoteError(f"expected one of {opts}")
self.logger.warning(f"env got {val}, expected one of {opts}")
self.setter("env", opts.index(val))
@property
@@ -269,7 +278,9 @@ class Dyn:
@threshold.setter
def threshold(self, val: float):
if not -60 <= val <= 0:
raise XAirRemoteError("expected value in range -60.0 to 0")
self.logger.warning(
f"threshold got {val}, expected value in range -60.0 to 0"
)
self.setter("thr", util.lin_set(-60, 0, val))
@property
@@ -288,7 +299,7 @@ class Dyn:
@knee.setter
def knee(self, val: int):
if not 0 <= val <= 5:
raise XAirRemoteError("expected value in range 0 to 5")
self.logger.warning(f"knee got {val}, expected value in range 0 to 5")
self.setter("knee", util.lin_set(0, 5, val))
@property
@@ -298,7 +309,7 @@ class Dyn:
@mgain.setter
def mgain(self, val: float):
if not 0 <= val <= 24:
raise XAirRemoteError("expected value in range 0.0 to 24.0")
self.logger.warning(f"mgain got {val}, expected value in range 0.0 to 24.0")
self.setter("mgain", util.lin_set(0, 24, val))
@property
@@ -308,7 +319,7 @@ class Dyn:
@attack.setter
def attack(self, val: int):
if not 0 <= val <= 120:
raise XAirRemoteError("expected value in range 0 to 120")
self.logger.warning(f"attack got {val}, expected value in range 0 to 120")
self.setter("attack", util.lin_set(0, 120, val))
@property
@@ -319,7 +330,9 @@ class Dyn:
@hold.setter
def hold(self, val: float):
if not 0.02 <= val <= 2000:
raise XAirRemoteError("expected value in range 0.02 to 2000.0")
self.logger.warning(
f"hold got {val}, expected value in range 0.02 to 2000.0"
)
self.setter("hold", util.log_set(0.02, 2000, val))
@property
@@ -329,7 +342,7 @@ class Dyn:
@release.setter
def release(self, val: int):
if not 5 <= val <= 4000:
raise XAirRemoteError("expected value in range 5 to 4000")
self.logger.warning(f"release got {val}, expected value in range 5 to 4000")
self.setter("release", util.log_set(5, 4000, val))
@property
@@ -339,7 +352,7 @@ class Dyn:
@mix.setter
def mix(self, val: int):
if not 0 <= val <= 100:
raise XAirRemoteError("expected value in range 0 to 100")
self.logger.warning(f"mix got {val}, expected value in range 0 to 100")
self.setter("mix", util.lin_set(0, 100, val))
@property
@@ -382,7 +395,9 @@ class Dyn:
@filterfreq.setter
def filterfreq(self, val: Union[float, int]):
if not 20 <= val <= 20000:
raise XAirRemoteError("expected value in range 20 to 20000")
self.logger.warning(
f"filterfreq got {val}, expected value in range 20 to 20000"
)
self.setter("filter/f", util.log_set(20, 20000, val))
@@ -462,7 +477,7 @@ class EQ:
def mode(self, val: str):
opts = ("peq", "geq", "teq")
if val not in opts:
raise XAirRemoteError(f"expected one of {opts}")
self.logger.warning(f"mode got {val}, expected one of {opts}")
self.setter("mode", opts.index(val))
class EQBand:
@@ -481,7 +496,7 @@ class EQ:
@type.setter
def type(self, val: int):
self.setter(f"type", val)
self.setter("type", val)
@property
def frequency(self) -> float:
@@ -491,7 +506,9 @@ class EQ:
@frequency.setter
def frequency(self, val: float):
if not 20 <= val <= 20000:
raise XAirRemoteError("expected value in range 20.0 to 20000.0")
self.logger.warning(
f"frequency got {val}, expected value in range 20.0 to 20000.0"
)
self.setter("f", util.log_set(20, 20000, val))
@property
@@ -501,7 +518,9 @@ class EQ:
@gain.setter
def gain(self, val: float):
if not -15 <= val <= 15:
raise XAirRemoteError("expected value in range -15.0 to 15.0")
self.logger.warning(
f"gain got {val}, expected value in range -15.0 to 15.0"
)
self.setter("g", util.lin_set(-15, 15, val))
@property
@@ -512,7 +531,9 @@ class EQ:
@quality.setter
def quality(self, val: float):
if not 0.3 <= val <= 10:
raise XAirRemoteError("expected value in range 0.3 to 10.0")
self.logger.warning(
f"quality got {val}, expected value in range 0.3 to 10.0"
)
self.setter("q", util.log_set(0.3, 10, val))
@@ -528,7 +549,7 @@ class GEQ:
f"slider_{param}": geq_prop(param)
for param in [
"20", "25", "31_5", "40", "50", "63", "80", "100", "125",
"160", "200", "250", "315" "400", "500", "630", "800", "1k",
"160", "200", "250", "315", "400", "500", "630", "800", "1k",
"1k25", "1k6", "2k", "2k5", "3k15", "4k", "5k", "6k3", "8k",
"10k", "12k5", "16k", "20k",
]
@@ -620,7 +641,9 @@ class Automix:
@weight.setter
def weight(self, val: float):
if not -12 <= val <= 12:
raise XAirRemoteError("expected value in range -12.0 to 12.0")
self.logger.warning(
f"weight got {val}, expected value in range -12.0 to 12.0"
)
self.setter("weight", util.lin_set(-12, 12, val))
@@ -641,10 +664,10 @@ class Send:
@property
@util.db_from
def level(self):
def level(self) -> float:
return self.getter("level")[0]
@level.setter
@util.db_to
def level(self, val):
def level(self, val: float):
self.setter("level", val)

View File

@@ -1,20 +1,10 @@
import abc
import logging
from .errors import XAirRemoteError
from .meta import mute_prop
from .shared import (
EQ,
GEQ,
Automix,
Config,
Dyn,
Gate,
Group,
Insert,
Mix,
Preamp,
Send,
)
from .shared import EQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp, Send
logger = logging.getLogger(__name__)
class IStrip(abc.ABC):
@@ -23,6 +13,7 @@ class IStrip(abc.ABC):
def __init__(self, remote, index: int):
self._remote = remote
self.index = index + 1
self.logger = logger.getChild(self.__class__.__name__)
def getter(self, param: str) -> tuple:
return self._remote.query(f"{self.address}/{param}")

View File

@@ -25,6 +25,8 @@ from .lr import LR
from .rtn import AuxRtn, FxRtn
from .strip import Strip
logger = logging.getLogger(__name__)
class OSCClientServer(BlockingOSCUDPServer):
def __init__(self, address: str, dispatcher: Dispatcher):
@@ -45,8 +47,6 @@ class OSCClientServer(BlockingOSCUDPServer):
class XAirRemote(abc.ABC):
"""Handles the communication with the mixer via the OSC protocol"""
logger = logging.getLogger("xair.xairremote")
_CONNECT_TIMEOUT = 0.5
_info_response = []
@@ -57,6 +57,7 @@ class XAirRemote(abc.ABC):
self.xair_ip = kwargs["ip"] or self._ip_from_toml()
self.xair_port = kwargs["port"]
self._delay = kwargs["delay"]
self.logger = logger.getChild(self.__class__.__name__)
if not self.xair_ip:
raise XAirRemoteError("No valid ip detected")
self.server = OSCClientServer((self.xair_ip, self.xair_port), dispatcher)
@@ -80,7 +81,7 @@ class XAirRemote(abc.ABC):
raise XAirRemoteError(
"Failed to setup OSC connection to mixer. Please check for correct ip address."
)
print(
self.logger.info(
f"Successfully connected to {self.info_response[2]} at {self.info_response[0]}."
)
@@ -174,9 +175,10 @@ def request_remote_obj(kind_id: str, *args, **kwargs) -> XAirRemote:
Returns a reference to an XAirRemote class of a kind
"""
XAIRREMOTE_cls = None
try:
XAIRREMOTE_cls = _remotes[kind_id]
except ValueError as e:
raise SystemExit(e)
except KeyError as e:
raise XAirRemoteError(f"Unknown mixer kind '{kind_id}'") from e
return XAIRREMOTE_cls(*args, **kwargs)