openhome

openhome Sound — speaker service & music app (design + tracker)

Whole-home audio for openhome: add Wi-Fi and Bluetooth speakers, group them into zones,

build a surround / calibrated system, and play music (Spotify first). This doc is the

living tracker — architecture, decisions, and the build checklist.

Live app: /music · Service: services/hub/src/audio.ts (Phase-0 in the hub;
graduates to its own services/speaker process in Phase 1).

Goals

single-room convenience.

with per-speaker latency so a room sounds right.

Architecture (maps onto the Pod Mesh stack — see /docs/pod-mesh)

LayerReal backendStatus
Multi-room syncSnapcast (one client per speaker)🔌 modelled, not wired
Source bridgeMusic Assistant (Spotify/AirPlay/Cast/DLNA)🔌 modelled
SpotifyOAuth + Web API (browse/control) + librespot (Connect playback)✅ OAuth + Web API · ⬜ librespot
BluetoothBlueZ A2DP sink🔌 modelled
Calibrationmic sweep → per-speaker delay/level🚧 stubbed (assigns delays)

Everything currently runs as a mock-but-real-shaped model in audio.ts (same philosophy as

home.ts) so the app is fully clickable before the hardware/daemons are attached.

Data model (audio.ts)

volume, muted, zone, role, latencyMs, calibrated.

HTTP API (served by the hub)

remove-speaker · create-zone · add-to-zone · remove-from-zone · dissolve-zone ·

set-role · calibrate · spotify-connect · spotify-play · play-in-zone.

Build checklist

Done (v0 — app + model)

Next (v1 — make it real)

(mock-home stand-in for room speakers; no desktop app; Premium)

Open questions


Room calibration & spatial audio (implemented)

Each room can be tuned into a time-aligned, level-matched spatial soundstage —

the technique behind Trueplay/Audyssey/Dirac, mapped onto our Snapcast layer

(services/hub/src/calibration.ts).

  1. Time-align — sound from the farthest speaker takes longest to reach the

seat, so every closer speaker is delayed by (d_far − d) / c, where

c = 331.3·√(1 + T/273.15) m/s (temperature-corrected speed of sound). The

delay is applied as each Snapcast client's latency.

  1. Level-match — closer speakers are louder (inverse-square), so each gets a

trim of 20·log10(d / d_far) dB (attenuate-only, capped at −9 dB; sub gets a

small boost allowance). Applied as Snapcast client volume.

  1. Spatialize — a role→channel matrix upmix turns stereo into

L / R / center / surround / sub / virtual-height (a passive matrix decode:

center = ½(L+R), surround = ½(L−R) with a Haas delay, sub = lowpass, height =

highpass differential). Roles auto-suggest from speaker count (2→L/R … 6→5.1).

Profiles persist per room (.data/room-calibration.json) and annotate each

speaker in /audio/state with {role, delayMs, trimDb}. The Calibrate

wizard on /speakers captures per-speaker distance + room temperature, computes

the profile, and shows the resulting delays/trims before applying.

Boundary: the numbers are real and correct, and are pushed to Snapcast — but

the audible spatial result needs the Snapcast server + one client per speaker

running on the hub (see Run on your Pi). A future auto path

replaces manual distances with a mic sweep (log-chirp → cross-correlate the

impulse for per-speaker delay), Trueplay-style.

*Refs: speaker time-alignment (speed-of-sound delay) and Snapcast per-client

latency/volume — see Calculator Academy, Trinnov, and the Snapcast docs.*