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:
onyx-and-iris 2022-08-07 23:55:51 +01:00
parent e8d23562f1
commit f8c6659fd8
26 changed files with 522 additions and 239 deletions

View File

@ -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

View File

@ -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)

View File

@ -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
View File

@ -0,0 +1,2 @@
[connection]
ip=172.16.0.100

View File

@ -1,3 +0,0 @@
from .mair import connect
_ALL__ = ['connect']

View File

@ -1,4 +0,0 @@
class MAirRemoteError(Exception):
"""Base error class for MAIR Remote."""
pass

291
poetry.lock generated Normal file
View 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
View 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"

View File

@ -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]"]},
)

View File

@ -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)

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,3 @@
from .xair import request_remote_obj as connect
_ALL__ = ["connect"]

View File

@ -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,

View File

@ -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"),

View File

@ -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
View File

@ -0,0 +1,4 @@
class XAirRemoteError(Exception):
"""Base error class for XAIR Remote."""
pass

View File

@ -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)

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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))

View File

@ -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,

View File

@ -1,4 +1,4 @@
from math import log, exp
from math import exp, log
def lin_get(min, max, val):

View File

@ -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)