Network remote and audio bridge for RME ADI-2 series converters (ADI-2 DAC, ADI-2 Pro / Pro EX, ADI-2/4 Pro SE / Pro EX). Runs on a Raspberry Pi connected to the unit by USB.
- Web interface: all output, input and device settings, parametric EQ with a draggable curve, bass/treble, loudness, setups, the unit's 20 EQ presets (edit, store, load).
- API: WebSocket and HTTP, used by the web interface, a Stream Deck plugin and other clients. See docs/API.md. Reference client: clients/adi2pi-client.js.
- Volume safety: volume limit per output, rise-speed limit, linked volumes, start volume (louder outputs are turned down when the unit is switched on).
- Volume from the sender: the volume slider of AirPlay and Spotify Connect senders sets the ADI-2's volume; the stream plays at full level.
- Scenes, actions, sleep timer: named sets of values; actions apply them on events (Bluetooth device connects, playback starts or stops, time of day, unit switched on); sleep timer with fade-out and mute.
- Backup and restore of the unit's settings, EQ presets, setup names and ADI-2-Pi's settings.
- Optional access key for the API and the web interface.
- MQTT with Home Assistant discovery.
- Bluetooth (A2DP), both directions on the Pi's built-in adapter: phones play into the ADI-2; Bluetooth headphones/speakers receive Line Out or Phones 3/4 after its DSP. Codecs: LDAC, aptX HD, aptX, SBC-XQ, SBC.
- Network receivers: AirPlay (optionally AirPlay 2), UPnP/DLNA, Squeezelite, Snapcast; Spotify Connect and Roon Bridge as install options.
- Headphone EQ Generator at
/eq/: fits a parametric EQ for the ADI-2 or TotalMix FX Room EQ to oratory1990's measurements and writes it to an output. See docs/eq-generator.md. - Shutdown with the ADI-2 (optional): shuts the Pi down when the ADI-2's USB connection disappears, or when its 12 V Trigger Out switches off (wired to GPIO3; Pi 4/3 also start again).
- Works offline. Outgoing connections made by ADI-2-Pi itself: the optional headphone data update, the optional update check, and the MQTT server when MQTT is switched on.
- Raspberry Pi with Raspberry Pi OS Bookworm or Trixie, Lite or Desktop, 64-bit recommended. Tested on a Raspberry Pi 4. The trigger wake-up does not work on a Pi 5.
- ADI-2 connected by USB, MIDI Control = ON (Pro models: SETUP – Options – Device Mode; DAC: Remap Keys / Diag).
- For Bluetooth and streaming: the ADI-2 in USB stereo mode (default).
git clone https://github.com/shells-dw/adi2-pi.git
cd adi2-pi
sudo ./install.shor download a release archive, unpack it and run sudo ./install.sh in the folder.
The installer:
- installs Node.js 24 LTS from NodeSource's apt repository (
deb.nodesource.com; 64-bit systems). On 32-bit systems, with--system-node, or when the repository can't be reached, Raspberry Pi OS's Node.js is used (Trixie: 20, Bookworm: 18) - installs the other packages from the Raspberry Pi OS repositories (avahi, ALSA utilities, gpiod, i2c-tools; with audio: PipeWire, WirePlumber, BlueZ, Python D-Bus bindings; with streaming: the receivers)
- creates the system user
adi2piand the servicesadi2-pi(bridge) andadi2-pi-audio(a dedicated PipeWire instance for the ADI-2 and Bluetooth) - copies the program to
/opt/adi2-pi; settings are kept in/var/lib/adi2-pi - announces the bridge via mDNS (
_adi2._tcp,_http._tcp) - looks for a Waveshare UPS HAT on I²C and, if one answers, keeps I²C switched on (see UPS HAT)
- prints the address, e.g.
http://raspberrypi.local
Running install.sh again updates the installation and keeps the settings. Options can be added
in a later run.
| Option | Effect |
|---|---|
--port N |
HTTP/WebSocket port. Default 80; if 80 is in use, the installer asks for another port (-y: first free port from 8080). |
--no-bluetooth |
Remote control only: no audio service, no Bluetooth, no streaming. |
--no-streaming |
No network receivers. |
--with-airplay2 |
Builds shairport-sync with AirPlay 2 and nqptp from source (github.com/mikebrady), about 10 minutes on a Pi 4. An existing AirPlay 2 build is kept, with or without the option; the installer says when GitHub has newer sources. |
--rebuild-airplay2 |
Builds AirPlay 2 again from the current sources on GitHub. |
--with-spotify |
Spotify Connect: adds the raspotify apt repository and installs librespot. |
--with-roon |
Roon Bridge: runs Roon's installer. |
--keep-pipewire-midi |
Leaves the MIDI bridge of the desktop session's PipeWire enabled (see MIDI port). |
--journal-ram |
Keeps the system journal in RAM only. |
--with-ups |
Switches I²C on for a UPS HAT even when the ADI-2 trigger is set to GPIO3 (and keeps I²C on if no HAT answers). |
--no-ups |
Doesn't look for a UPS HAT. |
--system-node |
Node.js from Raspberry Pi OS instead of NodeSource; removes NodeSource's repository if present. Without the option, a later run switches to Node.js 24 again. |
-y, --yes |
No questions. |
Uninstall: sudo /opt/adi2-pi/uninstall.sh (--purge also removes settings and the service user).
| Tab | Content |
|---|---|
| Overview | All outputs: volume, mute, dim, quick levels, headphone profile, source, DSP switches |
| Line Out, Phones 1/2, Phones 3/4 | Level, EQ, bass/treble, loudness, settings of the output |
| Input | Analog input (Pro models) |
| EQ Presets | The unit's 20 EQ presets: editor, apply to an output, store, load |
| Device | Device settings, setups |
| Pi Playback | Title and artist of what plays, sources played by the Pi, sample rate, switching outputs to USB |
| Bluetooth | Pairing phones, pairing and routing headphones/speakers |
| Streaming | Network receivers on/off, server addresses |
| State | Device and output status overview |
| Scenes & Actions | Sleep timer, headphone profiles, scenes, actions |
| Headphone EQ | Headphone EQ Generator |
| System | Bridge name, language, volume limits, quick levels, start volume, volume from the sender, shutdown with the ADI-2, UPS HAT, updates, backup/restore, access key, MQTT, Debug tab switch |
| Debug | Shown when enabled under System (per browser): changes sent to the unit with their origin, parameters not in the schema, MIDI monitor, connection log, parameter table, status recorder |
Controls: volume knobs react to dragging (Shift = fine), scroll wheel and arrow keys; double-click mutes. EQ curve points are dragged for frequency and gain, the scroll wheel changes Q, double-click resets the gain.
Parameters the connected unit does not report (e.g. Phones 1/2 Mute on the ADI-2 Pro EX) are not shown.
Language: System → Language, per browser; without a choice the page follows the browser's language. English and German are included; other languages are one JSON file each, see web/lang/README.md. The bridge's own messages and the Headphone EQ Generator are English.
- SysEx over the unit's USB-MIDI port (
/dev/snd/midiC*D0), see Protocol. - On connect, the bridge requests all settings, setup names and the 20 EQ presets. Changes made on the unit arrive as they happen.
- The unit does not echo values received over MIDI. The bridge applies a change to its model, broadcasts it, and reads the unit back 1.5 s after the last change. A full read runs every 30 s.
- The unit sends a status message about once per second. If none arrives for 4 s, the unit is treated as not answering (MIDI Control off); changes are refused until it answers again.
- Only one program can open the MIDI port at a time.
On USB, the host sets the ADI-2's sample rate. With the audio service, the rate follows the
playing source or is fixed on the Pi Playback tab (PipeWire clock.force-rate). Without the
audio service (--no-bluetooth), the Device tab sets it by opening the playback device at the
selected rate (aplay).
- Phones → ADI-2: Make visible opens a 2-minute pairing window; pairing is refused outside it. Paired devices reconnect automatically and are trusted for audio profiles (A2DP, AVRCP) only. Playback reaches every output whose Source is Auto or USB.
- ADI-2 → headphones/speakers: Search, then Pair. Source options:
- Line Out or Phones 3/4, after its DSP: while a Bluetooth output is connected, the output's Loopback is set to "Post FX −6 dB" (USB record 1/2 carries the output after EQ, bass/treble and crossfeed, before volume). The previous Loopback value is restored on disconnect.
- Analog input, or off.
- Level (default +6 dB) is applied on the Pi and compensates the −6 dB of the Loopback.
- Codecs to headphones: LDAC, aptX HD, aptX, SBC-XQ, SBC. From phones: aptX HD, aptX, SBC-XQ, SBC (no AAC in Raspberry Pi OS's PipeWire). Rate: 44.1 or 48 kHz.
- The Pi 4's Bluetooth shares the antenna with 2.4 GHz Wi-Fi; use Ethernet or 5 GHz Wi-Fi for LDAC.
- The installer sets the Bluetooth device class to audio (
Class = 0x200414in/etc/bluetooth/main.conf, backup kept) so that phones list the Pi, and configures desktop sessions on the Pi to leave Bluetooth audio and the ADI-2 toadi2-pi-audio.
| Receiver | Clients | Package |
|---|---|---|
| AirPlay | iOS, macOS, AirPlay apps | shairport-sync (Raspberry Pi OS); AirPlay 2 with --with-airplay2 |
| UPnP / DLNA renderer | UPnP control points (e.g. BubbleUPnP, Linn Kazoo) | gmediarender |
| Squeezelite | Lyrion Music Server | squeezelite |
| Snapcast client | Snapcast server | snapclient |
| Spotify Connect | Spotify apps | librespot from raspotify, --with-spotify |
| Roon Bridge | Roon ("System Output" of the Pi) | Roon's installer, --with-roon |
Receivers are started by the bridge as child processes under the adi2pi user, use the bridge
name, and play into adi2-pi-audio through its PulseAudio socket or the ALSA device adi2pi.
The packages' own systemd services are masked (uninstall.sh unmasks them). Squeezelite and
Snapcast find their server via mDNS unless an address is set. Roon Bridge runs as its own
service; only its status is shown.
Applies to audio played by the Pi (Bluetooth inputs, network receivers); settings inside the ADI-2 are not affected.
- Several sources: newest (default) – the most recently started source is heard, the others are muted until it stops; Switch to this selects another one. Mix plays all.
- Sample rate: Follow what plays (default) or a fixed rate from 44.1 to 192 kHz; with a fixed rate, PipeWire resamples all sources.
- USB while the Pi plays (Line Out, Phones 3/4; default on): when a source starts, the output's Source is set to USB 1/2; 10 s after the last source stops, the previous Source is restored. An output whose Source is changed manually during playback is not restored. The previous value is stored in the settings and survives a restart.
- A connected Snapcast client counts as playing while connected.
System → Shut down with the ADI-2. The Pi shuts down after the ADI-2 has been off for a set time (10 s, 30 s, 1 min, 5 min). The ADI-2 is detected as off in one of two ways (Detect standby by):
| USB connection | 12 V trigger on a GPIO | |
|---|---|---|
| Off when | the ADI-2's USB MIDI port disappears or the unit stops answering (standby, power off, cable unplugged, MIDI Control off) | Trigger Out is off |
| Wiring | none | optocoupler, see below |
| Pi starts again with the ADI-2 | no | Pi 4 and 3, trigger on GPIO3 only |
In both cases the Pi only shuts down after it has seen the ADI-2 on: a Pi started while the ADI-2 is off stays on. A running countdown can be cancelled on the System tab.
Trigger input selects the GPIO: GPIO3 (pin 5, default) or GPIO4, 17, 22, 23, 24, 25, 27. Only GPIO3 also starts a halted Pi. With I²C switched on (a UPS HAT), GPIO3 is SCL and can't be used: choose another GPIO, which uses the Pi's internal pull-up; the Pi then shuts down with the ADI-2 but doesn't start with it.
The ADI-2's Trigger Out (3.5 mm mono jack; tip = supply voltage, 9–16 V; ring and sleeve =
ground) is on while the unit is on. Connected to GPIO3 through an optocoupler, the bridge shuts
the Pi down after the trigger has been off for the set time. A halted Raspberry Pi 4 or 3 boots when GPIO3 is pulled low, so switching the ADI-2 on
starts the Pi (bootloader defaults WAKE_ON_GPIO=1, POWER_OFF_ON_HALT=0; not on the Pi 5).
ADI-2 Trigger Out Raspberry Pi
tip ──[ 2.2 kΩ ]──►|── LED ┐ PC817 ┌── collector ── GPIO3 (pin 5)
sleeve ──────────────────────┘ └── emitter ── GND (pin 6)
(1N4148 in reverse across the LED)
Alternatively a PC817 optocoupler module with 12 V input: IN+ to tip, IN− to sleeve, OUT to GPIO3, GND to GND, output VCC to 3.3 V (pin 1; not 5 V).
Power switching (all models, including the Pi 5; untested). For models that cannot be woken through GPIO3, a 12 V delay-off timer relay module switches the Pi's supply in addition to the optocoupler connection above:
ADI-2 Trigger Out ──┬── optocoupler ── GPIO3 / GND (shutdown signal, as above)
└── delay-off relay module (12 V)
contacts in the Pi's 5 V supply line
- Trigger on: the relay closes, the Pi boots on power-up.
- Trigger off: ADI-2-Pi shuts the Pi down after the set delay; the relay opens after its own off-delay. The relay's off-delay must exceed ADI-2-Pi's delay plus the shutdown time (e.g. 10 s + 30 s → 60 s).
- The Trigger Out supplies at most 150 mA for the relay module and the optocoupler together.
- The relay contacts go into the 5 V line between power supply and Pi and must carry the Pi's supply current (Pi 4: 3 A, Pi 5: 5 A).
Behaviour:
- A shutdown happens only after the trigger has been seen on since the bridge started: a Pi booted while the ADI-2 is in standby stays on.
- Power loss affects both devices at once; the trigger does not cover it.
- With Auto Standby enabled on the ADI-2, the Pi shuts down with it.
- GPIO3 is also I²C SCL; it is unavailable while I²C is enabled (UPS HAT or another HAT).
- GPIO is read with
gpioget(libgpiod 1.x and 2.x); the shutdown usessystemctl poweroff, permitted foradi2piby/etc/polkit-1/rules.d/50-adi2-pi.rules.
Supported: Waveshare UPS HAT and UPS HAT (B) (INA219 at 0x42, two cells), UPS HAT (C) and (D) (INA219 at 0x43, one cell), UPS HAT (E) (controller at 0x2D). The installer switches I²C on for a moment, looks at these addresses and keeps I²C on when a HAT answers. If the ADI-2 trigger is set to GPIO3, it asks first (I²C uses GPIO2/3).
System → UPS HAT shows the power source, battery level, voltage and current; the (E) also input voltage, remaining capacity and time to empty/full. On the INA219 models the level is estimated from the battery voltage (6.0–8.4 V or 3.0–4.2 V, as in Waveshare's scripts); a negative current means the battery supplies the Pi.
Shut down on battery (on by default): when the HAT runs on battery, the Pi shuts down after the set time (10 s … 10 min) unless power returns, or earlier when the battery reaches the set level (5–50 %, default 10 %). This works next to Shut down with the ADI-2.
Starting again: the UPS HAT (E) is told to switch its output off at the end of a power-off on
battery (/usr/lib/systemd/system-shutdown/adi2-pi-ups-off) and starts the Pi when power
returns. The other models keep the halted Pi powered from the battery; it starts again when power
is switched off and on, or with the HAT's switch.
System → Updates. Check for updates daily (off by default) asks GitHub for the latest release
of ADI-2-Pi once a day; Check now asks once. A newer version is shown on the System tab with a
link to its release page, and with MQTT as a Home Assistant update entity. Nothing is downloaded
or installed. To update, download the release from
github.com/shells-dw/adi2-pi/releases, unpack
it and run sudo ./install.sh with the options used before; settings are kept.
System → Volume from the sender. AirPlay and Spotify Connect play at full level; the position of
the sender's volume slider sets the volume of the chosen outputs within a range (default
−60 … −10 dB). The volume limits apply; mute on the sender mutes the outputs. One way only: the
unit's knob doesn't move the slider on the phone. Spotify Connect needs a librespot version with
--volume-ctrl fixed (the Streaming tab shows when it can't). UPnP, Squeezelite, Snapcast and
Bluetooth keep their own volume.
System → Start volume: when the ADI-2 is switched on or reconnects, outputs louder than their level are turned down to it.
System → Quick levels: up to four volume buttons per output (dB), shown under the knob on the Overview and the output pages. The volume limits apply.
Tab Scenes & Actions → Headphone profiles. A profile holds values of one output – EQ (on/off,
preset, bands, Dual EQ), bass/treble, loudness, crossfeed, optionally the output settings (Ref
Level, filters) – and optionally a volume limit. It is taken from an output and can be applied to
any output: the values are written to that output, the limit becomes the output's limit. The
profile applied last is shown on the output's card, in the API (settings.activeProfile) and as
an MQTT select; it is cleared when one of the profile's values is changed another way. Values the
output doesn't have (crossfeed on Line Out) are skipped. Apps and the Stream Deck plugin use the
same list (GET /api/profiles, POST /api/profile).
Tab Scenes & Actions.
- Scene: set the unit as wanted, tick what the scene holds (volume, mute, dim, source, EQ, EQ preset, bass/treble, loudness, crossfeed per output) and save. Applying it changes only these values. Scenes can be applied from the page, the API, MQTT and actions.
- Action: applies a scene when the ADI-2 is switched on, a Bluetooth device connects or disconnects, a source starts or stops playing on the Pi, the Pi has played nothing for a set number of minutes (once per quiet period), the AES or SPDIF input gets or loses its signal (Pro models; carrier lock, not whether audio plays), or at a time of day (Pi local time). A time action can have an end time with a second scene, e.g. 22:00 → Night, 07:00 → Day.
- Sleep timer: 15 … 90 minutes or any time up to 24 h. The chosen outputs fade down by 40 dB over the fade time, are muted, and get their volume back, so unmuting plays as before.
Pi Playback → Track: title, artist, album and play state of what the Pi plays – from AirPlay
(shairport-sync's metadata pipe, with cover art), Spotify Connect (librespot's events; librespot
0.4 reports no titles) and Bluetooth (AVRCP). UPnP, Squeezelite, Snapcast and Roon report
nothing. Also GET /api/nowplaying, the nowplaying WebSocket message and an MQTT sensor.
System → Backup: a JSON file with every setting the unit reports, its 20 EQ presets, the setup names and ADI-2-Pi's settings (without access key and MQTT password). The content of the unit's 9 setups is not readable over MIDI and not included. Restore takes a file and the parts to restore; unit settings only go back to the same model.
System → Access key. With a key set, the web page asks for it once per browser; apps and the
Stream Deck plugin send it as X-ADI2PI-Key header or ?key= (see docs/API.md).
Programs on the Pi itself don't need it. The connection stays plain HTTP: the key keeps others on
the network out, it doesn't encrypt.
Needed for: changes only lets anyone on the network see the page and the state (for a wall
tablet); every change asks for the key. Without it, nothing is served except /api/info.
System → MQTT: server, port, user name, password, topic. With Home Assistant discovery, the outputs' volume, mute, dim, source, EQ, EQ preset, bass/treble, loudness and crossfeed, the setups, the scenes and the sleep timer appear in Home Assistant by themselves (MQTT integration). Plain MQTT without TLS, for a server in the local network. Topics: docs/API.md.
The PipeWire instance of a Raspberry Pi OS desktop session opens all ALSA MIDI ports, which
blocks the ADI-2's port. The installer disables WirePlumber's ALSA MIDI monitor for desktop
sessions; --keep-pipewire-midi skips this.
The web interface loads no external resources. With MQTT switched on, ADI-2-Pi connects to the
MQTT server that is set. With the update check switched on (off by default), it asks
api.github.com once a day for the latest release; Check now asks once. The Headphone EQ Generator's Check for updated
headphone data contacts api.github.com and raw.githubusercontent.com (AutoEq repository)
and stores new or changed files in /var/lib/adi2-pi/eq; Back to the shipped data deletes
them. The installer uses the package repositories, NodeSource's repository (deb.nodesource.com,
without --system-node) and, for --with-* options, GitHub, the raspotify repository or Roon's
download server.
/var/lib/adi2-pi/settings.json (written when a bridge setting changes; holds scenes, actions,
the access key's hash and the MQTT password), downloaded headphone
data in /var/lib/adi2-pi/eq, the audio service's state in /var/lib/adi2-pi/audio-state,
the AirPlay metadata pipe /var/lib/adi2-pi/shairport-sync-metadata, and with --with-roon
Roon Bridge's ALSA configuration in /var/lib/adi2-pi/roon-home.
Unit settings are not stored on the Pi.
journalctl -u adi2-pi -f # bridge log
journalctl -u adi2-pi-audio -n 50 # audio service
cat /proc/asound/cards # ADI-2 present?
sudo -u adi2pi XDG_RUNTIME_DIR=/run/adi2-pi-audio wpctl status # PipeWire graph
bluetoothctl show # Bluetooth adapter| Message | Cause / action |
|---|---|
no ADI-2 found |
USB connection; try another port. |
does not answer / stopped answering |
MIDI Control is off: SETUP – Options – Device Mode – MIDI Control = ON. |
busy |
Another program has the MIDI port open (see MIDI port). |
| A setting changes unexpectedly | Debug tab → Sent to the unit lists every change sent by ADI-2-Pi and its origin. |
no Bluetooth adapter found |
Bluetooth disabled: check dtoverlay=disable-bt in /boot/firmware/config.txt, run sudo rfkill unblock bluetooth. |
the audio service … is not running |
sudo systemctl restart adi2-pi-audio, check its log. |
| Bluetooth headphones connected but silent | Bluetooth source is off, or the selected output is muted before its DSP. |
Trigger: GPIO3 is in use |
I²C is on (UPS HAT or another HAT): choose another Trigger input GPIO. |
UPS HAT: no UPS HAT found / I2C is off |
sudo i2cdetect -y 1 should list 2d, 42 or 43; run the installer again (--with-ups). |
| Page asks for an access key | A key is set (System → Access key). Forgotten: remove "access" from /var/lib/adi2-pi/settings.json and sudo systemctl restart adi2-pi. |
MQTT: wrong user name or password / not authorised |
Check the user on the MQTT server (Home Assistant: Mosquitto add-on, a Home Assistant user). |
RME "MIDI-Protocol for ADI-2 DAC, ADI-2 Pro and ADI-2/4 Pro SE" v0.2, extended with behaviour observed on an ADI-2 Pro EX:
- Device IDs 0x71 (DAC), 0x72 (Pro, Pro EX), 0x73 (2/4 Pro SE; the 2/4 Pro EX is expected on the same ID, as the Pro EX kept the Pro's).
- EQ preset store: data on addresses 13/14, then the flags word as a separate message at address 13 index 2 (the unit sends it at index 1 and ignores it there on receive). B/T Enable (13.20) and Load B/T (13.27) must not be part of a store: the unit switches MIDI Control off.
- Device parameter 16 (sample rate, read-only): index into 44.1 … 768 kHz.
- Device parameter 62 (not in v0.2): Load Volume with Setup (0 = disabled, 1 = with Setup).
- The ADI-2 Pro EX does not report Phones 1/2 Mute, Dim, Width, Lock Volume, Source, Mono, DA Filter, De-Emphasis, Loopback, DC Protection, or input Bass/Treble.
- ADI-2/4 Pro EX (untested): its manual (v2.0) lists values the 2/4 Pro SE doesn't have and v0.2
doesn't number – input Ref Level −5 … −35 dBu (digital gain), RIAA 0 dB, DC Filter ADC,
Bal. Impedance 94 k / 47 k, Phones 3/4 Source ADAT 3/4, Remap Keys RIAA Mode + / − and RIAA
Mono B. Values the bridge has no name for are shown as
#<number>; Debug → Settings ADI-2-Pi can’t name yet lists them and the parameters the bridge doesn't know.
No npm dependencies, no build step. Node.js 18 or later; the installer sets up Node.js 24.
npm run mock # bridge with a simulated ADI-2, Bluetooth, audio and receivers on http://localhost:8080
npm test # unit, API and integration tests (Node.js 20.10 or later)Integration tests run against a fake BlueZ on a private D-Bus (python3-dbus, dbus-daemon), a private PipeWire/WirePlumber instance (pipewire, wireplumber, pipewire-pulse) and real receivers (gmediarender, snapserver/snapclient); they are skipped when those programs are missing.
bin/adi2-pi.js entry point, command line options
lib/protocol.js SysEx encoding and decoding
lib/schema.json parameter definitions (addresses, ranges, value names)
lib/catalog.js parameter names ↔ addresses
lib/device.js unit model: identification, sync, sets, limits, linking, watchdog
lib/transport-alsa.js ALSA raw MIDI device, hot-plug
lib/server.js, ws.js HTTP and WebSocket API, static files
lib/settings.js bridge settings
lib/audio.js PipeWire routing, source arbitration, sample rate, Loopback
lib/autosource.js output Source switching while the Pi plays
lib/bluetooth.js Bluetooth control via lib/bt-helper.py (BlueZ D-Bus, pairing agent)
lib/receivers.js network receivers: processes, status
lib/trigger.js shutdown with the ADI-2: USB connection or trigger GPIO
lib/ups.js Waveshare UPS HAT: readings over I²C, shutdown on battery
lib/automation.js scenes, actions, sleep timer, start volume
lib/streamvol.js sender volume (AirPlay, Spotify Connect) → ADI-2 volume; bin/adi2-pi-hook.js posts it
lib/backup.js backup file, restore
lib/mqtt.js, mqtt-bridge.js MQTT client, entities, Home Assistant discovery
lib/updates.js update check (GitHub releases)
lib/nowplaying.js now playing: AirPlay metadata pipe, librespot events, Bluetooth AVRCP
lib/usb-audio.js sample rate via the USB playback device (without audio service)
lib/eq-data.js headphone data: bundled files, update on request
lib/mock-adi.js simulated unit; lib/bt-mock.js simulated Bluetooth, audio, receivers
clients/ reference API client
audio/ audio service start script, PipeWire/WirePlumber configuration
systemd/ service units, mDNS service definition
web/ web interface; web/lang/ languages; web/eq/ Headphone EQ Generator and its data
tools/ i18n-keys.js: lists the page's translatable texts
test/ tests
GPL-3.0-or-later, see LICENSE. Third-party content: NOTICE.md.
RME, ADI-2 and TotalMix are trademarks of RME Intelligent Audio Solutions / Audio AG. This project is not affiliated with RME.

