diff --git a/CHANGELOG.md b/CHANGELOG.md index e076563..93b209f 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/README.md b/README.md index 3df9577..7322e97 100644 --- a/README.md +++ b/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=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 = '' + +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) diff --git a/__main__.py b/__main__.py index 8b3b7ed..374bd97 100644 --- a/__main__.py +++ b/__main__.py @@ -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( diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..2f68de6 --- /dev/null +++ b/config.toml @@ -0,0 +1,2 @@ +[connection] +ip=172.16.0.100 \ No newline at end of file diff --git a/mair/__init__.py b/mair/__init__.py deleted file mode 100644 index 6ab08ef..0000000 --- a/mair/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .mair import connect - -_ALL__ = ['connect'] diff --git a/mair/errors.py b/mair/errors.py deleted file mode 100644 index d370b9e..0000000 --- a/mair/errors.py +++ /dev/null @@ -1,4 +0,0 @@ -class MAirRemoteError(Exception): - """Base error class for MAIR Remote.""" - - pass diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ca50baf --- /dev/null +++ b/poetry.lock @@ -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"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cb72e16 --- /dev/null +++ b/pyproject.toml @@ -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 "] +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" diff --git a/setup.py b/setup.py deleted file mode 100644 index 48cf866..0000000 --- a/setup.py +++ /dev/null @@ -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]"]}, -) diff --git a/tests/__init__.py b/tests/__init__.py index 4d0b5aa..b4723ff 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -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) diff --git a/tests/pre-commit.ps1 b/tests/pre-commit.ps1 index e2c8e27..de4a602 100644 --- a/tests/pre-commit.ps1 +++ b/tests/pre-commit.ps1 @@ -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 diff --git a/tests/test_shared.py b/tests/test_shared.py index 926a0ce..196f58e 100644 --- a/tests/test_shared.py +++ b/tests/test_shared.py @@ -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. diff --git a/xair_api/__init__.py b/xair_api/__init__.py new file mode 100644 index 0000000..879ef6e --- /dev/null +++ b/xair_api/__init__.py @@ -0,0 +1,3 @@ +from .xair import request_remote_obj as connect + +_ALL__ = ["connect"] diff --git a/mair/bus.py b/xair_api/bus.py similarity index 83% rename from mair/bus.py rename to xair_api/bus.py index 3bad44f..d9db513 100644 --- a/mair/bus.py +++ b/xair_api/bus.py @@ -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, diff --git a/mair/config.py b/xair_api/config.py similarity index 86% rename from mair/config.py rename to xair_api/config.py index d1ca3eb..11d95a0 100644 --- a/mair/config.py +++ b/xair_api/config.py @@ -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"), diff --git a/mair/dca.py b/xair_api/dca.py similarity index 84% rename from mair/dca.py rename to xair_api/dca.py index 8ca2660..23551e9 100644 --- a/mair/dca.py +++ b/xair_api/dca.py @@ -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) diff --git a/xair_api/errors.py b/xair_api/errors.py new file mode 100644 index 0000000..eac42f6 --- /dev/null +++ b/xair_api/errors.py @@ -0,0 +1,4 @@ +class XAirRemoteError(Exception): + """Base error class for XAIR Remote.""" + + pass diff --git a/mair/fx.py b/xair_api/fx.py similarity index 81% rename from mair/fx.py rename to xair_api/fx.py index 12435b7..5da7589 100644 --- a/mair/fx.py +++ b/xair_api/fx.py @@ -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) diff --git a/mair/kinds.py b/xair_api/kinds.py similarity index 84% rename from mair/kinds.py rename to xair_api/kinds.py index eeb7669..3db7898 100644 --- a/mair/kinds.py +++ b/xair_api/kinds.py @@ -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 diff --git a/mair/lr.py b/xair_api/lr.py similarity index 81% rename from mair/lr.py rename to xair_api/lr.py index 49bf112..d85827a 100644 --- a/mair/lr.py +++ b/xair_api/lr.py @@ -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, diff --git a/mair/meta.py b/xair_api/meta.py similarity index 84% rename from mair/meta.py rename to xair_api/meta.py index 7d54f82..9cfd712 100644 --- a/mair/meta.py +++ b/xair_api/meta.py @@ -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) diff --git a/mair/rtn.py b/xair_api/rtn.py similarity index 89% rename from mair/rtn.py rename to xair_api/rtn.py index 2455a66..7d7ec90 100644 --- a/mair/rtn.py +++ b/xair_api/rtn.py @@ -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, diff --git a/mair/shared.py b/xair_api/shared.py similarity index 87% rename from mair/shared.py rename to xair_api/shared.py index 970603c..32d8256 100644 --- a/mair/shared.py +++ b/xair_api/shared.py @@ -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)) diff --git a/mair/strip.py b/xair_api/strip.py similarity index 84% rename from mair/strip.py rename to xair_api/strip.py index 64fa190..33f11b5 100644 --- a/mair/strip.py +++ b/xair_api/strip.py @@ -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, diff --git a/mair/util.py b/xair_api/util.py similarity index 98% rename from mair/util.py rename to xair_api/util.py index 4986a5d..cfc69a9 100644 --- a/mair/util.py +++ b/xair_api/util.py @@ -1,4 +1,4 @@ -from math import log, exp +from math import exp, log def lin_get(min, max, val): diff --git a/mair/mair.py b/xair_api/xair.py similarity index 69% rename from mair/mair.py rename to xair_api/xair.py index feb2389..dbb1d5b 100644 --- a/mair/mair.py +++ b/xair_api/xair.py @@ -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)