mirror of
https://github.com/onyx-and-iris/xair-api-python.git
synced 2024-11-23 13:20:58 +00:00
initial commit
initial commit
This commit is contained in:
commit
bf9b72f31f
53
.gitignore
vendored
Normal file
53
.gitignore
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
bin/
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
|
||||||
|
# Mr Developer
|
||||||
|
.mr.developer.cfg
|
||||||
|
.project
|
||||||
|
.pydevproject
|
||||||
|
|
||||||
|
# Rope
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# config files
|
||||||
|
*.ini
|
22
LICENSE
Normal file
22
LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Peter Dikant
|
||||||
|
Copyright (c) 2022 Onyx and Iris
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
262
README.md
Normal file
262
README.md
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/xair-api-python/blob/dev/LICENSE)
|
||||||
|
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
|
||||||
|
# Mair Remote
|
||||||
|
|
||||||
|
This package offers a python interface to the Midas MR18 digital rack mixer.
|
||||||
|
All testing was done using a Midas MR18 but I've been informed that the software for XR18 is identical.
|
||||||
|
Midas are not affiliated with/nor do they support this package in any way.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- Python 3.9+
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
```
|
||||||
|
git clone https://github.com/onyx-and-iris/mair-api-python
|
||||||
|
cd mair-api-python
|
||||||
|
```
|
||||||
|
|
||||||
|
Just the interface:
|
||||||
|
```
|
||||||
|
pip install .
|
||||||
|
```
|
||||||
|
|
||||||
|
With development dependencies:
|
||||||
|
```
|
||||||
|
pip install -e .['development']
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
### Connection
|
||||||
|
An ini file named config.ini, placed into the current working directory of your code may be used to configure the mixers ip. It's contents should resemble:
|
||||||
|
```
|
||||||
|
[connection]
|
||||||
|
ip=<ip address>
|
||||||
|
```
|
||||||
|
Alternatively you may state it explicitly as an argument to mair.connect()
|
||||||
|
|
||||||
|
### Example 1
|
||||||
|
```python
|
||||||
|
import mair
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with mair.connect(kind_id, ip=ip) as mixer:
|
||||||
|
mixer.strip[8].config.name = 'sm7b'
|
||||||
|
mixer.strip[8].config.on = True
|
||||||
|
print(f'strip 09 ({mixer.strip[8].config.name}) has been set to {mixer.strip[8].config.on}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
kind_id = 'MR18'
|
||||||
|
ip = '<ip address>'
|
||||||
|
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
Currently the following devices are support:
|
||||||
|
- `XR18`
|
||||||
|
- `MR18`
|
||||||
|
|
||||||
|
However, this interface can be expanded upon to support other devices.
|
||||||
|
|
||||||
|
### MAirRemote (higher level)
|
||||||
|
`mixer.lr`
|
||||||
|
|
||||||
|
A class representing Main LR channel
|
||||||
|
|
||||||
|
`mixer.strip`
|
||||||
|
|
||||||
|
A Strip tuple containing a class for each input strip channel
|
||||||
|
|
||||||
|
`mixer.bus`
|
||||||
|
|
||||||
|
A Bus tuple containing a class for each output bus channel
|
||||||
|
|
||||||
|
`mixer.dca`
|
||||||
|
|
||||||
|
A DCA tuple containing a class for each DCA group
|
||||||
|
|
||||||
|
`mixer.fxsend`
|
||||||
|
|
||||||
|
An FXSend tuple containing a class for each FX Send channel
|
||||||
|
|
||||||
|
`mixer.fxreturn`
|
||||||
|
|
||||||
|
An FXReturn tuple containing a class for each FX Return channel
|
||||||
|
|
||||||
|
`mixer.aux`
|
||||||
|
|
||||||
|
A class representing aux channel
|
||||||
|
|
||||||
|
`mixer.rtn`
|
||||||
|
|
||||||
|
An RTN tuple containing a class for each rtn channel
|
||||||
|
|
||||||
|
`mixer.config`
|
||||||
|
|
||||||
|
A class representing the main config settings
|
||||||
|
|
||||||
|
|
||||||
|
### `LR`
|
||||||
|
Contains the subclasses:
|
||||||
|
(`Config`, `Dyn`, `Insert`, `EQ`, `Mix`)
|
||||||
|
|
||||||
|
### `Strip`
|
||||||
|
Contains the subclasses:
|
||||||
|
(`Config`, `Preamp`, `Gate`, `Dyn`, `Insert`, `GEQ`, `EQ`, `Mix`, `Group`, `Automix`)
|
||||||
|
|
||||||
|
### `Bus`
|
||||||
|
Contains the subclasses:
|
||||||
|
(`Config`, `Dyn`, `Insert`, `EQ`, `Mix`, `Group`)
|
||||||
|
|
||||||
|
### `FXSend`
|
||||||
|
Contains the subclasses:
|
||||||
|
(`Config`, `Mix`, `Group`)
|
||||||
|
|
||||||
|
### `Aux`
|
||||||
|
Contains the subclasses:
|
||||||
|
(`Config`, `Preamp`, `EQ`, `Mix`, `Group`)
|
||||||
|
|
||||||
|
### `Rtn`
|
||||||
|
Contains the subclasses:
|
||||||
|
(`Config`, `Preamp`, `EQ`, `Mix`, `Group`)
|
||||||
|
|
||||||
|
|
||||||
|
### `Subclasses`
|
||||||
|
For each subclass the corresponding properties are available.
|
||||||
|
|
||||||
|
`Config`
|
||||||
|
- `name`: string
|
||||||
|
- `color`: int, from 0, 16
|
||||||
|
- `inputsource`: int
|
||||||
|
- `usbreturn`: int
|
||||||
|
|
||||||
|
`Preamp`
|
||||||
|
- `on`: bool
|
||||||
|
- `usbtrim`: float, from -18.0 to 18.0
|
||||||
|
- `usbinput`: bool
|
||||||
|
- `invert`: bool
|
||||||
|
- `highpasson`: bool
|
||||||
|
- `highpassfilter`: int, from 20 to 400
|
||||||
|
|
||||||
|
`Gate`
|
||||||
|
- `on`: bool
|
||||||
|
- `mode`: str, one of ('gate', 'exp2', 'exp3', 'exp4', 'duck')
|
||||||
|
- `threshold`: float, from -80.0 to 0.0
|
||||||
|
- `range`: int, from 3 to 60
|
||||||
|
- `attack`: int, from 0 to 120
|
||||||
|
- `hold`: float, from 0.02 to 2000
|
||||||
|
- `release`: int, from 5 to 4000
|
||||||
|
- `keysource`, from 0 to 22
|
||||||
|
- `filteron`: bool
|
||||||
|
- `filtertype`: int, from 0 to 8
|
||||||
|
- `filterfreq`: float, from 20 to 20000
|
||||||
|
|
||||||
|
`Dyn`
|
||||||
|
- `on`: bool
|
||||||
|
- `mode`: str, one of ('comp', 'exp')
|
||||||
|
- `det`: str, one of ('peak', 'rms')
|
||||||
|
- `env`: str, one of ('lin', 'log')
|
||||||
|
- `threshold`: float, from -60.0 to 0.0
|
||||||
|
- `ratio`: int, from 0 to 11
|
||||||
|
- `knee`: int, from 0 to 5
|
||||||
|
- `mgain`: float, from 0.0 to 24.0
|
||||||
|
- `attack`: int, from 0 to 120
|
||||||
|
- `hold`: float, from 0.02 to 2000
|
||||||
|
- `release`: int, from 5 to 4000
|
||||||
|
- `mix`: int, from 0 to 100
|
||||||
|
- `keysource`: int, from 0 to 22
|
||||||
|
- `auto`: bool
|
||||||
|
- `filteron`: bool
|
||||||
|
- `filtertype`: int, from 0 to 8
|
||||||
|
- `filterfreq`: float, from 20 to 20000
|
||||||
|
|
||||||
|
`Insert`
|
||||||
|
- `on`: bool
|
||||||
|
- `sel`: int
|
||||||
|
|
||||||
|
`GEQ`
|
||||||
|
The following method names preceded by `slider_`
|
||||||
|
- `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`: float, from -15.0 to 15.0
|
||||||
|
|
||||||
|
for example: `slider_20`, `slider_6k3` etc..
|
||||||
|
|
||||||
|
`EQ`
|
||||||
|
- `on`: bool
|
||||||
|
- `mode`: str, one of ('peq', 'geq', 'teq')
|
||||||
|
|
||||||
|
For the subclasses: `low`, `low2`, `lomid`, `himid`, `high2`, `high` the following properties are available:
|
||||||
|
- `type`: int, from 0 to 5
|
||||||
|
- `frequency`: float, from 20.0 to 20000.0
|
||||||
|
- `gain`: float, -15.0 to 15.0
|
||||||
|
- `quality`: float, from 0.3 to 10.0
|
||||||
|
|
||||||
|
for example: `eq.low2.type`
|
||||||
|
|
||||||
|
`Mix`
|
||||||
|
- `on`: bool
|
||||||
|
- `fader`: float, -inf, to 10.0
|
||||||
|
- `lr`: bool
|
||||||
|
|
||||||
|
`Group`
|
||||||
|
- `dca`: int, from 0 to 15
|
||||||
|
- `mute`: int, from 0 to 15
|
||||||
|
|
||||||
|
`Automix`
|
||||||
|
- `group`: int, from 0 to 2
|
||||||
|
- `weight`: float, from -12.0 to 12.0
|
||||||
|
|
||||||
|
|
||||||
|
### `DCA`
|
||||||
|
- `on`: bool
|
||||||
|
- `name`: str
|
||||||
|
- `color`: int, from 0 to 15
|
||||||
|
|
||||||
|
|
||||||
|
### `Config`
|
||||||
|
The following method names preceded by `chlink`
|
||||||
|
- `1_2`, `3_4`, `5_6`, `7_8`, `9_10`, `11_12`, `13_14`, `15_16`
|
||||||
|
|
||||||
|
The following method names preceded by `buslink`
|
||||||
|
- `1_2`, `3_4`, `5_6`
|
||||||
|
|
||||||
|
for example: `chlink1_2`, `buslink5_6` etc..
|
||||||
|
|
||||||
|
- `link_eq`: bool
|
||||||
|
- `link_dyn`: bool
|
||||||
|
- `link_fader_mute`: bool
|
||||||
|
- `amixenable`: bool
|
||||||
|
- `amixlock`: bool
|
||||||
|
- `mute_group`: bool
|
||||||
|
|
||||||
|
For the subclass `monitor` the following properties are available
|
||||||
|
- `level`: float, -inf to 10.0
|
||||||
|
- `source`: int, from 0 to 14
|
||||||
|
- `chmode` bool
|
||||||
|
- `busmode` bool
|
||||||
|
- `dim` bool
|
||||||
|
- `mono` bool
|
||||||
|
- `mute` bool
|
||||||
|
- `dimfpl` bool
|
||||||
|
|
||||||
|
for example: `config.monitor.chmode`
|
||||||
|
|
||||||
|
|
||||||
|
### `Tests`
|
||||||
|
First make sure you installed the [development dependencies](https://github.com/onyx-and-iris/mair-api-python#installation)
|
||||||
|
|
||||||
|
To run the tests from tests directory:
|
||||||
|
|
||||||
|
WARNING: First save your settings and make sure your equipment is safe from damage.
|
||||||
|
Run tests at your own risk.
|
||||||
|
|
||||||
|
`nosetests --r test -v`.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
|
||||||
|
|
||||||
|
## Special Thanks
|
||||||
|
|
||||||
|
Peter Dikant for writing the base class
|
14
__main__.py
Normal file
14
__main__.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import mair
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with mair.connect(kind_id, ip=ip) as mixer:
|
||||||
|
mixer.strip[8].config.name = 'sm7b'
|
||||||
|
mixer.strip[8].config.on = True
|
||||||
|
print(f'strip 09 ({mixer.strip[8].config.name}) has been set to {mixer.strip[8].config.on}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
kind_id = 'MR18'
|
||||||
|
ip = '<ip address>'
|
||||||
|
|
||||||
|
main()
|
3
mair/__init__.py
Normal file
3
mair/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from .mair import connect
|
||||||
|
|
||||||
|
_ALL__ = ['connect']
|
72
mair/bus.py
Normal file
72
mair/bus.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import abc
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from .shared import (
|
||||||
|
Config,
|
||||||
|
Preamp,
|
||||||
|
Gate,
|
||||||
|
Dyn,
|
||||||
|
Insert,
|
||||||
|
EQ,
|
||||||
|
GEQ,
|
||||||
|
Mix,
|
||||||
|
Group,
|
||||||
|
Automix,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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.id_}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
_cls.__name__.lower(): type(
|
||||||
|
f"{_cls.__name__}{remote.kind.id_}", (_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}"
|
210
mair/config.py
Normal file
210
mair/config.py
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import abc
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from . import kinds
|
||||||
|
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.id_}",
|
||||||
|
(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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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.id_}",
|
||||||
|
(),
|
||||||
|
{
|
||||||
|
"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}
|
59
mair/dca.py
Normal file
59
mair/dca.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import abc
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
|
||||||
|
|
||||||
|
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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("color is an int parameter")
|
||||||
|
self.setter("config/color", val)
|
4
mair/errors.py
Normal file
4
mair/errors.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
class MAirRemoteError(Exception):
|
||||||
|
"""Base error class for MAIR Remote."""
|
||||||
|
|
||||||
|
pass
|
82
mair/fx.py
Normal file
82
mair/fx.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import abc
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from .shared import (
|
||||||
|
Config,
|
||||||
|
Preamp,
|
||||||
|
Gate,
|
||||||
|
Dyn,
|
||||||
|
Insert,
|
||||||
|
EQ,
|
||||||
|
GEQ,
|
||||||
|
Mix,
|
||||||
|
Group,
|
||||||
|
Automix,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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.id_}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
_cls.__name__.lower(): type(
|
||||||
|
f"{_cls.__name__}{remote.kind.id_}", (_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 MAirRemoteError("type is an integer parameter")
|
||||||
|
self.setter("type", val)
|
19
mair/kinds.py
Normal file
19
mair/kinds.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MR18KindMap:
|
||||||
|
id_: str = "MR18"
|
||||||
|
num_dca: int = 4
|
||||||
|
num_strip: int = 16
|
||||||
|
num_bus: int = 6
|
||||||
|
num_fx: int = 4
|
||||||
|
num_rtn: int = 4
|
||||||
|
|
||||||
|
|
||||||
|
_kinds = {
|
||||||
|
"XR18": MR18KindMap(),
|
||||||
|
"MR18": MR18KindMap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
all = list(kind for kind in _kinds.values())
|
70
mair/lr.py
Normal file
70
mair/lr.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import abc
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from .shared import (
|
||||||
|
Config,
|
||||||
|
Preamp,
|
||||||
|
Gate,
|
||||||
|
Dyn,
|
||||||
|
Insert,
|
||||||
|
EQ,
|
||||||
|
GEQ,
|
||||||
|
Mix,
|
||||||
|
Group,
|
||||||
|
Automix,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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.id_}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
_cls.__name__.lower(): type(
|
||||||
|
f"{_cls.__name__}{remote.kind.id_}", (_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"
|
138
mair/mair.py
Normal file
138
mair/mair.py
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import abc
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from pythonosc.dispatcher import Dispatcher
|
||||||
|
from pythonosc.osc_server import BlockingOSCUDPServer
|
||||||
|
from pythonosc.osc_message_builder import OscMessageBuilder
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from . import kinds
|
||||||
|
from .lr import LR
|
||||||
|
from .strip import Strip
|
||||||
|
from .bus import Bus
|
||||||
|
from .dca import DCA
|
||||||
|
from .fx import FXSend, FXReturn
|
||||||
|
from .config import Config
|
||||||
|
from .rtn import Aux, Rtn
|
||||||
|
|
||||||
|
|
||||||
|
class OSCClientServer(BlockingOSCUDPServer):
|
||||||
|
def __init__(self, address, dispatcher):
|
||||||
|
super().__init__(("", 0), dispatcher)
|
||||||
|
self.xr_address = address
|
||||||
|
|
||||||
|
def send_message(self, address, value):
|
||||||
|
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 MAirRemote(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_ini()
|
||||||
|
self.server = OSCClientServer((self.xair_ip, self.XAIR_PORT), dispatcher)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.worker = threading.Thread(target=self.run_server)
|
||||||
|
self.worker.daemon = True
|
||||||
|
self.worker.start()
|
||||||
|
self.validate_connection()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _ip_from_ini(self):
|
||||||
|
ini_path = Path.cwd() / "config.ini"
|
||||||
|
parser = ConfigParser()
|
||||||
|
if not parser.read(ini_path):
|
||||||
|
print("Could not read config file")
|
||||||
|
return parser["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, param=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: kinds.MR18KindMap) -> MAirRemote:
|
||||||
|
"""
|
||||||
|
Creates a new MAIR remote class.
|
||||||
|
|
||||||
|
The returned class will subclass MAirRemote.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def init(self, *args, **kwargs):
|
||||||
|
defaultkwargs = {"ip": None}
|
||||||
|
kwargs = defaultkwargs | kwargs
|
||||||
|
MAirRemote.__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.id_}",
|
||||||
|
(MAirRemote,),
|
||||||
|
{
|
||||||
|
"__init__": init,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_remotes = {kind.id_: _make_remote(kind) for kind in kinds.all}
|
||||||
|
|
||||||
|
|
||||||
|
def connect(kind_id: str, *args, **kwargs):
|
||||||
|
MAIRREMOTE_cls = _remotes[kind_id]
|
||||||
|
return MAIRREMOTE_cls(*args, **kwargs)
|
77
mair/meta.py
Normal file
77
mair/meta.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from .errors import MAirRemoteError
|
||||||
|
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 MAirRemoteError(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 MAirRemoteError(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 MAirRemoteError(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 MAirRemoteError(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)
|
109
mair/rtn.py
Normal file
109
mair/rtn.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import abc
|
||||||
|
from typing import Optional
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from .shared import (
|
||||||
|
Config,
|
||||||
|
Preamp,
|
||||||
|
Gate,
|
||||||
|
Dyn,
|
||||||
|
Insert,
|
||||||
|
EQ,
|
||||||
|
GEQ,
|
||||||
|
Mix,
|
||||||
|
Group,
|
||||||
|
Automix,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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.id_}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
_cls.__name__.lower(): type(
|
||||||
|
f"{_cls.__name__}{remote.kind.id_}", (_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
mair/shared.py
Normal file
680
mair/shared.py
Normal file
@ -0,0 +1,680 @@
|
|||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from .util import lin_get, lin_set, log_get, log_set, _get_fader_val, _set_fader_val
|
||||||
|
from .meta import geq_prop
|
||||||
|
|
||||||
|
"""
|
||||||
|
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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(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 MAirRemoteError(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 MAirRemoteError(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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError("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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError(
|
||||||
|
"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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError("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 MAirRemoteError(
|
||||||
|
"weight is a float parameter, expected value in range -12 to 12"
|
||||||
|
)
|
||||||
|
self.setter("weight", lin_set(-12, 12, val))
|
74
mair/strip.py
Normal file
74
mair/strip.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import abc
|
||||||
|
from .errors import MAirRemoteError
|
||||||
|
from .shared import (
|
||||||
|
Config,
|
||||||
|
Preamp,
|
||||||
|
Gate,
|
||||||
|
Dyn,
|
||||||
|
Insert,
|
||||||
|
EQ,
|
||||||
|
GEQ,
|
||||||
|
Mix,
|
||||||
|
Group,
|
||||||
|
Automix,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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.id_}",
|
||||||
|
(cls,),
|
||||||
|
{
|
||||||
|
**{
|
||||||
|
_cls.__name__.lower(): type(
|
||||||
|
f"{_cls.__name__}{remote.kind.id_}", (_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
mair/util.py
Normal file
77
mair/util.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from math import log, exp
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
18
setup.py
Normal file
18
setup.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='mair_remote',
|
||||||
|
version='0.1',
|
||||||
|
description='MAIR Remote Python API',
|
||||||
|
packages=['mair'],
|
||||||
|
install_requires=[
|
||||||
|
'python-osc'
|
||||||
|
],
|
||||||
|
extras_require={
|
||||||
|
'development': [
|
||||||
|
'nose',
|
||||||
|
'randomize',
|
||||||
|
'parameterized'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
17
tests/__init__.py
Normal file
17
tests/__init__.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import mair
|
||||||
|
from mair import kinds
|
||||||
|
import threading
|
||||||
|
|
||||||
|
_kind = 'MR18'
|
||||||
|
|
||||||
|
mars = {kind.id_: mair.connect(_kind) for kind in kinds.all}
|
||||||
|
tests = mars[_kind]
|
||||||
|
|
||||||
|
def setup_package():
|
||||||
|
tests.worker = threading.Thread(target = tests.run_server)
|
||||||
|
tests.worker.daemon = True
|
||||||
|
tests.worker.start()
|
||||||
|
tests.validate_connection()
|
||||||
|
|
||||||
|
def teardown_package():
|
||||||
|
tests.server.shutdown()
|
332
tests/shared_tests.py
Normal file
332
tests/shared_tests.py
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
from nose.tools import assert_equal, nottest
|
||||||
|
from parameterized import parameterized, parameterized_class
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from tests import tests
|
||||||
|
|
||||||
|
"""
|
||||||
|
Not every subclass is tested for every superclass to avoid redundancy.
|
||||||
|
LR: mix, config, insert, geq
|
||||||
|
Strip: mix, preamp, config, gate, automix
|
||||||
|
Bus: config, dyn, eq
|
||||||
|
FXSend: group
|
||||||
|
"""
|
||||||
|
|
||||||
|
""" LR TESTS """
|
||||||
|
#@nottest
|
||||||
|
class TestSetAndGetLRMixHigher(unittest.TestCase):
|
||||||
|
""" Mix """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'lr')
|
||||||
|
self.target = getattr(self.target, 'mix')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('on', True), ('on', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_lr_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('fader', -80.6), ('fader', -67.0)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_lr_float_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
class TestSetAndGetLRConfigHigher(unittest.TestCase):
|
||||||
|
""" Config """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'lr')
|
||||||
|
self.target = getattr(self.target, 'config')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('name', 'test0'), ('name', 'test1')
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_lr_string_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
class TestSetAndGetLRInsertHigher(unittest.TestCase):
|
||||||
|
""" Insert """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'lr')
|
||||||
|
self.target = getattr(self.target, 'insert')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('on', True), ('on', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_lr_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('sel', 0), ('sel', 4)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_lr_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
class TestSetAndGetLRGEQHigher(unittest.TestCase):
|
||||||
|
""" GEQ """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'lr')
|
||||||
|
self.target = getattr(self.target, 'geq')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('slider_20', -13.5), ('slider_20', 5.5), ('slider_6k3', -8.5), ('slider_6k3', 8.5)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_lr_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
|
||||||
|
""" STRIP TESTS """
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 15 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetStripMixHigher(unittest.TestCase):
|
||||||
|
""" Mix """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'strip')
|
||||||
|
self.target = getattr(self.target[self.i], 'mix')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('on', True), ('on', False), ('lr', True), ('lr', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 8 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetStripPreampHigher(unittest.TestCase):
|
||||||
|
""" Preamp """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'strip')
|
||||||
|
self.target = getattr(self.target[self.i], 'preamp')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('highpasson', True), ('highpasson', False), ('usbinput', True), ('usbinput', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('highpassfilter', 20), ('highpassfilter', 399)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('usbtrim', -16.5), ('usbtrim', 5.5)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_float_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 3 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetStripConfigHigher(unittest.TestCase):
|
||||||
|
""" Config """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'strip')
|
||||||
|
self.target = getattr(self.target[self.i], 'config')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('inputsource', 0), ('inputsource', 18), ('usbreturn', 3), ('usbreturn', 12)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 12 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetStripGateHigher(unittest.TestCase):
|
||||||
|
""" Gate """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'strip')
|
||||||
|
self.target = getattr(self.target[self.i], 'gate')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('on', True), ('on', False), ('invert', True), ('invert', False),
|
||||||
|
('filteron', True), ('filteron', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('range', 11), ('range', 48), ('attack', 5), ('attack', 110),
|
||||||
|
('release', 360), ('release', 2505), ('filtertype', 0), ('filtertype', 8)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('mode', 'exp2'), ('mode', 'duck')
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_string_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('threshold', -80.0), ('threshold', 0.0), ('hold', 355), ('hold', 63.2),
|
||||||
|
('filterfreq', 37.2), ('filterfreq', 12765)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_strip_float_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 6 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetStripAutomixHigher(unittest.TestCase):
|
||||||
|
""" Automix """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'strip')
|
||||||
|
self.target = getattr(self.target[self.i], 'automix')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('group', 0), ('group', 2)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_fxsend_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('weight', -10.5), ('weight', 3.5)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_fxsend_float_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
|
||||||
|
""" BUS TESTS """
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 1 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetBusConfigHigher(unittest.TestCase):
|
||||||
|
""" Config """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'bus')
|
||||||
|
self.target = getattr(self.target[self.i], 'config')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('color', 0), ('color', 15)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_bus_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 2 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetBusDynHigher(unittest.TestCase):
|
||||||
|
""" Dyn """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'bus')
|
||||||
|
self.target = getattr(self.target[self.i], 'dyn')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('on', True), ('on', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_bus_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('mode', 'comp'), ('mode', 'exp'), ('env', 'lin'), ('env', 'log'),
|
||||||
|
('det', 'peak'), ('det', 'rms')
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_bus_string_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 0 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetBusEQHigher(unittest.TestCase):
|
||||||
|
""" EQ """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'bus')
|
||||||
|
self.target = getattr(self.target[self.i], 'eq')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('on', True), ('on', False)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_bus_bool_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
self.assertTrue(isinstance(retval, bool))
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('mode', 'peq'), ('mode', 'geq'), ('mode', 'teq')
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_bus_string_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
||||||
|
|
||||||
|
|
||||||
|
""" FXSEND TESTS """
|
||||||
|
#@nottest
|
||||||
|
@parameterized_class([
|
||||||
|
{ 'i': 1 }
|
||||||
|
])
|
||||||
|
class TestSetAndGetFXSendGroupHigher(unittest.TestCase):
|
||||||
|
""" Group """
|
||||||
|
def setUp(self):
|
||||||
|
self.target = getattr(tests, 'fxsend')
|
||||||
|
self.target = getattr(self.target[self.i], 'group')
|
||||||
|
|
||||||
|
@parameterized.expand([
|
||||||
|
('dca', 0), ('dca', 12), ('mute', 3), ('mute', 8)
|
||||||
|
])
|
||||||
|
def test_it_sets_and_gets_fxsend_int_params(self, param, val):
|
||||||
|
setattr(self.target, param, val)
|
||||||
|
retval = getattr(self.target, param)
|
||||||
|
assert_equal(retval, val)
|
Loading…
Reference in New Issue
Block a user