xair-cli
Control Behringer X-Air and Midas MR mixers from the command line.
I wanted my mixer scriptable, so this talks OSC to the console directly instead of driving the X AIR Edit GUI. Faders, mutes, sends, scenes, and soundcheck routines all become shell commands you can commit to a repo.
$ xair discover
192.168.1.50 XR18 "Main Room"
$ xair --ip 192.168.1.50 --json health
{"status": "ok", "ip": "192.168.1.50", "name": "Main Room", "model": "XR18", "version": "1.18", "connect": "ok", "info": "ok"}
Features
- Faders, mutes, pans, EQ, dynamics, preamp gain, and sends
- Multi-channel targets: ranges (
1-4), lists (1,3,5), orall - Scene load, save, and list
- Mixer discovery over UDP broadcast
- Batch scripts from a file or stdin
- Three output modes: human-readable, JSON, and
key=value - Shell completion for bash, zsh, and fish
Supported mixers
- Behringer XR18, XR16, XR12
- Midas MR18
- Behringer X32
Installation
From source (repo root). This is the only path that gives you the build this README describes:
./setup.sh # creates ./.venv if needed, installs the package into it
./setup.sh --dev # same, plus the test/lint/build extras
source .venv/bin/activate
setup.sh installs into an already-activated virtualenv if there is one,
otherwise into ./.venv. It uses uv when available and pip when not, and
prints which environment and installer it used. Set PYTHON=/path/to/python3.x
to choose the interpreter for the virtualenv it creates.
Or manually:
python -m venv .venv
source .venv/bin/activate
pip install -e "./xair-cli"
Why not pip install dc-xair-cli right now. The package exists on PyPI,
but the published wheel labeled 2.4.3 was built from 2.4.1-era code: it
predates the negative-value parsing, the misplaced-flag guards, and the
--dry-run safety work this README documents. Worst of the differences:
xair --dry-run discover from that wheel puts a real UDP broadcast on the
wire, which this README promises never happens. It also omits the docs
command and ships without LICENSE and NOTICE. You can tell the builds apart:
the stale wheel prints 2.4.1 from xair --version; a source build prints
the current version. PyPI filenames are immutable, so the fix will arrive as
a release newer than 2.4.3; until one appears there, install from source. I
will remove this caveat when that release lands.
Quick start
# Discover mixers on the network
xair discover
# Show mixer info
xair --ip 192.168.1.50 info
# Control channels
xair --ip 192.168.1.50 ch 1 fader -6
xair --ip 192.168.1.50 ch 1-4 mute on
# Load a scene
xair --ip 192.168.1.50 scene load 1
See QUICKSTART.md for more examples.
Configuration
Create a config file to avoid passing --ip every time:
xair config init
Edit ~/.config/xair/config.toml:
[connection]
ip = "192.168.1.50"
model = "XR18"
Or set environment variables:
export XAIR_IP="192.168.1.50"
export XAIR_MODEL="XR18"
Commands
| Command | Description |
|---|---|
info |
Show mixer model, IP, and firmware |
discover |
Find mixers on the network |
health |
Run a read-only check of the connection |
status |
Show channel overview |
ch |
Control input channels |
bus |
Control output buses |
lr |
Control main L/R output |
dca |
Control DCA groups |
fx |
Control FX slots |
rtn |
Control FX returns |
scene |
Manage scenes (load/save/list) |
raw |
Send raw OSC commands |
meters |
Show channel levels |
batch |
Execute commands from file |
config |
Manage configuration |
completion |
Generate shell completions |
docs |
Create or update documentation templates |
xair health exits non-zero when the mixer cannot be reached, so it works as a
readiness check in scripts:
xair --json health --details # timings for connect and info
xair health --auto-discover # find the mixer first, then check it
Channel properties
| Property | Description | Range |
|---|---|---|
fader |
Level in dB | -90 to +10 |
mute |
Mute state | on/off/toggle |
name |
Channel name | string |
gain |
Preamp gain (channels only) | 0-60 dB |
phantom |
48V phantom power | on/off |
pan |
Pan position | -100 to +100 |
eq |
EQ enable | on/off |
gate |
Gate enable | on/off |
comp |
Compressor enable | on/off |
send.N |
Send level to bus N | -90 to +10 dB |
Not every channel type has every property. DCA groups expose mute, name,
and color only; use xair raw for anything a typed command does not cover.
Negative and relative values
A leading dash is a value, not an option. Absolute negatives work verbatim:
xair ch 1 fader -6 # Set fader to -6 dB
xair ch 1 pan -50 # Pan halfway left
xair ch 1 send.1 -10 # Set send to bus 1 to -10 dB
Use + or -- for changes relative to the current value:
xair ch 1 fader +3 # 3 dB louder than it is now
xair ch 1 fader --6 # 6 dB quieter than it is now (double dash)
xair bus 1-4 fader --3 # 3 dB quieter on every bus in the range
So -6 means "set to -6 dB" and --6 means "turn down by 6 dB".
Dry run
--dry-run prints the OSC address and value each command would send and exits
without contacting a mixer. It needs no IP and no network:
$ xair --dry-run ch 1 fader -6
Dry-run mode: nothing is sent to XR18 (no IP configured)
[dry-run] would query /xinfo
[dry-run] would query /ch/01/mix/fader
[dry-run] would send /ch/01/mix/fader 0.6
fader = -6.0 dB
Commands that would otherwise reach the network are previewed too, so
--dry-run never puts a packet on the wire:
$ xair --dry-run discover
DRY RUN: would broadcast discovery on port 10024
Preview lines go to stderr, so stdout carries only the command's own output:
under --json that is one JSON document per affected target, in dry-run and
live alike.
$ xair --json --dry-run ch 1-2 fader -6 2>/dev/null
{"fader": -6.0, "unit": "dB"}
{"fader": -6.0, "unit": "dB"}
Values read back in dry-run are placeholders; there is no mixer to ask.
Output formats
xair ch 1 fader # Human readable
xair --json ch 1 fader # JSON output
xair --plain ch 1 fader # key=value pairs
Shell completion
# Bash
xair completion bash >> ~/.bashrc
# Zsh
xair completion zsh >> ~/.zshrc
# Fish
xair completion fish > ~/.config/fish/completions/xair.fish
Repository layout
The Python package lives one level down, in xair-cli/, so the repository root
holds the workspace and the package directory holds the distribution:
. repository root (docs, setup.sh, CI workflows)
└── xair-cli/ the dc-xair-cli package root
├── pyproject.toml build config, entry point, pytest settings
├── src/xair/ CLI layer (Click commands, context, output)
├── lib/xair_api/ vendored OSC mixer API (see NOTICE)
└── tests/ pytest suite
Two consequences worth knowing before you clone:
- Test commands run from
xair-cli/, not the repository root../setup.shinstalls from there for you and can be run from anywhere. README.md,LICENSE, andNOTICEexist at the root and again inxair-cli/, kept byte-identical. The build backend only sees the inner copies and puts them in the published package, while GitHub renders the outer ones. Edit both.
Development
cd xair-cli
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest # test suite
ruff check . && ruff format . --check
mypy .
CI runs the same four commands plus python -m build on every pull request.
Documentation and policies
- CONTRIBUTING.md
- development setup, the checks CI runs, and how to open a pull request.
- SECURITY.md
- how to report a vulnerability privately, and what is in scope.
- QUICKSTART.md
- a longer worked example than the section above.
- xair-cli/CHANGELOG.md
- the release history.
- docs/
docs/archive/holds superseded planning notes, kept for reference and not maintained.
Non-goals
- No GUI. This is a command-line tool, and
xair rawis the escape hatch for anything the typed commands do not cover. - No mixer emulator. The test suite uses a mock mixer plus the
--dry-runtransport, so it proves argument parsing, dispatch, OSC address construction, and output formatting. It does not prove behavior against real hardware. - Shell completions are static. They do not reflect the connected mixer's state.
- Discovery relies on UDP broadcast, so it will not cross subnets.
Third-party code
xair-cli/lib/xair_api/ is a vendored and modified copy of
xair-api by Onyx and Iris,
used under the MIT License. See
NOTICE for
attribution and the copyright lines it preserves.
License
MIT. See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dc_xair_cli-2.4.4.tar.gz.
File metadata
- Download URL: dc_xair_cli-2.4.4.tar.gz
- Upload date:
- Size: 64.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
30a2c773c594449f32a273cf6e885b3e6c2614deaee2f5c34782880a7cf1a07f
|
|
| MD5 |
afb1a90392d7c834aa1c96842d4c3925
|
|
| BLAKE2b-256 |
98dd02866d7820d930f390eed66ed22ddef7419b361a7bd2a0aa8e1af250cac4
|
File details
Details for the file dc_xair_cli-2.4.4-py3-none-any.whl.
File metadata
- Download URL: dc_xair_cli-2.4.4-py3-none-any.whl
- Upload date:
- Size: 60.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c09b60183319edc08e3bf426e695c3682631aa384e83f91bb5f48e869a2c8947
|
|
| MD5 |
d9856e8e386bb791b38e2939a39b5787
|
|
| BLAKE2b-256 |
a5d3fe588b78dfd25976c157626237b748daf02216f3fd969f3c9d7f1fd08db3
|