# unisonus-livekit WebRTC audio infrastructure for **Unisonus** — a choir/music streaming application. Provides a LiveKit server and a custom choir audio mixer agent that subscribes to participant audio streams, mixes them, and publishes the combined output back to the room. ## Architecture ``` Participants (up to 6) │ audio tracks ▼ choir-mixer agent ├── subscribes to all participant audio ├── normalises RMS per stream (target: -20 dBFS) ├── noise-gates signals below -40 dBFS ├── sums all streams ├── soft-limits via tanh (no hard clipping) └── publishes mixed audio track back to room │ ▼ All room participants receive the mix ``` ## Services | Service | Image | Ports | Description | |---------|-------|-------|-------------| | `livekit` | `livekit/livekit-server:latest` | `7880` (HTTP/WS), `7881` (RTC TCP), `50000-50100/udp` | LiveKit WebRTC media server | | `choir-mixer` | Built from `./choir-mixer/Dockerfile` | — | Python agent: subscribes, mixes, publishes | ## Audio Mixer Details **File:** `choir-mixer/main.py` + `choir-mixer/mixer.py` | Parameter | Value | |-----------|-------| | Sample rate | 48 kHz | | Channels | Mono (1) | | Frame duration | 20 ms (960 samples/frame) | | Max simultaneous streams | 6 | | Target loudness | -20 dBFS (RMS) | | Noise gate | -40 dBFS (silence below this threshold) | | Limiter | tanh soft-limit (smooth saturation, no hard clipping) | | Stale frame timeout | 60 ms (frames older than this are discarded from mix) | The mixer runs at ~50 Hz (every 20 ms). Each cycle it: 1. Collects the most recent frame from each active participant (discarding stale frames) 2. Normalises each stream to -20 dBFS 3. Noise-gates signals below -40 dBFS (suppresses background noise and keyboard clicks) 4. Sums all streams into one 5. Applies tanh soft limiting to prevent clipping 6. Publishes the result as a mono 48kHz audio track ## LiveKit Server Config **File:** `livekit.yaml` | Setting | Value | |---------|-------| | HTTP/WS port | 7880 | | RTC TCP port | 7881 | | RTC UDP range | 50000–50100 | | Node IP | 192.168.0.241 | | External IP | Disabled (LAN only) | | Room empty timeout | 300 s | | Max participants | 50 | | Log level | info | ## Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `LIVEKIT_API_KEY` | Yes | LiveKit API key (from `livekit.yaml` `keys:`) | | `LIVEKIT_API_SECRET` | Yes | LiveKit API secret | Copy `.env.example` to `.env` and fill in the values. The API key/secret pair must match what is configured in `livekit.yaml`. ## Running ```bash docker compose up -d ``` To check mixer logs: ```bash docker compose logs -f choir-mixer ``` ## Dependencies **Python** (choir-mixer): - `livekit` — LiveKit Agents SDK - `livekit-rtc` — RTC bindings - `numpy` — audio processing ## Deployment Runs on `192.168.0.241` (albert-MacBookPro Linux server). | Endpoint | URL | |----------|-----| | LiveKit signaling | `ws://192.168.0.241:7880` | | LiveKit RTC TCP | `192.168.0.241:7881` | | LiveKit RTC UDP | `192.168.0.241:50000-50100` |