- Tools to identify and configure speakers connected to your USB amps.
- Generator for ALSA configuration for stereo room-based playback
- Generator for Snapcast server configuration for multiroom streaming & matching clients
- Power manager to turn on and off amps via Relays
I built a house with a custom multiroom audio system: a Raspberry Pi 5 connected to three Wondom GAB8 8-channel USB amplifiers, driving 24 speaker channels throughout the house.
The problem? After running speaker cables to every room and connecting them to the amplifiers, I had no idea which speaker was connected to which amplifier channel. Walking around the house with a laptop, playing test tones, and manually noting down "amp2 channel 5 = kitchen left" for 24 speakers is tedious and error-prone.
What started as a simple speaker identification tool grew from there: I needed ALSA configuration to combine mono channels into stereo room pairs (sometimes across different amplifiers), Snapcast configuration for multiroom streaming with per-room Spotify and AirPlay endpoints, persistent USB device naming so configurations survive reboots, and automatic power management to switch off the amplifiers when idle.
This tooling handles all of that: interactive speaker identification with spoken announcements, automatic generation of ALSA and Snapcast configs, one-shot deployment, and relay-based power management.
While originally built for Wondom GAB8 amplifiers, the tool is device-agnostic and works with any multi-channel USB audio device that exposes individual channels via ALSA.1
- Python 3
- ALSA utilities (
aplay,speaker-test) espeak-ngfor TTS announcements during identification (sudo apt install espeak-ng)- Multi-channel USB audio amplifiers with per-channel ALSA routing pre-configured (
amp1_ch1throughamp*_ch8) - Snapcast server and client (
snapserver,snapclient) for multiroom streaming - Optional:
librespotfor Spotify Connect - Optional:
crelayfor automatic amplifier power management via USB relay
For AirPlay 2 support, shairport-sync must be compiled from source with AirPlay 2 enabled (not available in standard packages):
# Install dependencies
sudo apt install build-essential git autoconf automake libtool \
libpopt-dev libconfig-dev libasound2-dev avahi-daemon libavahi-client-dev \
libssl-dev libsoxr-dev libplist-dev libsodium-dev libavutil-dev \
libavcodec-dev libavformat-dev uuid-dev libgcrypt-dev xxd
# Clone and build
git clone https://github.com/mikebrady/shairport-sync.git
cd shairport-sync
autoreconf -fi
./configure --sysconfdir=/etc --with-alsa --with-soxr --with-avahi \
--with-ssl=openssl --with-metadata --with-airplay-2 --with-stdout
make
sudo make installBy default, USB audio devices get names based on enumeration order, which can change between reboots or when devices are reconnected. This project includes example udev rules to assign persistent names (amp1, amp2, amp3) based on USB port path.
The included devconfig/99-wondom-gab8.rules is an example for Wondom GAB8 amplifiers on a Raspberry Pi 5. You'll need to adapt it for your specific devices.
- Find your device's current ALSA ID and USB path:
# List current ALSA cards
cat /proc/asound/cards
# Get the USB path for a specific card (replace controlC2 with your card)
udevadm info -q all /dev/snd/controlC2 | grep -E "^E: (ID_PATH|ID_MODEL)="- Create udev rules matching your devices. Example structure:
# Match by device model/ID and USB path, assign persistent name
SUBSYSTEM=="sound", ATTR{id}=="YourDeviceID*", ENV{ID_PATH}=="your-usb-path", ATTR{id}="amp1"- Install and activate:
sudo cp devconfig/your-rules-file.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
# Verify the new names
cat /proc/asound/cardsImportant: The names are bound to USB ports, not to physical amplifier units. If you move an amplifier to a different USB port, you'll need to update the udev rules to match. Label your USB ports or cables to keep track.
python3 speaker_identify.pyThe tool will:
- Detect all configured amplifiers from
speaker_config.json - Play a TTS announcement on each channel (e.g., "Verstärker 1, Kanal 3")
- Prompt you for the room name, position (left/right), and zone assignments
- Save progress incrementally (can be resumed if interrupted)
Commands during identification:
- Enter a room name (e.g.,
living room,kitchen) - Press Enter or type
skip- Skip unused channels quit- Save progress and exit
CLI options:
--announce-rooms/-r- Announce existing room names instead of amplifier/channel--all/-a- Re-announce all channels, including those already mapped
python3 generate_alsa_config.py > asound.confReview the generated config, then install:
sudo cp asound.conf /etc/asound.confpython3 generate_snapserver_conf.py > snapserver.conf
sudo cp snapserver.conf /etc/snapserver.conf
sudo systemctl restart snapserverInstall the systemd template service for snapcast clients:
sudo cp services/[email protected] /etc/systemd/system/
sudo systemctl daemon-reloadEnable and start a client for each room:
# Enable clients for all configured rooms
for room in $(python3 -c "import json; print(' '.join(json.load(open('speaker_config.json'))['rooms'].keys()))"); do
sudo systemctl enable --now snapclient@room_${room}.service
done
# Or enable individual rooms manually
sudo systemctl enable --now snapclient@room_living_room.service
sudo systemctl enable --now snapclient@room_kitchen.serviceCheck client status:
systemctl status 'snapclient@*'The powermanager/ folder contains a service that automatically switches the amplifier on/off via a USB relay based on ALSA activity. This saves power when no audio is playing.
Requirements:
- USB relay controlled via
crelay(e.g., Conrad/Sainsmart USB relay board) - Install crelay: https://github.com/ondrej1024/crelay
How it works:
- Monitors
/proc/asound/cardX/pcm*/sub*/statusfor RUNNING state - Turns relay ON immediately when audio starts
- Turns relay OFF after 5 minutes (300s) of inactivity
- Polls every 50ms for responsive switching
Installation:
# Install the script
sudo cp powermanager/powermanager.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/powermanager.sh
# Edit the script to match your setup
sudo nano /usr/local/bin/powermanager.sh
# - CARDS: ALSA card numbers to monitor (check with: cat /proc/asound/cards)
# - RELAY_ON_CMD / RELAY_OFF_CMD: commands for your relay
# - IDLE_TIMEOUT: seconds before power off (default: 300)
# Install and enable the service
sudo cp powermanager/powermanager.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now powermanager.serviceCheck status and logs:
systemctl status powermanager
journalctl -u powermanager -faplay -D room_living_room test.wav
aplay -D room_kitchen test.wav
aplay -D all_rooms test.wav # Play to all speakersSpeaker mappings are stored in speaker_config.json:
{
"version": "2.0",
"amplifiers": {
"amp1": { "card": "amp1", "channels": 8 },
"amp2": { "card": "amp2", "channels": 8 }
},
"speakers": {
"living_room_left": { "amplifier": "amp1", "channel": 1, "volume": 100, "latency": 0 },
"living_room_right": { "amplifier": "amp1", "channel": 2, "volume": 100, "latency": 0 },
"kitchen_left": { "amplifier": "amp2", "channel": 3, "volume": 100, "latency": 0 },
"kitchen_right": { "amplifier": "amp2", "channel": 4, "volume": 100, "latency": 0 }
},
"rooms": {
"living_room": {
"name": "Living Room",
"left": "living_room_left",
"right": "living_room_right",
"zones": ["eg", "main"]
},
"kitchen": {
"name": "Kitchen",
"left": "kitchen_left",
"right": "kitchen_right",
"zones": ["eg"]
}
},
"zones": {
"eg": { "name": "Erdgeschoss" },
"main": { "name": "Main Rooms" },
"alle": { "name": "Überall", "include_all": true }
},
"snapcast": {
"server": "localhost",
"streams": {
"spotify": { "type": "librespot", "name": "Spotify", "bitrate": 320 },
"airplay": { "type": "airplay", "name": "AirPlay", "port": 7000 },
"default": { "type": "pipe", "path": "/tmp/snapfifo", "sampleformat": "48000:16:2" }
},
"stream_targets": {
"spotify": { "zones": ["alle"] },
"airplay": { "zones": ["eg"] }
}
}
}| Device | Description |
|---|---|
amp1, amp2, ... |
Base amplifier PCM (hw access) |
room_<name> |
Stereo output for a specific room |
all_rooms |
Combined stereo output to all configured speakers |
The tool handles stereo pairs split across different amplifiers. For example, if your kitchen left speaker is on amp2 and right speaker is on amp3, the generator creates a multi-device ALSA configuration that combines them into a single stereo room_kitchen device.
Rooms can belong to multiple zones (tag-based, not hierarchical). Zones are used by Snapcast to group rooms for streaming - they are not managed by ALSA. The special zone alle with include_all: true automatically includes all rooms.
Footnotes
-
This project was developed with the assistance of Claude, Anthropic's AI assistant, using Claude Code. ↩