mirror of
https://github.com/onyx-and-iris/xair-api-python.git
synced 2024-11-21 20:31:00 +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:
parent
e8d23562f1
commit
f8c6659fd8
19
CHANGELOG.md
19
CHANGELOG.md
@ -5,11 +5,26 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Before any major/minor/patch patch is released all test units will be run to verify they pass.
|
||||
Before any major/minor/patch patch is released all unit tests will be run to verify they pass.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- [ ] Create a stable branch.
|
||||
- [x]
|
||||
|
||||
## [1.0.2] - 2022-08-07
|
||||
|
||||
### Added
|
||||
|
||||
- now packaged with poetry
|
||||
- package added to pypi
|
||||
- pypi, isort badges added to readme
|
||||
|
||||
### Changed
|
||||
|
||||
- package renamed to xair-api
|
||||
- now using tomllib for config, requires python 3.11
|
||||
- readme, example updated.
|
||||
- imports isorted.
|
||||
|
||||
## [0.1.0] - 2022-05-01
|
||||
|
||||
|
54
README.md
54
README.md
@ -1,8 +1,10 @@
|
||||
[![PyPI version](https://badge.fury.io/py/xair-api.svg)](https://badge.fury.io/py/xair-api)
|
||||
[![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)
|
||||
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
|
||||
![Tests Status](./tests/MR18.svg?dummy=8484744)
|
||||
|
||||
# Mair Remote
|
||||
# Xair API
|
||||
|
||||
This package offers a python interface for the [Behringer XAir](https://www.behringer.com/series.html?category=R-BEHRINGER-XAIRSERIES), [Midas MR](https://www.midasconsoles.com/catalog.html?catalog=Category&category=C-MIDAS-MIXERS-DIGITALSTAGEBOXMIXERS) series of digital rack mixers. I only have access to an MR18 for testing so if there is an error in the kind maps feel free to raise an issue or PR.
|
||||
|
||||
@ -10,54 +12,45 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.9+
|
||||
- Python 3.11 or greater
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
git clone https://github.com/onyx-and-iris/xair-api-python
|
||||
cd xair-api-python
|
||||
```
|
||||
|
||||
Just the interface:
|
||||
|
||||
```
|
||||
pip install .
|
||||
```
|
||||
|
||||
With development dependencies:
|
||||
|
||||
```
|
||||
pip install -e .['development']
|
||||
pip install xair-api
|
||||
```
|
||||
|
||||
## 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:
|
||||
A toml file named config.toml, placed into the current working directory of your code may be used to configure the mixers ip. A valid `config.toml` may resemble:
|
||||
|
||||
```
|
||||
```toml
|
||||
[connection]
|
||||
ip=<ip address>
|
||||
ip=192.168.0.100
|
||||
```
|
||||
|
||||
Alternatively you may state it explicitly as an argument to mair.connect()
|
||||
Alternatively you may pass it as a keyword argument.
|
||||
|
||||
### Example 1
|
||||
### Example
|
||||
|
||||
```python
|
||||
import mair
|
||||
import xair_api
|
||||
|
||||
|
||||
def main():
|
||||
with mair.connect(kind_id, ip=ip) as mixer:
|
||||
mixer.strip[8].config.name = 'sm7b'
|
||||
with xair_api.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}')
|
||||
print(
|
||||
f"strip 09 ({mixer.strip[8].config.name}) on has been set to {mixer.strip[8].config.on}"
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
kind_id = 'MR18'
|
||||
ip = '<ip address>'
|
||||
|
||||
if __name__ == "__main__":
|
||||
kind_id = "MR18"
|
||||
ip = "192.168.0.100"
|
||||
|
||||
main()
|
||||
```
|
||||
@ -71,7 +64,7 @@ Currently the following devices are support:
|
||||
- `XR16`
|
||||
- `XR12`
|
||||
|
||||
### MAirRemote (higher level)
|
||||
### XAirRemote class (higher level)
|
||||
|
||||
`mixer.lr`
|
||||
|
||||
@ -277,8 +270,7 @@ for example: `config.monitor.chmode`
|
||||
|
||||
### `Tests`
|
||||
|
||||
People plug expensive equipment into these mixers, the unit tests adjust parameter values such as gain sliders etc. My advice is
|
||||
to unplug all equipment from the mixer before running these tests. No tests alter phantom power state.
|
||||
Unplug any/all expensive equipment before running any tests.
|
||||
Save your current settings to a snapshot first.
|
||||
|
||||
First make sure you installed the [development dependencies](https://github.com/onyx-and-iris/xair-api-python#installation)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import mair
|
||||
import xair_api
|
||||
|
||||
|
||||
def main():
|
||||
with mair.connect(kind_id, ip=ip) as mixer:
|
||||
with xair_api.connect(kind_id, ip=ip) as mixer:
|
||||
mixer.strip[8].config.name = "sm7b"
|
||||
mixer.strip[8].config.on = True
|
||||
print(
|
||||
|
2
config.toml
Normal file
2
config.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[connection]
|
||||
ip=172.16.0.100
|
@ -1,3 +0,0 @@
|
||||
from .mair import connect
|
||||
|
||||
_ALL__ = ['connect']
|
@ -1,4 +0,0 @@
|
||||
class MAirRemoteError(Exception):
|
||||
"""Base error class for MAIR Remote."""
|
||||
|
||||
pass
|
291
poetry.lock
generated
Normal file
291
poetry.lock
generated
Normal file
@ -0,0 +1,291 @@
|
||||
[[package]]
|
||||
name = "atomicwrites"
|
||||
version = "1.4.1"
|
||||
description = "Atomic file writes."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "22.1.0"
|
||||
description = "Classes Without Boilerplate"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
|
||||
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
|
||||
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
|
||||
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.6.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "1.1.1"
|
||||
description = "iniconfig: brain-dead simple config-ini parsing"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.10.1"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1,<4.0"
|
||||
|
||||
[package.extras]
|
||||
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||
requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
plugins = ["setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.0.0"
|
||||
description = "plugin and hook calling mechanisms for python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["railroad-diagrams", "jinja2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.1.2"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
py = ">=1.8.2"
|
||||
tomli = ">=1.0.0"
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-randomly"
|
||||
version = "3.12.0"
|
||||
description = "Pytest plugin to randomly order tests and control random.seed."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
pytest = "*"
|
||||
|
||||
[[package]]
|
||||
name = "python-osc"
|
||||
version = "1.8.0"
|
||||
description = "Open Sound Control server and client implementations in pure Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "ba3fcf3e019a33e98eb7f8ba760c0bbda4017819a6c45b50f2ec7e0e31ac127c"
|
||||
|
||||
[metadata.files]
|
||||
atomicwrites = []
|
||||
attrs = []
|
||||
black = [
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f586c26118bc6e714ec58c09df0157fe2d9ee195c764f630eb0d8e7ccce72e69"},
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b270a168d69edb8b7ed32c193ef10fd27844e5c60852039599f9184460ce0807"},
|
||||
{file = "black-22.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6797f58943fceb1c461fb572edbe828d811e719c24e03375fd25170ada53825e"},
|
||||
{file = "black-22.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c85928b9d5f83b23cee7d0efcb310172412fbf7cb9d9ce963bd67fd141781def"},
|
||||
{file = "black-22.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6fe02afde060bbeef044af7996f335fbe90b039ccf3f5eb8f16df8b20f77666"},
|
||||
{file = "black-22.6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cfaf3895a9634e882bf9d2363fed5af8888802d670f58b279b0bece00e9a872d"},
|
||||
{file = "black-22.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94783f636bca89f11eb5d50437e8e17fbc6a929a628d82304c80fa9cd945f256"},
|
||||
{file = "black-22.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2ea29072e954a4d55a2ff58971b83365eba5d3d357352a07a7a4df0d95f51c78"},
|
||||
{file = "black-22.6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e439798f819d49ba1c0bd9664427a05aab79bfba777a6db94fd4e56fae0cb849"},
|
||||
{file = "black-22.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:187d96c5e713f441a5829e77120c269b6514418f4513a390b0499b0987f2ff1c"},
|
||||
{file = "black-22.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:074458dc2f6e0d3dab7928d4417bb6957bb834434516f21514138437accdbe90"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a218d7e5856f91d20f04e931b6f16d15356db1c846ee55f01bac297a705ca24f"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:568ac3c465b1c8b34b61cd7a4e349e93f91abf0f9371eda1cf87194663ab684e"},
|
||||
{file = "black-22.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6c1734ab264b8f7929cef8ae5f900b85d579e6cbfde09d7387da8f04771b51c6"},
|
||||
{file = "black-22.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a3ac16efe9ec7d7381ddebcc022119794872abce99475345c5a61aa18c45ad"},
|
||||
{file = "black-22.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:b9fd45787ba8aa3f5e0a0a98920c1012c884622c6c920dbe98dbd05bc7c70fbf"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7ba9be198ecca5031cd78745780d65a3f75a34b2ff9be5837045dce55db83d1c"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3db5b6409b96d9bd543323b23ef32a1a2b06416d525d27e0f67e74f1446c8f2"},
|
||||
{file = "black-22.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:560558527e52ce8afba936fcce93a7411ab40c7d5fe8c2463e279e843c0328ee"},
|
||||
{file = "black-22.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b154e6bbde1e79ea3260c4b40c0b7b3109ffcdf7bc4ebf8859169a6af72cd70b"},
|
||||
{file = "black-22.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:4af5bc0e1f96be5ae9bd7aaec219c901a94d6caa2484c21983d043371c733fc4"},
|
||||
{file = "black-22.6.0-py3-none-any.whl", hash = "sha256:ac609cf8ef5e7115ddd07d85d988d074ed00e10fbc3445aee393e70164a2219c"},
|
||||
{file = "black-22.6.0.tar.gz", hash = "sha256:6c6d39e28aed379aec40da1c65434c77d75e65bb59a1e1c283de545fb4e7c6c9"},
|
||||
]
|
||||
click = [
|
||||
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
]
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
|
||||
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
|
||||
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
|
||||
]
|
||||
pytest-randomly = [
|
||||
{file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"},
|
||||
{file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"},
|
||||
]
|
||||
python-osc = []
|
||||
tomli = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
22
pyproject.toml
Normal file
22
pyproject.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[tool.poetry]
|
||||
name = "xair-api"
|
||||
version = "1.0.2"
|
||||
description = "Remote control Behringer X-Air | Midas MR mixers through OSC"
|
||||
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/onyx-and-iris/xair-api-python"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.11"
|
||||
python-osc = "^1.8.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "^7.1.2"
|
||||
pytest-randomly = "^3.12.0"
|
||||
black = "^22.6.0"
|
||||
isort = "^5.10.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
10
setup.py
10
setup.py
@ -1,10 +0,0 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="mair_remote",
|
||||
version="0.1.0",
|
||||
description="MAIR Remote Python API",
|
||||
packages=["mair"],
|
||||
install_requires=["python-osc"],
|
||||
extras_require={"development": ["pytest", "pytest-randomly", "genbadge[tests]"]},
|
||||
)
|
@ -1,13 +1,14 @@
|
||||
import mair
|
||||
from mair import kinds
|
||||
import sys
|
||||
import threading
|
||||
from dataclasses import dataclass
|
||||
import sys
|
||||
|
||||
import xair_api
|
||||
from xair_api import kinds
|
||||
|
||||
kind_id = "MR18"
|
||||
ip = "mixer.local"
|
||||
|
||||
tests = mair.connect(kind_id, ip=ip)
|
||||
tests = xair_api.connect(kind_id, ip=ip)
|
||||
|
||||
kind = kinds.get(kind_id)
|
||||
|
||||
|
@ -11,7 +11,7 @@ Function RunTests {
|
||||
$line | Tee-Object -FilePath $coverage -Append
|
||||
}
|
||||
}
|
||||
Write-Output "$(Get-TimeStamp)" | Out-file $coverage -Append
|
||||
Write-Output "$(Get-TimeStamp)" | Out-File $coverage -Append
|
||||
|
||||
Invoke-Expression "genbadge tests -t 90 -i ./tests/.coverage.xml -o ./tests/$kind.svg"
|
||||
}
|
||||
@ -23,7 +23,7 @@ Function Get-TimeStamp {
|
||||
}
|
||||
|
||||
if ($MyInvocation.InvocationName -ne ".") {
|
||||
Invoke-Expression ".\venv\Scripts\Activate.ps1"
|
||||
Invoke-Expression ".\.venv\Scripts\Activate.ps1"
|
||||
|
||||
RunTests
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
from tests import tests, data
|
||||
|
||||
from tests import data, tests
|
||||
|
||||
"""
|
||||
Not every subclass is tested for every superclass to avoid redundancy.
|
||||
|
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"]
|
@ -1,17 +1,7 @@
|
||||
import abc
|
||||
from .errors import MAirRemoteError
|
||||
from .shared import (
|
||||
Config,
|
||||
Preamp,
|
||||
Gate,
|
||||
Dyn,
|
||||
Insert,
|
||||
EQ,
|
||||
GEQ,
|
||||
Mix,
|
||||
Group,
|
||||
Automix,
|
||||
)
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IBus(abc.ABC):
|
||||
@ -46,12 +36,12 @@ class Bus(IBus):
|
||||
Returns a Bus class of a kind.
|
||||
"""
|
||||
BUS_cls = type(
|
||||
f"Bus{remote.kind.id_}",
|
||||
f"Bus{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind.id_}", (_cls, cls), {}
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (
|
||||
Config,
|
@ -1,6 +1,7 @@
|
||||
import abc
|
||||
from .errors import MAirRemoteError
|
||||
|
||||
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
|
||||
|
||||
@ -36,7 +37,7 @@ class Config(IConfig):
|
||||
LINKS_cls = _make_links_mixins[remote.kind.id_]
|
||||
MONITOR_cls = type(f"ConfigMonitor", (Config.Monitor, cls), {})
|
||||
CONFIG_cls = type(
|
||||
f"Config{remote.kind.id_}",
|
||||
f"Config{remote.kind}",
|
||||
(cls, LINKS_cls),
|
||||
{"monitor": MONITOR_cls(remote)},
|
||||
)
|
||||
@ -53,7 +54,7 @@ class Config(IConfig):
|
||||
@amixenable.setter
|
||||
def amixenable(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("amixenable is a bool parameter")
|
||||
raise XAirRemoteError("amixenable is a bool parameter")
|
||||
self.setter("amixenable", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -63,7 +64,7 @@ class Config(IConfig):
|
||||
@amixlock.setter
|
||||
def amixlock(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("amixlock is a bool parameter")
|
||||
raise XAirRemoteError("amixlock is a bool parameter")
|
||||
self.setter("amixlock", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -73,7 +74,7 @@ class Config(IConfig):
|
||||
@mute_group.setter
|
||||
def mute_group(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("mute_group is a bool parameter")
|
||||
raise XAirRemoteError("mute_group is a bool parameter")
|
||||
self.setter("mute", 1 if val else 0)
|
||||
|
||||
class Monitor:
|
||||
@ -98,7 +99,7 @@ class Config(IConfig):
|
||||
@source.setter
|
||||
def source(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("source is an int parameter")
|
||||
raise XAirRemoteError("source is an int parameter")
|
||||
self.setter(f"source", val)
|
||||
|
||||
@property
|
||||
@ -108,7 +109,7 @@ class Config(IConfig):
|
||||
@sourcetrim.setter
|
||||
def sourcetrim(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"sourcetrim is a float parameter, expected value in range -18 to 18"
|
||||
)
|
||||
self.setter("sourcetrim", lin_set(-18, 18, val))
|
||||
@ -120,7 +121,7 @@ class Config(IConfig):
|
||||
@chmode.setter
|
||||
def chmode(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("chmode is a bool parameter")
|
||||
raise XAirRemoteError("chmode is a bool parameter")
|
||||
self.setter("chmode", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -130,7 +131,7 @@ class Config(IConfig):
|
||||
@busmode.setter
|
||||
def busmode(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("busmode is a bool parameter")
|
||||
raise XAirRemoteError("busmode is a bool parameter")
|
||||
self.setter("busmode", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -140,7 +141,7 @@ class Config(IConfig):
|
||||
@dimgain.setter
|
||||
def dimgain(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"dimgain is an int parameter, expected value in range -40 to 0"
|
||||
)
|
||||
self.setter("dimatt", lin_set(-40, 0, val))
|
||||
@ -152,7 +153,7 @@ class Config(IConfig):
|
||||
@dim.setter
|
||||
def dim(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("dim is a bool parameter")
|
||||
raise XAirRemoteError("dim is a bool parameter")
|
||||
self.setter("dim", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -162,7 +163,7 @@ class Config(IConfig):
|
||||
@mono.setter
|
||||
def mono(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("mono is a bool parameter")
|
||||
raise XAirRemoteError("mono is a bool parameter")
|
||||
self.setter("mono", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -172,7 +173,7 @@ class Config(IConfig):
|
||||
@mute.setter
|
||||
def mute(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("mute is a bool parameter")
|
||||
raise XAirRemoteError("mute is a bool parameter")
|
||||
self.setter("mute", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -182,14 +183,14 @@ class Config(IConfig):
|
||||
@dimfpl.setter
|
||||
def dimfpl(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("dimfpl is a bool parameter")
|
||||
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.id_}",
|
||||
f"Links{kind}",
|
||||
(),
|
||||
{
|
||||
"link_eq": bool_prop("linkcfg/eq"),
|
@ -1,5 +1,6 @@
|
||||
import abc
|
||||
from .errors import MAirRemoteError
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
|
||||
|
||||
class IDCA(abc.ABC):
|
||||
@ -35,7 +36,7 @@ class DCA(IDCA):
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("on is a boolean parameter")
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -45,7 +46,7 @@ class DCA(IDCA):
|
||||
@name.setter
|
||||
def name(self, val: str):
|
||||
if not isinstance(val, str):
|
||||
raise MAirRemoteError("name is a str parameter")
|
||||
raise XAirRemoteError("name is a str parameter")
|
||||
self.setter("config/name")[0]
|
||||
|
||||
@property
|
||||
@ -55,5 +56,5 @@ class DCA(IDCA):
|
||||
@color.setter
|
||||
def color(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("color is an int parameter")
|
||||
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
|
@ -1,17 +1,7 @@
|
||||
import abc
|
||||
from .errors import MAirRemoteError
|
||||
from .shared import (
|
||||
Config,
|
||||
Preamp,
|
||||
Gate,
|
||||
Dyn,
|
||||
Insert,
|
||||
EQ,
|
||||
GEQ,
|
||||
Mix,
|
||||
Group,
|
||||
Automix,
|
||||
)
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IFX(abc.ABC):
|
||||
@ -46,12 +36,12 @@ class FXSend(IFX):
|
||||
Returns an FXSend class of a kind.
|
||||
"""
|
||||
FXSEND_cls = type(
|
||||
f"FXSend{remote.kind.id_}",
|
||||
f"FXSend{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind.id_}", (_cls, cls), {}
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (Config, Mix, Group)
|
||||
}
|
||||
@ -78,5 +68,5 @@ class FXReturn(IFX):
|
||||
@type.setter
|
||||
def type(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("type is an integer parameter")
|
||||
raise XAirRemoteError("type is an integer parameter")
|
||||
self.setter("type", val)
|
@ -15,7 +15,13 @@ class X32KindMap:
|
||||
|
||||
|
||||
@dataclass
|
||||
class MR18KindMap:
|
||||
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
|
||||
@ -26,7 +32,7 @@ class MR18KindMap:
|
||||
|
||||
|
||||
@dataclass
|
||||
class XR16KindMap:
|
||||
class XR16KindMap(KindMap):
|
||||
id_: str
|
||||
num_dca: int = 4
|
||||
num_strip: int = 16
|
||||
@ -36,7 +42,7 @@ class XR16KindMap:
|
||||
|
||||
|
||||
@dataclass
|
||||
class XR12KindMap:
|
||||
class XR12KindMap(KindMap):
|
||||
id_: str
|
||||
num_dca: int = 4
|
||||
num_strip: int = 12
|
@ -1,17 +1,7 @@
|
||||
import abc
|
||||
from .errors import MAirRemoteError
|
||||
from .shared import (
|
||||
Config,
|
||||
Preamp,
|
||||
Gate,
|
||||
Dyn,
|
||||
Insert,
|
||||
EQ,
|
||||
GEQ,
|
||||
Mix,
|
||||
Group,
|
||||
Automix,
|
||||
)
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class ILR(abc.ABC):
|
||||
@ -45,12 +35,12 @@ class LR(ILR):
|
||||
Returns an LR class of a kind.
|
||||
"""
|
||||
LR_cls = type(
|
||||
f"LR{remote.kind.id_}",
|
||||
f"LR{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind.id_}", (_cls, cls), {}
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote)
|
||||
for _cls in (
|
||||
Config,
|
@ -1,4 +1,4 @@
|
||||
from .errors import MAirRemoteError
|
||||
from .errors import XAirRemoteError
|
||||
from .util import lin_get, lin_set
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ def bool_prop(param):
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError(f"{param} is a boolean parameter")
|
||||
raise XAirRemoteError(f"{param} is a boolean parameter")
|
||||
self.setter(param, 1 if val else 0)
|
||||
|
||||
return property(fget, fset)
|
||||
@ -24,7 +24,7 @@ def string_prop(param):
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, str):
|
||||
raise MAirRemoteError(f"{param} is a string parameter")
|
||||
raise XAirRemoteError(f"{param} is a string parameter")
|
||||
self.setter(param, val)
|
||||
|
||||
return property(fget, fset)
|
||||
@ -38,7 +38,7 @@ def int_prop(param):
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(f"{param} is an integer parameter")
|
||||
raise XAirRemoteError(f"{param} is an integer parameter")
|
||||
self.setter(param, val)
|
||||
|
||||
return property(fget, fset)
|
||||
@ -52,7 +52,7 @@ def float_prop(param):
|
||||
|
||||
def fset(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(f"{param} is a float parameter")
|
||||
raise XAirRemoteError(f"{param} is a float parameter")
|
||||
self.setter(param, val)
|
||||
|
||||
return property(fget, fset)
|
@ -1,18 +1,8 @@
|
||||
import abc
|
||||
from typing import Optional
|
||||
from .errors import MAirRemoteError
|
||||
from .shared import (
|
||||
Config,
|
||||
Preamp,
|
||||
Gate,
|
||||
Dyn,
|
||||
Insert,
|
||||
EQ,
|
||||
GEQ,
|
||||
Mix,
|
||||
Group,
|
||||
Automix,
|
||||
)
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IRtn(abc.ABC):
|
||||
@ -48,12 +38,12 @@ class Aux(IRtn):
|
||||
Returns an Aux class of a kind.
|
||||
"""
|
||||
AUX_cls = type(
|
||||
f"Aux{remote.kind.id_}",
|
||||
f"Aux{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind.id_}", (_cls, cls), {}
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote)
|
||||
for _cls in (
|
||||
Config,
|
@ -1,8 +1,8 @@
|
||||
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 .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
|
||||
@ -22,7 +22,7 @@ class Config:
|
||||
@name.setter
|
||||
def name(self, val: str):
|
||||
if not isinstance(val, str):
|
||||
raise MAirRemoteError("name is a string parameter")
|
||||
raise XAirRemoteError("name is a string parameter")
|
||||
self.setter("name", val)
|
||||
|
||||
@property
|
||||
@ -32,7 +32,7 @@ class Config:
|
||||
@color.setter
|
||||
def color(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("color is an int parameter")
|
||||
raise XAirRemoteError("color is an int parameter")
|
||||
self.setter("color", val)
|
||||
|
||||
@property
|
||||
@ -42,7 +42,7 @@ class Config:
|
||||
@inputsource.setter
|
||||
def inputsource(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("inputsource is an int parameter")
|
||||
raise XAirRemoteError("inputsource is an int parameter")
|
||||
self.setter("insrc", val)
|
||||
|
||||
@property
|
||||
@ -52,7 +52,7 @@ class Config:
|
||||
@usbreturn.setter
|
||||
def usbreturn(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("usbreturn is an int parameter")
|
||||
raise XAirRemoteError("usbreturn is an int parameter")
|
||||
self.setter("rtnsrc", val)
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ class Preamp:
|
||||
@usbtrim.setter
|
||||
def usbtrim(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"usbtrim is a float parameter, expected value in range -18 to 18"
|
||||
)
|
||||
self.setter("rtntrim", lin_set(-18, 18, val))
|
||||
@ -81,7 +81,7 @@ class Preamp:
|
||||
@usbinput.setter
|
||||
def usbinput(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("rtnsw is a bool parameter")
|
||||
raise XAirRemoteError("rtnsw is a bool parameter")
|
||||
self.setter("rtnsw", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -91,7 +91,7 @@ class Preamp:
|
||||
@invert.setter
|
||||
def invert(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("invert is a bool parameter")
|
||||
raise XAirRemoteError("invert is a bool parameter")
|
||||
self.setter("invert", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -101,7 +101,7 @@ class Preamp:
|
||||
@highpasson.setter
|
||||
def highpasson(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("hpon is a bool parameter")
|
||||
raise XAirRemoteError("hpon is a bool parameter")
|
||||
self.setter("hpon", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -111,7 +111,7 @@ class Preamp:
|
||||
@highpassfilter.setter
|
||||
def highpassfilter(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("highpassfilter is an int parameter")
|
||||
raise XAirRemoteError("highpassfilter is an int parameter")
|
||||
self.setter("hpf", log_set(20, 400, val))
|
||||
|
||||
|
||||
@ -128,7 +128,7 @@ class Gate:
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("on is a boolean parameter")
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -140,7 +140,7 @@ class Gate:
|
||||
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}")
|
||||
raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
|
||||
self.setter("mode", opts.index(val))
|
||||
|
||||
@property
|
||||
@ -150,7 +150,7 @@ class Gate:
|
||||
@threshold.setter
|
||||
def threshold(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"threshold is a float parameter, expected value in range -80 to 0"
|
||||
)
|
||||
self.setter("thr", lin_set(-80, 0, val))
|
||||
@ -162,7 +162,7 @@ class Gate:
|
||||
@range.setter
|
||||
def range(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"range is an int parameter, expected value in range 3 to 60"
|
||||
)
|
||||
self.setter("range", lin_set(3, 60, val))
|
||||
@ -174,7 +174,7 @@ class Gate:
|
||||
@attack.setter
|
||||
def attack(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"attack is an int parameter, expected value in range 0 to 120"
|
||||
)
|
||||
self.setter("attack", lin_set(0, 120, val))
|
||||
@ -195,7 +195,7 @@ class Gate:
|
||||
@release.setter
|
||||
def release(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"release is an int parameter, expected value in range 5 to 4000"
|
||||
)
|
||||
self.setter("release", log_set(5, 4000, val))
|
||||
@ -207,7 +207,7 @@ class Gate:
|
||||
@keysource.setter
|
||||
def keysource(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("keysource is an int parameter")
|
||||
raise XAirRemoteError("keysource is an int parameter")
|
||||
self.setter("keysrc", val)
|
||||
|
||||
@property
|
||||
@ -217,7 +217,7 @@ class Gate:
|
||||
@filteron.setter
|
||||
def filteron(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("filteron is a boolean parameter")
|
||||
raise XAirRemoteError("filteron is a boolean parameter")
|
||||
self.setter("filter/on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -227,7 +227,7 @@ class Gate:
|
||||
@filtertype.setter
|
||||
def filtertype(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("filtertype is an int parameter")
|
||||
raise XAirRemoteError("filtertype is an int parameter")
|
||||
self.setter("filter/type", val)
|
||||
|
||||
@property
|
||||
@ -253,7 +253,7 @@ class Dyn:
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("on is a boolean parameter")
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -265,7 +265,7 @@ class Dyn:
|
||||
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}")
|
||||
raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
|
||||
self.setter("mode", opts.index(val))
|
||||
|
||||
@property
|
||||
@ -277,7 +277,7 @@ class Dyn:
|
||||
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}")
|
||||
raise XAirRemoteError(f"det is a string parameter, expected one of {opts}")
|
||||
self.setter("det", opts.index(val))
|
||||
|
||||
@property
|
||||
@ -289,7 +289,7 @@ class Dyn:
|
||||
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}")
|
||||
raise XAirRemoteError(f"env is a string parameter, expected one of {opts}")
|
||||
self.setter("env", opts.index(val))
|
||||
|
||||
@property
|
||||
@ -299,7 +299,7 @@ class Dyn:
|
||||
@threshold.setter
|
||||
def threshold(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"threshold is a float parameter, expected value in range -80 to 0"
|
||||
)
|
||||
self.setter("thr", lin_set(-60, 0, val))
|
||||
@ -312,7 +312,7 @@ class Dyn:
|
||||
@ratio.setter
|
||||
def ratio(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("ratio is an int parameter")
|
||||
raise XAirRemoteError("ratio is an int parameter")
|
||||
self.setter("ratio", val)
|
||||
|
||||
@property
|
||||
@ -322,7 +322,7 @@ class Dyn:
|
||||
@knee.setter
|
||||
def knee(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"knee is an int parameter, expected value in range 0 to 5"
|
||||
)
|
||||
self.setter("knee", lin_set(0, 5, val))
|
||||
@ -359,7 +359,7 @@ class Dyn:
|
||||
@release.setter
|
||||
def release(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"release is an int parameter, expected value in range 5 to 4000"
|
||||
)
|
||||
self.setter("release", log_set(5, 4000, val))
|
||||
@ -371,7 +371,7 @@ class Dyn:
|
||||
@mix.setter
|
||||
def mix(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"mix is an int parameter, expected value in range 0 to 5"
|
||||
)
|
||||
self.setter("mix", lin_set(0, 100, val))
|
||||
@ -383,7 +383,7 @@ class Dyn:
|
||||
@keysource.setter
|
||||
def keysource(self, val):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("keysource is an int parameter")
|
||||
raise XAirRemoteError("keysource is an int parameter")
|
||||
self.setter("keysrc", val)
|
||||
|
||||
@property
|
||||
@ -393,7 +393,7 @@ class Dyn:
|
||||
@auto.setter
|
||||
def auto(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("auto is a boolean parameter")
|
||||
raise XAirRemoteError("auto is a boolean parameter")
|
||||
self.setter("auto", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -403,7 +403,7 @@ class Dyn:
|
||||
@filteron.setter
|
||||
def filteron(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("filteron is a boolean parameter")
|
||||
raise XAirRemoteError("filteron is a boolean parameter")
|
||||
self.setter("filter/on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -413,7 +413,7 @@ class Dyn:
|
||||
@filtertype.setter
|
||||
def filtertype(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("filtertype is an int parameter")
|
||||
raise XAirRemoteError("filtertype is an int parameter")
|
||||
self.setter("filter/type", val)
|
||||
|
||||
@property
|
||||
@ -439,7 +439,7 @@ class Insert:
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("on is a boolean parameter")
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -449,7 +449,7 @@ class Insert:
|
||||
@sel.setter
|
||||
def sel(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("sel is an int parameter")
|
||||
raise XAirRemoteError("sel is an int parameter")
|
||||
self.setter("sel", val)
|
||||
|
||||
|
||||
@ -496,7 +496,7 @@ class EQ:
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("on is a boolean parameter")
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -508,7 +508,7 @@ class EQ:
|
||||
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}")
|
||||
raise XAirRemoteError(f"mode is a string parameter, expected one of {opts}")
|
||||
self.setter("mode", opts.index(val))
|
||||
|
||||
class EQBand:
|
||||
@ -531,7 +531,7 @@ class EQ:
|
||||
@type.setter
|
||||
def type(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("type is an int parameter")
|
||||
raise XAirRemoteError("type is an int parameter")
|
||||
self.setter(f"type", val)
|
||||
|
||||
@property
|
||||
@ -601,7 +601,7 @@ class Mix:
|
||||
@on.setter
|
||||
def on(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("on is a boolean parameter")
|
||||
raise XAirRemoteError("on is a boolean parameter")
|
||||
self.setter("on", 1 if val else 0)
|
||||
|
||||
@property
|
||||
@ -620,7 +620,7 @@ class Mix:
|
||||
@lr.setter
|
||||
def lr(self, val: bool):
|
||||
if not isinstance(val, bool):
|
||||
raise MAirRemoteError("lr is a boolean parameter")
|
||||
raise XAirRemoteError("lr is a boolean parameter")
|
||||
self.setter("lr", 1 if val else 0)
|
||||
|
||||
|
||||
@ -637,7 +637,7 @@ class Group:
|
||||
@dca.setter
|
||||
def dca(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("dca is an int parameter")
|
||||
raise XAirRemoteError("dca is an int parameter")
|
||||
self.setter("dca", val)
|
||||
|
||||
@property
|
||||
@ -647,7 +647,7 @@ class Group:
|
||||
@mute.setter
|
||||
def mute(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("mute is an int parameter")
|
||||
raise XAirRemoteError("mute is an int parameter")
|
||||
self.setter("mute", val)
|
||||
|
||||
|
||||
@ -664,7 +664,7 @@ class Automix:
|
||||
@group.setter
|
||||
def group(self, val: int):
|
||||
if not isinstance(val, int):
|
||||
raise MAirRemoteError("group is an int parameter")
|
||||
raise XAirRemoteError("group is an int parameter")
|
||||
self.setter("group", val)
|
||||
|
||||
@property
|
||||
@ -674,7 +674,7 @@ class Automix:
|
||||
@weight.setter
|
||||
def weight(self, val: float):
|
||||
if not isinstance(val, float):
|
||||
raise MAirRemoteError(
|
||||
raise XAirRemoteError(
|
||||
"weight is a float parameter, expected value in range -12 to 12"
|
||||
)
|
||||
self.setter("weight", lin_set(-12, 12, val))
|
@ -1,17 +1,7 @@
|
||||
import abc
|
||||
from .errors import MAirRemoteError
|
||||
from .shared import (
|
||||
Config,
|
||||
Preamp,
|
||||
Gate,
|
||||
Dyn,
|
||||
Insert,
|
||||
EQ,
|
||||
GEQ,
|
||||
Mix,
|
||||
Group,
|
||||
Automix,
|
||||
)
|
||||
|
||||
from .errors import XAirRemoteError
|
||||
from .shared import EQ, GEQ, Automix, Config, Dyn, Gate, Group, Insert, Mix, Preamp
|
||||
|
||||
|
||||
class IStrip(abc.ABC):
|
||||
@ -46,12 +36,12 @@ class Strip(IStrip):
|
||||
Returns a Strip class of a kind.
|
||||
"""
|
||||
STRIP_cls = type(
|
||||
f"Strip{remote.kind.id_}",
|
||||
f"Strip{remote.kind}",
|
||||
(cls,),
|
||||
{
|
||||
**{
|
||||
_cls.__name__.lower(): type(
|
||||
f"{_cls.__name__}{remote.kind.id_}", (_cls, cls), {}
|
||||
f"{_cls.__name__}{remote.kind}", (_cls, cls), {}
|
||||
)(remote, index)
|
||||
for _cls in (
|
||||
Config,
|
@ -1,4 +1,4 @@
|
||||
from math import log, exp
|
||||
from math import exp, log
|
||||
|
||||
|
||||
def lin_get(min, max, val):
|
@ -1,29 +1,33 @@
|
||||
import abc
|
||||
import time
|
||||
import threading
|
||||
from pythonosc.dispatcher import Dispatcher
|
||||
from pythonosc.osc_server import BlockingOSCUDPServer
|
||||
from pythonosc.osc_message_builder import OscMessageBuilder
|
||||
import time
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
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 .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 .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, dispatcher):
|
||||
def __init__(self, address: str, dispatcher: Dispatcher):
|
||||
super().__init__(("", 0), dispatcher)
|
||||
self.xr_address = address
|
||||
|
||||
def send_message(self, address, value):
|
||||
def send_message(self, address: str, value: str):
|
||||
builder = OscMessageBuilder(address=address)
|
||||
if value is None:
|
||||
values = list()
|
||||
@ -37,10 +41,8 @@ class OSCClientServer(BlockingOSCUDPServer):
|
||||
self.socket.sendto(msg.dgram, self.xr_address)
|
||||
|
||||
|
||||
class MAirRemote(abc.ABC):
|
||||
"""
|
||||
Handles the communication with the M-Air mixer via the OSC protocol
|
||||
"""
|
||||
class XAirRemote(abc.ABC):
|
||||
"""Handles the communication with the M-Air mixer via the OSC protocol"""
|
||||
|
||||
_CONNECT_TIMEOUT = 0.5
|
||||
_WAIT_TIME = 0.025
|
||||
@ -53,23 +55,23 @@ class MAirRemote(abc.ABC):
|
||||
def __init__(self, **kwargs):
|
||||
dispatcher = Dispatcher()
|
||||
dispatcher.set_default_handler(self.msg_handler)
|
||||
self.xair_ip = kwargs["ip"] or self._ip_from_ini()
|
||||
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.worker = threading.Thread(target=self.run_server)
|
||||
self.worker.daemon = True
|
||||
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_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 _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")
|
||||
@ -87,7 +89,7 @@ class MAirRemote(abc.ABC):
|
||||
def msg_handler(self, addr, *data):
|
||||
self.info_response = data[:]
|
||||
|
||||
def send(self, address, param=None):
|
||||
def send(self, address: str, param: Optional[str] = None):
|
||||
self.server.send_message(address, param)
|
||||
time.sleep(self._WAIT_TIME)
|
||||
|
||||
@ -100,7 +102,7 @@ class MAirRemote(abc.ABC):
|
||||
self.server.shutdown()
|
||||
|
||||
|
||||
def _make_remote(kind: kinds.MR18KindMap) -> MAirRemote:
|
||||
def _make_remote(kind: KindMap) -> XAirRemote:
|
||||
"""
|
||||
Creates a new MAIR remote class.
|
||||
|
||||
@ -110,7 +112,7 @@ def _make_remote(kind: kinds.MR18KindMap) -> MAirRemote:
|
||||
def init(self, *args, **kwargs):
|
||||
defaultkwargs = {"ip": None, "port": None}
|
||||
kwargs = defaultkwargs | kwargs
|
||||
MAirRemote.__init__(self, *args, **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))
|
||||
@ -123,8 +125,8 @@ def _make_remote(kind: kinds.MR18KindMap) -> MAirRemote:
|
||||
self.rtn = tuple(Rtn.make(self, i) for i in range(kind.num_rtn))
|
||||
|
||||
return type(
|
||||
f"MAirRemote{kind.id_}",
|
||||
(MAirRemote,),
|
||||
f"MAirRemote{kind}",
|
||||
(XAirRemote,),
|
||||
{
|
||||
"__init__": init,
|
||||
},
|
||||
@ -134,6 +136,15 @@ def _make_remote(kind: kinds.MR18KindMap) -> MAirRemote:
|
||||
_remotes = {kind.id_: _make_remote(kind) for kind in kinds.all}
|
||||
|
||||
|
||||
def connect(kind_id: str, *args, **kwargs):
|
||||
MAIRREMOTE_cls = _remotes[kind_id]
|
||||
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)
|
Loading…
Reference in New Issue
Block a user