BILLO Wireless Audio Hub — README

BILLO Wireless Audio Hub

Open-source tooling to turn any mic, any phone, or any DJ gear into one stream → BILLO, reachable wherever Billy is.

Use it now (always-on service is installed)

Both servers start automatically at login and restart if they crash.

What Open this
Listen to the live hub (MacBook mic) http://100.77.111.113:8123/ (Tailscale, from anywhere) or http://<mac-wifi-ip>:8123/
Phone as a mic https://100.77.111.113:8200/?id=phone1 (Tailscale) or https://<mac-wifi-ip>:8200/?id=phone1 (same Wi-Fi)
Health check ./billo-audio.sh status (prints the exact URLs) · ./billo-audio.sh doctor (also checks parked hardware)

Phone steps: join the same Wi-Fi as the Mac (or turn on the Tailscale app) → open the phone URL → tap through the “not private / certificate” warning once (it’s this Mac’s own certificate) → Start Mic → allow the microphone. Give each phone its own ?id= name. Each press of Start makes a new file in phone-mic/recordings/ (iPhone saves .m4a, Android .webm). Hotel/venue Wi-Fi often blocks phone-to-laptop traffic — if the page won’t load, use the Tailscale URL. After a reboot the hub takes about a minute to start hearing the mic.

Components

Path What it does Status
hub.py Capture + MIX one or more macOS audio devices → record WAV and/or serve a live MP3 stream over HTTP ✅ verified (incl. multi-input mixing)
phone-mic/ Turn old phones into wireless mics (browser → server over WiFi/HTTPS) ✅ server verified
sdr-mic.sh Receive analog UHF wireless mics with an RTL-SDR dongle ⏳ ready (needs dongle)
billo-audio-hub.plist + phone-mic.plist launchd always-on services (hub :8123 + phone-mic :8200) ✅ installed + running
~/roadmics/ Headless recorder + RØDE Wireless GO setup (physical mics → interface) ✅ built

One command to run it all

./billo-audio.sh start                                    # hub + phone-mic (default mic)
./billo-audio.sh start "BlackHole 2ch" "MacBook Pro Microphone"   # MIX multiple sources
./billo-audio.sh status                                   # health of both
./billo-audio.sh stop                                     # stop everything

Quick start

List devices

python3 hub.py list

Record one or more devices (mixed into one file)

python3 hub.py record "MacBook Pro Microphone" --secs 30        # 30s WAV
python3 hub.py record "MacBook Pro Microphone" "BlackHole 2ch" --secs 30   # MIX two sources

Live stream — MIX everything into ONE stream

python3 hub.py stream "MacBook Pro Microphone" --port 8123
python3 hub.py stream "MacBook Pro Microphone" "BlackHole 2ch" --port 8123  # two mics, one stream
python3 hub.py stream recordings/foo.wav --port 8123              # loop-stream a recorded file (no hardware)
python3 hub.py record recordings/a.wav recordings/b.wav --secs 5  # MIX two recorded files
# Browser/player: http://<this-mac-ip>:8123/
# Over Tailscale: http://100.77.111.113:8123/   (any BILLO machine or Billy's phone)
# Raw stream:     http://<ip>:8123/stream

Phone as a wireless mic — realtime (WebRTC / VDO.Ninja)

Zero install: open https://vdo.ninja on the phone (“Add your camera/mic”) and on this Mac, play the phone’s view link out to BlackHole 2ch → the hub picks it up.

Self-hosted copy (static site, ~500 MB clone — use Wi-Fi):

./vdo-ninja-setup.sh install   # once (git clone of steveseguin/vdo.ninja)
./vdo-ninja-setup.sh start     # serves https://<mac-ip>:8443/

Peers still meet through VDO.Ninja’s public handshake server, so internet is required.

Phone as a wireless mic — simple recorder (no install)

Already running as a service on :8200 (see “Use it now”). Manual run:

cd phone-mic && python3 server.py 8200 --https
# On each phone: open https://<mac-ip>:8200/?id=phone1  (trust the cert once)
# Audio lands in phone-mic/recordings/<id>_<time>.webm (Android) or .m4a (iPhone)

Bluetooth mic (HFP) is narrowband ~8 kHz mono, one device at a time, and macOS doesn’t expose a clean BT-mic input. Prefer WiFi (above) or WebRTC. Android has a BT fallback via WO Mic (https://wolicheng.com/womic); iOS has no good BT-mic path. Use BT only as a last resort for a single low-fi mic.

Analog UHF mic over the air (RTL-SDR)

# rtl-sdr software is already installed; just plug in the dongle
./sdr-mic.sh 570.125             # tune to the mic's frequency in MHz

Without the dongle the script stops with a plain “PARKED: plug in the RTL-SDR dongle” message.

Run the hub always-on (installed)

./install-service.sh             # (re)install + start hub AND phone-mic as launchd agents
./install-service.sh uninstall   # stop + remove both (back to manual ./billo-audio.sh start)

Logs: .run/service-hub.log, .run/service-phone-mic.log. To stream different inputs always-on, edit the device names in billo-audio-hub.plist and re-run ./install-service.sh.

The universal bus (BlackHole — realtime, no extra hardware)

BlackHole (already installed) is the virtual mixer. Route ANY audio into it and the hub captures it as ONE live stream — this is how phones (via a player / VDO.Ninja), SDR output, a DAW, DJ gear, or any app’s audio all converge.

./route-to-blackhole.sh recordings/foo.wav &        # loop a file into the bus
python3 hub.py stream "BlackHole 2ch" --port 8123   # capture the bus -> one stream

Proven: sox -> BlackHole -> hub -> stream (tone measured at mean -6.5 dB).

Route into BILLO

Honest limits (know before you buy)