mirror of
https://github.com/onyx-and-iris/xair-api-python.git
synced 2026-04-18 05:23:32 +00:00
package renamed to xair-api
now packaged with poetry and added to pypi using tomllib, requires python 3.11 readme, changelog updated to reflect changes major version bump
This commit is contained in:
3
xair_api/__init__.py
Normal file
3
xair_api/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .xair import request_remote_obj as connect
|
||||
|
||||
_ALL__ = ["connect"]
|
||||
62
xair_api/bus.py
Normal file
62
xair_api/bus.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import abc
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IBus(abc.ABC):
|
||||
"""Abstract Base Class for buses"""
|
||||
|
||||
def __init__(self, remote, index: int):
|
||||
self._remote = remote
|
||||
self.index = index + 1
|
||||
|
||||
def getter(self, param: str):
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class Bus(IBus):
|
||||
"""Concrete class for buses"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote, index):
|
||||
"""
|
||||
Factory function for buses
|
||||
|
||||
Creates a mixin of shared subclasses, sets them as class attributes.
|
||||
|
||||
Returns a Bus class of a kind.
|
||||
"""
|
||||
BUS_cls = type(
|
||||
f"Bus{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (
|
||||
Config,
|
||||
Dyn,
|
||||
Insert,
|
||||
GEQ.make(),
|
||||
EQ.make_sixband(cls, remote, index),
|
||||
Mix,
|
||||
Group,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
return BUS_cls(remote, index)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/bus/{self.index}"
|
||||
211
xair_api/config.py
Normal file
211
xair_api/config.py
Normal file
@@ -0,0 +1,211 @@
|
||||
import abc
|
||||
|
||||
from . import kinds
|
||||
from .errors import XAirRemoteError
|
||||
from .meta import bool_prop
|
||||
from .util import _get_level_val, _set_level_val, lin_get, lin_set
|
||||
|
||||
|
||||
class IConfig(abc.ABC):
|
||||
"""Abstract Base Class for config"""
|
||||
|
||||
def __init__(self, remote):
|
||||
self._remote = remote
|
||||
|
||||
def getter(self, param: str):
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class Config(IConfig):
|
||||
"""Concrete class for config"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote):
|
||||
"""
|
||||
Factory function for Config
|
||||
|
||||
Returns a Config class of a kind.
|
||||
"""
|
||||
LINKS_cls = _make_links_mixins[remote.kind.id_]
|
||||
MONITOR_cls = type(f"ConfigMonitor", (Config.Monitor, cls), {})
|
||||
CONFIG_cls = type(
|
||||
f"Config{remote.kind}",
|
||||
(cls, LINKS_cls),
|
||||
{"monitor": MONITOR_cls(remote)},
|
||||
)
|
||||
return CONFIG_cls(remote)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/config"
|
||||
|
||||
@property
|
||||
def amixenable(self) -> bool:
|
||||
return self.getter("mute")[0] == 1
|
||||
|
||||
@amixenable.setter
|
||||
def amixenable(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("amixenable is a bool parameter")
|
||||
self.setter("amixenable", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def amixlock(self) -> bool:
|
||||
return self.getter("amixlock")[0] == 1
|
||||
|
||||
@amixlock.setter
|
||||
def amixlock(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("amixlock is a bool parameter")
|
||||
self.setter("amixlock", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mute_group(self) -> bool:
|
||||
return self.getter("mute")[0] == 1
|
||||
|
||||
@mute_group.setter
|
||||
def mute_group(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("mute_group is a bool parameter")
|
||||
self.setter("mute", 1 if val else 0)
|
||||
|
||||
class Monitor:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Config.Monitor, self).address
|
||||
return f"{root}/solo"
|
||||
|
||||
@property
|
||||
def level(self) -> float:
|
||||
retval = self.getter("level")[0]
|
||||
return _get_level_val(retval)
|
||||
|
||||
@level.setter
|
||||
def level(self, val: float):
|
||||
_set_level_val(self, val)
|
||||
|
||||
@property
|
||||
def source(self) -> int:
|
||||
return int(self.getter("source")[0])
|
||||
|
||||
@source.setter
|
||||
def source(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("source is an int parameter")
|
||||
self.setter(f"source", val)
|
||||
|
||||
@property
|
||||
def sourcetrim(self) -> float:
|
||||
return round(lin_get(-18, 18, self.getter("sourcetrim")[0]), 1)
|
||||
|
||||
@sourcetrim.setter
|
||||
def sourcetrim(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise XAirRemoteError(
|
||||
"sourcetrim is a float parameter, expected value in range -18 to 18"
|
||||
)
|
||||
self.setter("sourcetrim", lin_set(-18, 18, val))
|
||||
|
||||
@property
|
||||
def chmode(self) -> bool:
|
||||
return self.getter("chmode")[0] == 1
|
||||
|
||||
@chmode.setter
|
||||
def chmode(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("chmode is a bool parameter")
|
||||
self.setter("chmode", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def busmode(self) -> bool:
|
||||
return self.getter("busmode")[0] == 1
|
||||
|
||||
@busmode.setter
|
||||
def busmode(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("busmode is a bool parameter")
|
||||
self.setter("busmode", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def dimgain(self) -> int:
|
||||
return int(lin_get(-40, 0, self.getter("dimatt")[0]))
|
||||
|
||||
@dimgain.setter
|
||||
def dimgain(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"dimgain is an int parameter, expected value in range -40 to 0"
|
||||
)
|
||||
self.setter("dimatt", lin_set(-40, 0, val))
|
||||
|
||||
@property
|
||||
def dim(self) -> bool:
|
||||
return self.getter("dim")[0] == 1
|
||||
|
||||
@dim.setter
|
||||
def dim(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("dim is a bool parameter")
|
||||
self.setter("dim", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mono(self) -> bool:
|
||||
return self.getter("mono")[0] == 1
|
||||
|
||||
@mono.setter
|
||||
def mono(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("mono is a bool parameter")
|
||||
self.setter("mono", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mute(self) -> bool:
|
||||
return self.getter("mute")[0] == 1
|
||||
|
||||
@mute.setter
|
||||
def mute(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("mute is a bool parameter")
|
||||
self.setter("mute", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def dimfpl(self) -> bool:
|
||||
return self.getter("dimfpl")[0] == 1
|
||||
|
||||
@dimfpl.setter
|
||||
def dimfpl(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("dimfpl is a bool parameter")
|
||||
self.setter("dimfpl", 1 if val else 0)
|
||||
|
||||
|
||||
def _make_links_mixin(kind):
|
||||
"""Creates a links mixin"""
|
||||
return type(
|
||||
f"Links{kind}",
|
||||
(),
|
||||
{
|
||||
"link_eq": bool_prop("linkcfg/eq"),
|
||||
"link_dyn": bool_prop("linkcfg/dyn"),
|
||||
"link_fader_mute": bool_prop("linkcfg/fdrmute"),
|
||||
**{
|
||||
f"chlink{i}_{i+1}": bool_prop(f"chlink/{i}-{i+1}")
|
||||
for i in range(1, kind.num_strip, 2)
|
||||
},
|
||||
**{
|
||||
f"buslink{i}_{i+1}": bool_prop(f"buslink/{i}-{i+1}")
|
||||
for i in range(1, kind.num_bus, 2)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
_make_links_mixins = {kind.id_: _make_links_mixin(kind) for kind in kinds.all}
|
||||
60
xair_api/dca.py
Normal file
60
xair_api/dca.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import abc
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
|
||||
|
||||
class IDCA(abc.ABC):
|
||||
"""Abstract Base Class for DCA groups"""
|
||||
|
||||
def __init__(self, remote, index: int):
|
||||
self._remote = remote
|
||||
self.index = index + 1
|
||||
|
||||
def getter(self, param: str) -> tuple:
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class DCA(IDCA):
|
||||
"""Concrete class for DCA groups"""
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/dca/{self.index}"
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter("on")[0] == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.getter("config/name")[0]
|
||||
|
||||
@name.setter
|
||||
def name(self, val: str):
|
||||
if not isinstance(val, str):
|
||||
raise XAirRemoteError("name is a str parameter")
|
||||
self.setter("config/name")[0]
|
||||
|
||||
@property
|
||||
def color(self) -> int:
|
||||
return self.getter("config/color")[0]
|
||||
|
||||
@color.setter
|
||||
def color(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("color is an int parameter")
|
||||
self.setter("config/color", val)
|
||||
4
xair_api/errors.py
Normal file
4
xair_api/errors.py
Normal file
@@ -0,0 +1,4 @@
|
||||
class XAirRemoteError(Exception):
|
||||
"""Base error class for XAIR Remote."""
|
||||
|
||||
pass
|
||||
72
xair_api/fx.py
Normal file
72
xair_api/fx.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import abc
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IFX(abc.ABC):
|
||||
"""Abstract Base Class for fxs"""
|
||||
|
||||
def __init__(self, remote, index: int):
|
||||
self._remote = remote
|
||||
self.index = index + 1
|
||||
|
||||
def getter(self, param: str):
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class FXSend(IFX):
|
||||
"""Concrete class for fxsend"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote, index):
|
||||
"""
|
||||
Factory function for FXSend
|
||||
|
||||
Creates a mixin of shared subclasses, sets them as class attributes.
|
||||
|
||||
Returns an FXSend class of a kind.
|
||||
"""
|
||||
FXSEND_cls = type(
|
||||
f"FXSend{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (Config, Mix, Group)
|
||||
}
|
||||
},
|
||||
)
|
||||
return FXSEND_cls(remote, index)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/fxsend/{self.index}"
|
||||
|
||||
|
||||
class FXReturn(IFX):
|
||||
"""Concrete class for fxreturn"""
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/fx/{self.index}"
|
||||
|
||||
@property
|
||||
def type(self) -> int:
|
||||
return self.getter("type")[0]
|
||||
|
||||
@type.setter
|
||||
def type(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("type is an integer parameter")
|
||||
self.setter("type", val)
|
||||
66
xair_api/kinds.py
Normal file
66
xair_api/kinds.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
"""
|
||||
# osc slightly different, interface would need adjusting to support this mixer.
|
||||
|
||||
@dataclass
|
||||
class X32KindMap:
|
||||
id_: str = "X32"
|
||||
num_dca: int = 8
|
||||
num_strip: int = 32
|
||||
num_bus: int = 16
|
||||
num_fx: int = 8
|
||||
num_rtn: int = 6
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class KindMap:
|
||||
def __str__(self) -> str:
|
||||
return self.id_.capitalize()
|
||||
|
||||
|
||||
@dataclass
|
||||
class MR18KindMap(KindMap):
|
||||
# note ch 17-18 defined as aux rtn
|
||||
id_: str
|
||||
num_dca: int = 4
|
||||
num_strip: int = 16
|
||||
num_bus: int = 6
|
||||
num_fx: int = 4
|
||||
num_rtn: int = 4
|
||||
|
||||
|
||||
@dataclass
|
||||
class XR16KindMap(KindMap):
|
||||
id_: str
|
||||
num_dca: int = 4
|
||||
num_strip: int = 16
|
||||
num_bus: int = 4
|
||||
num_fx: int = 4
|
||||
num_rtn: int = 4
|
||||
|
||||
|
||||
@dataclass
|
||||
class XR12KindMap(KindMap):
|
||||
id_: str
|
||||
num_dca: int = 4
|
||||
num_strip: int = 12
|
||||
num_bus: int = 2
|
||||
num_fx: int = 4
|
||||
num_rtn: int = 4
|
||||
|
||||
|
||||
_kinds = {
|
||||
"XR18": MR18KindMap(id_="XR18"),
|
||||
"MR18": MR18KindMap(id_="MR18"),
|
||||
"XR16": XR16KindMap(id_="XR16"),
|
||||
"XR12": XR12KindMap(id_="XR12"),
|
||||
}
|
||||
|
||||
|
||||
def get(kind_id):
|
||||
return _kinds[kind_id]
|
||||
|
||||
|
||||
all = list(kind for kind in _kinds.values())
|
||||
60
xair_api/lr.py
Normal file
60
xair_api/lr.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import abc
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class ILR(abc.ABC):
|
||||
"""Abstract Base Class for buses"""
|
||||
|
||||
def __init__(self, remote):
|
||||
self._remote = remote
|
||||
|
||||
def getter(self, param: str):
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class LR(ILR):
|
||||
"""Concrete class for buses"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote):
|
||||
"""
|
||||
Factory function for LR
|
||||
|
||||
Creates a mixin of shared subclasses, sets them as class attributes.
|
||||
|
||||
Returns an LR class of a kind.
|
||||
"""
|
||||
LR_cls = type(
|
||||
f"LR{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote)
|
||||
for _cls in (
|
||||
Config,
|
||||
Dyn,
|
||||
Insert,
|
||||
GEQ.make(),
|
||||
EQ.make_sixband(cls, remote),
|
||||
Mix,
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
return LR_cls(remote)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/lr"
|
||||
77
xair_api/meta.py
Normal file
77
xair_api/meta.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from .errors import XAirRemoteError
|
||||
from .util import lin_get, lin_set
|
||||
|
||||
|
||||
def bool_prop(param):
|
||||
"""A boolean property object."""
|
||||
|
||||
def fget(self):
|
||||
return self.getter(param)[0] == 1
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError(f"{param} is a boolean parameter")
|
||||
self.setter(param, 1 if val else 0)
|
||||
|
||||
return property(fget, fset)
|
||||
|
||||
|
||||
def string_prop(param):
|
||||
"""A string property object"""
|
||||
|
||||
def fget(self):
|
||||
return self.getter(param)[0]
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, str):
|
||||
raise XAirRemoteError(f"{param} is a string parameter")
|
||||
self.setter(param, val)
|
||||
|
||||
return property(fget, fset)
|
||||
|
||||
|
||||
def int_prop(param):
|
||||
"""An integer property object"""
|
||||
|
||||
def fget(self):
|
||||
return int(self.getter(param)[0])
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(f"{param} is an integer parameter")
|
||||
self.setter(param, val)
|
||||
|
||||
return property(fget, fset)
|
||||
|
||||
|
||||
def float_prop(param):
|
||||
"""A float property object"""
|
||||
|
||||
def fget(self):
|
||||
return round(self.getter(param)[0], 1)
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(f"{param} is a float parameter")
|
||||
self.setter(param, val)
|
||||
|
||||
return property(fget, fset)
|
||||
|
||||
|
||||
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:
|
||||
return round(lin_get(-15, 15, self.getter(param)[0]), 1)
|
||||
|
||||
def fset(self, val):
|
||||
self.setter(param, lin_set(-15, 15, val))
|
||||
|
||||
return property(fget, fset)
|
||||
99
xair_api/rtn.py
Normal file
99
xair_api/rtn.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import abc
|
||||
from typing import Optional
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IRtn(abc.ABC):
|
||||
"""Abstract Base Class for aux"""
|
||||
|
||||
def __init__(self, remote, index: Optional[int] = None):
|
||||
self._remote = remote
|
||||
if index is not None:
|
||||
self.index = index + 1
|
||||
|
||||
def getter(self, param: str):
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class Aux(IRtn):
|
||||
"""Concrete class for aux"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote):
|
||||
"""
|
||||
Factory function for aux
|
||||
|
||||
Creates a mixin of shared subclasses, sets them as class attributes.
|
||||
|
||||
Returns an Aux class of a kind.
|
||||
"""
|
||||
AUX_cls = type(
|
||||
f"Aux{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote)
|
||||
for _cls in (
|
||||
Config,
|
||||
Preamp,
|
||||
EQ.make_fourband(cls, remote),
|
||||
Mix,
|
||||
Group,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
return AUX_cls(remote)
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
return "/rtn/aux"
|
||||
|
||||
|
||||
class Rtn(IRtn):
|
||||
"""Concrete class for rtn"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote, index):
|
||||
"""
|
||||
Factory function for rtn
|
||||
|
||||
Creates a mixin of shared subclasses, sets them as class attributes.
|
||||
|
||||
Returns an Rtn class of a kind.
|
||||
"""
|
||||
RTN_cls = type(
|
||||
f"Rtn{remote.kind.id_}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind.id_}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (
|
||||
Config,
|
||||
Preamp,
|
||||
EQ.make_fourband(cls, remote, index),
|
||||
Mix,
|
||||
Group,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
return RTN_cls(remote, index)
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
return f"/rtn/{self.index}"
|
||||
680
xair_api/shared.py
Normal file
680
xair_api/shared.py
Normal file
@@ -0,0 +1,680 @@
|
||||
from typing import Union
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .meta import geq_prop
|
||||
from .util import _get_fader_val, _set_fader_val, lin_get, lin_set, log_get, log_set
|
||||
|
||||
"""
|
||||
Classes shared by /ch, /rtn, /rt/aux, /bus, /fxsend, /lr
|
||||
"""
|
||||
|
||||
|
||||
class Config:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Config, self).address
|
||||
return f"{root}/config"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self.getter("name")[0]
|
||||
|
||||
@name.setter
|
||||
def name(self, val: str):
|
||||
if not isinstance(val, str):
|
||||
raise XAirRemoteError("name is a string parameter")
|
||||
self.setter("name", val)
|
||||
|
||||
@property
|
||||
def color(self) -> int:
|
||||
return self.getter("color")[0]
|
||||
|
||||
@color.setter
|
||||
def color(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("color is an int parameter")
|
||||
self.setter("color", val)
|
||||
|
||||
@property
|
||||
def inputsource(self) -> int:
|
||||
return self.getter("insrc")[0]
|
||||
|
||||
@inputsource.setter
|
||||
def inputsource(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("inputsource is an int parameter")
|
||||
self.setter("insrc", val)
|
||||
|
||||
@property
|
||||
def usbreturn(self) -> int:
|
||||
return self.getter("rtnsrc")[0]
|
||||
|
||||
@usbreturn.setter
|
||||
def usbreturn(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("usbreturn is an int parameter")
|
||||
self.setter("rtnsrc", val)
|
||||
|
||||
|
||||
class Preamp:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Preamp, self).address
|
||||
return f"{root}/preamp"
|
||||
|
||||
@property
|
||||
def usbtrim(self) -> float:
|
||||
return round(lin_get(-18, 18, self.getter("rtntrim")[0]), 1)
|
||||
|
||||
@usbtrim.setter
|
||||
def usbtrim(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise XAirRemoteError(
|
||||
"usbtrim is a float parameter, expected value in range -18 to 18"
|
||||
)
|
||||
self.setter("rtntrim", lin_set(-18, 18, val))
|
||||
|
||||
@property
|
||||
def usbinput(self) -> bool:
|
||||
return self.getter("rtnsw")[0] == 1
|
||||
|
||||
@usbinput.setter
|
||||
def usbinput(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("rtnsw is a bool parameter")
|
||||
self.setter("rtnsw", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def invert(self) -> bool:
|
||||
return self.getter("invert")[0] == 1
|
||||
|
||||
@invert.setter
|
||||
def invert(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("invert is a bool parameter")
|
||||
self.setter("invert", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def highpasson(self) -> bool:
|
||||
return self.getter("hpon")[0] == 1
|
||||
|
||||
@highpasson.setter
|
||||
def highpasson(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("hpon is a bool parameter")
|
||||
self.setter("hpon", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def highpassfilter(self) -> int:
|
||||
return int(log_get(20, 400, self.getter("hpf")[0]))
|
||||
|
||||
@highpassfilter.setter
|
||||
def highpassfilter(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("highpassfilter is an int parameter")
|
||||
self.setter("hpf", log_set(20, 400, val))
|
||||
|
||||
|
||||
class Gate:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Gate, self).address
|
||||
return f"{root}/gate"
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter("on")[0] == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mode(self) -> str:
|
||||
opts = ("gate", "exp2", "exp3", "exp4", "duck")
|
||||
return opts[self.getter("mode")[0]]
|
||||
|
||||
@mode.setter
|
||||
def mode(self, val: str):
|
||||
opts = ("gate", "exp2", "exp3", "exp4", "duck")
|
||||
if not isinstance(val, str) and val not in opts:
|
||||
raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
|
||||
self.setter("mode", opts.index(val))
|
||||
|
||||
@property
|
||||
def threshold(self) -> float:
|
||||
return round(lin_get(-80, 0, self.getter("thr")[0]), 1)
|
||||
|
||||
@threshold.setter
|
||||
def threshold(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise XAirRemoteError(
|
||||
"threshold is a float parameter, expected value in range -80 to 0"
|
||||
)
|
||||
self.setter("thr", lin_set(-80, 0, val))
|
||||
|
||||
@property
|
||||
def range(self) -> int:
|
||||
return int(lin_get(3, 60, self.getter("range")[0]))
|
||||
|
||||
@range.setter
|
||||
def range(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"range is an int parameter, expected value in range 3 to 60"
|
||||
)
|
||||
self.setter("range", lin_set(3, 60, val))
|
||||
|
||||
@property
|
||||
def attack(self) -> int:
|
||||
return int(lin_get(0, 120, self.getter("attack")[0]))
|
||||
|
||||
@attack.setter
|
||||
def attack(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"attack is an int parameter, expected value in range 0 to 120"
|
||||
)
|
||||
self.setter("attack", lin_set(0, 120, val))
|
||||
|
||||
@property
|
||||
def hold(self) -> Union[float, int]:
|
||||
val = log_get(0.02, 2000, self.getter("hold")[0])
|
||||
return round(val, 1) if val < 100 else int(val)
|
||||
|
||||
@hold.setter
|
||||
def hold(self, val: float):
|
||||
self.setter("hold", log_set(0.02, 2000, val))
|
||||
|
||||
@property
|
||||
def release(self) -> int:
|
||||
return int(log_get(5, 4000, self.getter("release")[0]))
|
||||
|
||||
@release.setter
|
||||
def release(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"release is an int parameter, expected value in range 5 to 4000"
|
||||
)
|
||||
self.setter("release", log_set(5, 4000, val))
|
||||
|
||||
@property
|
||||
def keysource(self):
|
||||
return self.getter("keysrc")[0]
|
||||
|
||||
@keysource.setter
|
||||
def keysource(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("keysource is an int parameter")
|
||||
self.setter("keysrc", val)
|
||||
|
||||
@property
|
||||
def filteron(self):
|
||||
return self.getter("filter/on")[0] == 1
|
||||
|
||||
@filteron.setter
|
||||
def filteron(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("filteron is a boolean parameter")
|
||||
self.setter("filter/on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def filtertype(self) -> int:
|
||||
return int(self.getter("filter/type")[0])
|
||||
|
||||
@filtertype.setter
|
||||
def filtertype(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("filtertype is an int parameter")
|
||||
self.setter("filter/type", val)
|
||||
|
||||
@property
|
||||
def filterfreq(self) -> Union[float, int]:
|
||||
retval = log_get(20, 20000, self.getter("filter/f")[0])
|
||||
return int(retval) if retval > 1000 else round(retval, 1)
|
||||
|
||||
@filterfreq.setter
|
||||
def filterfreq(self, val: Union[float, int]):
|
||||
self.setter("filter/f", log_set(20, 20000, val))
|
||||
|
||||
|
||||
class Dyn:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Dyn, self).address
|
||||
return f"{root}/dyn"
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter("on")[0] == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mode(self) -> str:
|
||||
opts = ("comp", "exp")
|
||||
return opts[self.getter("mode")[0]]
|
||||
|
||||
@mode.setter
|
||||
def mode(self, val: str):
|
||||
opts = ("comp", "exp")
|
||||
if not isinstance(val, str) and val not in opts:
|
||||
raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
|
||||
self.setter("mode", opts.index(val))
|
||||
|
||||
@property
|
||||
def det(self) -> str:
|
||||
opts = ("peak", "rms")
|
||||
return opts[self.getter("det")[0]]
|
||||
|
||||
@det.setter
|
||||
def det(self, val: str):
|
||||
opts = ("peak", "rms")
|
||||
if not isinstance(val, str) and val not in opts:
|
||||
raise XAirRemoteError(f"det is a string parameter, expected one of {opts}")
|
||||
self.setter("det", opts.index(val))
|
||||
|
||||
@property
|
||||
def env(self) -> str:
|
||||
opts = ("lin", "log")
|
||||
return opts[self.getter("env")[0]]
|
||||
|
||||
@env.setter
|
||||
def env(self, val: str):
|
||||
opts = ("lin", "log")
|
||||
if not isinstance(val, str) and val not in opts:
|
||||
raise XAirRemoteError(f"env is a string parameter, expected one of {opts}")
|
||||
self.setter("env", opts.index(val))
|
||||
|
||||
@property
|
||||
def threshold(self) -> float:
|
||||
return round(lin_get(-60, 0, self.getter("thr")[0]), 1)
|
||||
|
||||
@threshold.setter
|
||||
def threshold(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise XAirRemoteError(
|
||||
"threshold is a float parameter, expected value in range -80 to 0"
|
||||
)
|
||||
self.setter("thr", lin_set(-60, 0, val))
|
||||
|
||||
@property
|
||||
def ratio(self) -> Union[float, int]:
|
||||
opts = (1.1, 1.3, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0, 7.0, 10, 20, 100)
|
||||
return opts[self.getter("ratio")[0]]
|
||||
|
||||
@ratio.setter
|
||||
def ratio(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("ratio is an int parameter")
|
||||
self.setter("ratio", val)
|
||||
|
||||
@property
|
||||
def knee(self) -> int:
|
||||
return int(lin_get(0, 5, self.getter("knee")[0]))
|
||||
|
||||
@knee.setter
|
||||
def knee(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"knee is an int parameter, expected value in range 0 to 5"
|
||||
)
|
||||
self.setter("knee", lin_set(0, 5, val))
|
||||
|
||||
@property
|
||||
def mgain(self) -> float:
|
||||
return round(lin_get(0, 24, self.getter("mgain")[0]), 1)
|
||||
|
||||
@mgain.setter
|
||||
def mgain(self, val: float):
|
||||
self.setter("mgain", lin_set(0, 24, val))
|
||||
|
||||
@property
|
||||
def attack(self) -> int:
|
||||
return int(lin_get(0, 120, self.getter("attack")[0]))
|
||||
|
||||
@attack.setter
|
||||
def attack(self, val: int):
|
||||
self.setter("attack", lin_set(0, 120, val))
|
||||
|
||||
@property
|
||||
def hold(self) -> Union[float, int]:
|
||||
val = log_get(0.02, 2000, self.getter("hold")[0])
|
||||
return round(val, 1) if val < 100 else int(val)
|
||||
|
||||
@hold.setter
|
||||
def hold(self, val: float):
|
||||
self.setter("hold", log_set(0.02, 2000, val))
|
||||
|
||||
@property
|
||||
def release(self) -> int:
|
||||
return int(log_get(5, 4000, self.getter("release")[0]))
|
||||
|
||||
@release.setter
|
||||
def release(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"release is an int parameter, expected value in range 5 to 4000"
|
||||
)
|
||||
self.setter("release", log_set(5, 4000, val))
|
||||
|
||||
@property
|
||||
def mix(self) -> int:
|
||||
return int(lin_get(0, 100, self.getter("mix")[0]))
|
||||
|
||||
@mix.setter
|
||||
def mix(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError(
|
||||
"mix is an int parameter, expected value in range 0 to 5"
|
||||
)
|
||||
self.setter("mix", lin_set(0, 100, val))
|
||||
|
||||
@property
|
||||
def keysource(self):
|
||||
return self.getter("keysrc")[0]
|
||||
|
||||
@keysource.setter
|
||||
def keysource(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("keysource is an int parameter")
|
||||
self.setter("keysrc", val)
|
||||
|
||||
@property
|
||||
def auto(self) -> bool:
|
||||
return self.getter("auto")[0] == 1
|
||||
|
||||
@auto.setter
|
||||
def auto(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("auto is a boolean parameter")
|
||||
self.setter("auto", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def filteron(self):
|
||||
return self.getter("filter/on")[0] == 1
|
||||
|
||||
@filteron.setter
|
||||
def filteron(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("filteron is a boolean parameter")
|
||||
self.setter("filter/on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def filtertype(self) -> int:
|
||||
return int(self.getter("filter/type")[0])
|
||||
|
||||
@filtertype.setter
|
||||
def filtertype(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("filtertype is an int parameter")
|
||||
self.setter("filter/type", val)
|
||||
|
||||
@property
|
||||
def filterfreq(self) -> Union[float, int]:
|
||||
retval = log_get(20, 20000, self.getter("filter/f")[0])
|
||||
return int(retval) if retval > 1000 else round(retval, 1)
|
||||
|
||||
@filterfreq.setter
|
||||
def filterfreq(self, val: Union[float, int]):
|
||||
self.setter("filter/f", log_set(20, 20000, val))
|
||||
|
||||
|
||||
class Insert:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Insert, self).address
|
||||
return f"{root}/insert"
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter("on")[0] == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def sel(self) -> int:
|
||||
return self.getter("sel")[0]
|
||||
|
||||
@sel.setter
|
||||
def sel(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("sel is an int parameter")
|
||||
self.setter("sel", val)
|
||||
|
||||
|
||||
class EQ:
|
||||
@classmethod
|
||||
def make_fourband(cls, _cls, remote, index=None):
|
||||
EQBand_cls = type("EQBand", (EQ.EQBand, _cls), {})
|
||||
return type(
|
||||
"EQ",
|
||||
(cls,),
|
||||
{
|
||||
"low": EQBand_cls(1, remote, index),
|
||||
"lomid": EQBand_cls(2, remote, index),
|
||||
"himid": EQBand_cls(3, remote, index),
|
||||
"high": EQBand_cls(4, remote, index),
|
||||
},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def make_sixband(cls, _cls, remote, index=None):
|
||||
EQBand_cls = type("EQBand", (EQ.EQBand, _cls), {})
|
||||
return type(
|
||||
"EQ",
|
||||
(cls,),
|
||||
{
|
||||
"low": EQBand_cls(1, remote, index),
|
||||
"low2": EQBand_cls(2, remote, index),
|
||||
"lomid": EQBand_cls(3, remote, index),
|
||||
"himid": EQBand_cls(4, remote, index),
|
||||
"high2": EQBand_cls(5, remote, index),
|
||||
"high": EQBand_cls(6, remote, index),
|
||||
},
|
||||
)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(EQ, self).address
|
||||
return f"{root}/eq"
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter("on")[0] == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def mode(self) -> str:
|
||||
opts = ("peq", "geq", "teq")
|
||||
return opts[self.getter("mode")[0]]
|
||||
|
||||
@mode.setter
|
||||
def mode(self, val: str):
|
||||
opts = ("peq", "geq", "teq")
|
||||
if not isinstance(val, str) and val not in opts:
|
||||
raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
|
||||
self.setter("mode", opts.index(val))
|
||||
|
||||
class EQBand:
|
||||
def __init__(self, i, remote, index):
|
||||
if index is None:
|
||||
super(EQ.EQBand, self).__init__(remote)
|
||||
else:
|
||||
super(EQ.EQBand, self).__init__(remote, index)
|
||||
self.i = i
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(EQ.EQBand, self).address
|
||||
return f"{root}/eq/{self.i}"
|
||||
|
||||
@property
|
||||
def type(self) -> int:
|
||||
return int(self.getter("type")[0])
|
||||
|
||||
@type.setter
|
||||
def type(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("type is an int parameter")
|
||||
self.setter(f"type", val)
|
||||
|
||||
@property
|
||||
def frequency(self) -> float:
|
||||
retval = log_get(20, 20000, self.getter("f")[0])
|
||||
return round(retval, 1)
|
||||
|
||||
@frequency.setter
|
||||
def frequency(self, val: float):
|
||||
self.setter("f", log_set(20, 20000, val))
|
||||
|
||||
@property
|
||||
def gain(self) -> float:
|
||||
return round(lin_get(-15, 15, self.getter("g")[0]), 1)
|
||||
|
||||
@gain.setter
|
||||
def gain(self, val: float):
|
||||
self.setter("g", lin_set(-15, 15, val))
|
||||
|
||||
@property
|
||||
def quality(self) -> float:
|
||||
retval = log_get(0.3, 10, self.getter("q")[0])
|
||||
return round(retval, 1)
|
||||
|
||||
@quality.setter
|
||||
def quality(self, val: float):
|
||||
self.setter("q", log_set(0.3, 10, val))
|
||||
|
||||
|
||||
class GEQ:
|
||||
@classmethod
|
||||
def make(cls):
|
||||
# fmt: off
|
||||
return type(
|
||||
"GEQ",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
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",
|
||||
"1k25", "1k6", "2k", "2k5", "3k15", "4k", "5k", "6k3", "8k",
|
||||
"10k", "12k5", "16k", "20k",
|
||||
]
|
||||
}
|
||||
},
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(GEQ, self).address
|
||||
return f"{root}/geq"
|
||||
|
||||
|
||||
class Mix:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Mix, self).address
|
||||
return f"{root}/mix"
|
||||
|
||||
@property
|
||||
def on(self) -> bool:
|
||||
return self.getter("on")[0] == 1
|
||||
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
def fader(self) -> float:
|
||||
retval = self.getter("fader")[0]
|
||||
return _get_fader_val(retval)
|
||||
|
||||
@fader.setter
|
||||
def fader(self, val: float):
|
||||
_set_fader_val(self, val)
|
||||
|
||||
@property
|
||||
def lr(self) -> bool:
|
||||
return self.getter("lr")[0] == 1
|
||||
|
||||
@lr.setter
|
||||
def lr(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise XAirRemoteError("lr is a boolean parameter")
|
||||
self.setter("lr", 1 if val else 0)
|
||||
|
||||
|
||||
class Group:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Group, self).address
|
||||
return f"{root}/grp"
|
||||
|
||||
@property
|
||||
def dca(self) -> int:
|
||||
return self.getter("dca")[0]
|
||||
|
||||
@dca.setter
|
||||
def dca(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("dca is an int parameter")
|
||||
self.setter("dca", val)
|
||||
|
||||
@property
|
||||
def mute(self) -> int:
|
||||
return self.getter("mute")[0]
|
||||
|
||||
@mute.setter
|
||||
def mute(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("mute is an int parameter")
|
||||
self.setter("mute", val)
|
||||
|
||||
|
||||
class Automix:
|
||||
@property
|
||||
def address(self) -> str:
|
||||
root = super(Automix, self).address
|
||||
return f"{root}/automix"
|
||||
|
||||
@property
|
||||
def group(self) -> int:
|
||||
return self.getter("group")[0]
|
||||
|
||||
@group.setter
|
||||
def group(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise XAirRemoteError("group is an int parameter")
|
||||
self.setter("group", val)
|
||||
|
||||
@property
|
||||
def weight(self) -> float:
|
||||
return round(lin_get(-12, 12, self.getter("weight")[0]), 1)
|
||||
|
||||
@weight.setter
|
||||
def weight(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise XAirRemoteError(
|
||||
"weight is a float parameter, expected value in range -12 to 12"
|
||||
)
|
||||
self.setter("weight", lin_set(-12, 12, val))
|
||||
64
xair_api/strip.py
Normal file
64
xair_api/strip.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import abc
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IStrip(abc.ABC):
|
||||
"""Abstract Base Class for strips"""
|
||||
|
||||
def __init__(self, remote, index: int):
|
||||
self._remote = remote
|
||||
self.index = index + 1
|
||||
|
||||
def getter(self, param: str) -> tuple:
|
||||
self._remote.send(f"{self.address}/{param}")
|
||||
return self._remote.info_response
|
||||
|
||||
def setter(self, param: str, val: int):
|
||||
self._remote.send(f"{self.address}/{param}", val)
|
||||
|
||||
@abc.abstractmethod
|
||||
def address(self):
|
||||
pass
|
||||
|
||||
|
||||
class Strip(IStrip):
|
||||
"""Concrete class for strips"""
|
||||
|
||||
@classmethod
|
||||
def make(cls, remote, index):
|
||||
"""
|
||||
Factory function for strips
|
||||
|
||||
Creates a mixin of shared subclasses, sets them as class attributes.
|
||||
|
||||
Returns a Strip class of a kind.
|
||||
"""
|
||||
STRIP_cls = type(
|
||||
f"Strip{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (
|
||||
Config,
|
||||
Preamp,
|
||||
Gate,
|
||||
Dyn,
|
||||
Insert,
|
||||
EQ.make_fourband(cls, remote, index),
|
||||
Mix,
|
||||
Group,
|
||||
Automix,
|
||||
)
|
||||
},
|
||||
},
|
||||
)
|
||||
return STRIP_cls(remote, index)
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return f"/ch/{str(self.index).zfill(2)}"
|
||||
77
xair_api/util.py
Normal file
77
xair_api/util.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from math import exp, log
|
||||
|
||||
|
||||
def lin_get(min, max, val):
|
||||
return min + (max - min) * val
|
||||
|
||||
|
||||
def lin_set(min, max, val):
|
||||
return (val - min) / (max - min)
|
||||
|
||||
|
||||
def log_get(min, max, val):
|
||||
return min * exp(log(max / min) * val)
|
||||
|
||||
|
||||
def log_set(min, max, val):
|
||||
return log(val / min) / log(max / min)
|
||||
|
||||
|
||||
def _get_fader_val(retval):
|
||||
if retval >= 1:
|
||||
return 10
|
||||
elif retval >= 0.5:
|
||||
return round((40 * retval) - 30, 1)
|
||||
elif retval >= 0.25:
|
||||
return round((80 * retval) - 50, 1)
|
||||
elif retval >= 0.0625:
|
||||
return round((160 * retval) - 70, 1)
|
||||
elif retval >= 0:
|
||||
return round((480 * retval) - 90, 1)
|
||||
else:
|
||||
return -90
|
||||
|
||||
|
||||
def _set_fader_val(self, val):
|
||||
if val >= 10:
|
||||
self.setter("fader", 1)
|
||||
elif val >= -10:
|
||||
self.setter("fader", (val + 30) / 40)
|
||||
elif val >= -30:
|
||||
self.setter("fader", (val + 50) / 80)
|
||||
elif val >= -60:
|
||||
self.setter("fader", (val + 70) / 160)
|
||||
elif val >= -90:
|
||||
self.setter("fader", (val + 90) / 480)
|
||||
else:
|
||||
self.setter("fader", 0)
|
||||
|
||||
|
||||
def _get_level_val(retval):
|
||||
if retval >= 1:
|
||||
return 10
|
||||
elif retval >= 0.5:
|
||||
return round((40 * retval) - 30, 1)
|
||||
elif retval >= 0.25:
|
||||
return round((80 * retval) - 50, 1)
|
||||
elif retval >= 0.0625:
|
||||
return round((160 * retval) - 70, 1)
|
||||
elif retval >= 0:
|
||||
return round((480 * retval) - 90, 1)
|
||||
else:
|
||||
return -90
|
||||
|
||||
|
||||
def _set_level_val(self, val):
|
||||
if val >= 10:
|
||||
self.setter("level", 1)
|
||||
elif val >= -10:
|
||||
self.setter("level", (val + 30) / 40)
|
||||
elif val >= -30:
|
||||
self.setter("level", (val + 50) / 80)
|
||||
elif val >= -60:
|
||||
self.setter("level", (val + 70) / 160)
|
||||
elif val >= -90:
|
||||
self.setter("level", (val + 90) / 480)
|
||||
else:
|
||||
self.setter("level", 0)
|
||||
150
xair_api/xair.py
Normal file
150
xair_api/xair.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import abc
|
||||
import threading
|
||||
import time
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from typing import Optional, Self
|
||||
|
||||
import tomllib
|
||||
from pythonosc.dispatcher import Dispatcher
|
||||
from pythonosc.osc_message_builder import OscMessageBuilder
|
||||
from pythonosc.osc_server import BlockingOSCUDPServer
|
||||
|
||||
from . import kinds
|
||||
from .bus import Bus
|
||||
from .config import Config
|
||||
from .dca import DCA
|
||||
from .errors import XAirRemoteError
|
||||
from .fx import FXReturn, FXSend
|
||||
from .kinds import KindMap
|
||||
from .lr import LR
|
||||
from .rtn import Aux, Rtn
|
||||
from .strip import Strip
|
||||
|
||||
|
||||
class OSCClientServer(BlockingOSCUDPServer):
|
||||
def __init__(self, address: str, dispatcher: Dispatcher):
|
||||
super().__init__(("", 0), dispatcher)
|
||||
self.xr_address = address
|
||||
|
||||
def send_message(self, address: str, value: str):
|
||||
builder = OscMessageBuilder(address=address)
|
||||
if value is None:
|
||||
values = list()
|
||||
elif isinstance(value, list):
|
||||
values = value
|
||||
else:
|
||||
values = [value]
|
||||
for val in values:
|
||||
builder.add_arg(val)
|
||||
msg = builder.build()
|
||||
self.socket.sendto(msg.dgram, self.xr_address)
|
||||
|
||||
|
||||
class XAirRemote(abc.ABC):
|
||||
"""Handles the communication with the M-Air mixer via the OSC protocol"""
|
||||
|
||||
_CONNECT_TIMEOUT = 0.5
|
||||
_WAIT_TIME = 0.025
|
||||
_REFRESH_TIMEOUT = 5
|
||||
|
||||
XAIR_PORT = 10024
|
||||
|
||||
info_response = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
dispatcher = Dispatcher()
|
||||
dispatcher.set_default_handler(self.msg_handler)
|
||||
self.xair_ip = kwargs["ip"] or self._ip_from_toml()
|
||||
self.xair_port = kwargs["port"] or self.XAIR_PORT
|
||||
if not (self.xair_ip and self.xair_port):
|
||||
raise XAirRemoteError("No valid ip or password detected")
|
||||
self.server = OSCClientServer((self.xair_ip, self.xair_port), dispatcher)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
self.worker = threading.Thread(target=self.run_server, daemon=True)
|
||||
self.worker.start()
|
||||
self.validate_connection()
|
||||
return self
|
||||
|
||||
def _ip_from_toml(self) -> str:
|
||||
filepath = Path.cwd() / "config.toml"
|
||||
with open(filepath, "rb") as f:
|
||||
conn = tomllib.load(f)
|
||||
return conn["connection"].get("ip")
|
||||
|
||||
def validate_connection(self):
|
||||
self.send("/xinfo")
|
||||
time.sleep(self._CONNECT_TIMEOUT)
|
||||
if len(self.info_response) > 0:
|
||||
print(f"Successfully connected to {self.info_response[2]}.")
|
||||
else:
|
||||
print(
|
||||
"Error: Failed to setup OSC connection to mixer. Please check for correct ip address."
|
||||
)
|
||||
|
||||
def run_server(self):
|
||||
self.server.serve_forever()
|
||||
|
||||
def msg_handler(self, addr, *data):
|
||||
self.info_response = data[:]
|
||||
|
||||
def send(self, address: str, param: Optional[str] = None):
|
||||
self.server.send_message(address, param)
|
||||
time.sleep(self._WAIT_TIME)
|
||||
|
||||
def _query(self, address):
|
||||
self.send(address)
|
||||
time.sleep(self._WAIT_TIME)
|
||||
return self.info_response
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_tr):
|
||||
self.server.shutdown()
|
||||
|
||||
|
||||
def _make_remote(kind: KindMap) -> XAirRemote:
|
||||
"""
|
||||
Creates a new MAIR remote class.
|
||||
|
||||
The returned class will subclass MAirRemote.
|
||||
"""
|
||||
|
||||
def init(self, *args, **kwargs):
|
||||
defaultkwargs = {"ip": None, "port": None}
|
||||
kwargs = defaultkwargs | kwargs
|
||||
XAirRemote.__init__(self, *args, **kwargs)
|
||||
self.kind = kind
|
||||
self.lr = LR.make(self)
|
||||
self.strip = tuple(Strip.make(self, i) for i in range(kind.num_strip))
|
||||
self.bus = tuple(Bus.make(self, i) for i in range(kind.num_bus))
|
||||
self.dca = tuple(DCA(self, i) for i in range(kind.num_dca))
|
||||
self.fxsend = tuple(FXSend.make(self, i) for i in range(kind.num_fx))
|
||||
self.fxreturn = tuple(FXReturn(self, i) for i in range(kind.num_fx))
|
||||
self.config = Config.make(self)
|
||||
self.aux = Aux.make(self)
|
||||
self.rtn = tuple(Rtn.make(self, i) for i in range(kind.num_rtn))
|
||||
|
||||
return type(
|
||||
f"MAirRemote{kind}",
|
||||
(XAirRemote,),
|
||||
{
|
||||
"__init__": init,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
_remotes = {kind.id_: _make_remote(kind) for kind in kinds.all}
|
||||
|
||||
|
||||
def request_remote_obj(kind_id: str, *args, **kwargs) -> XAirRemote:
|
||||
"""
|
||||
Interface entry point. Wraps factory expression and handles errors
|
||||
|
||||
Returns a reference to an XAirRemote class of a kind
|
||||
"""
|
||||
MAIRREMOTE_cls = None
|
||||
try:
|
||||
MAIRREMOTE_cls = _remotes[kind_id]
|
||||
except ValueError as e:
|
||||
raise SystemExit(e)
|
||||
return MAIRREMOTE_cls(*args, **kwargs)
|
||||
Reference in New Issue
Block a user