Background
CamillaDSP's set_volume(level: float) takes a dB value. Snapcast's set_client_volume takes a percent integer. Currently the UI calls Snapcast for volume changes. This workstream re-wires volume changes to CamillaDSP and makes Snapcast volume read-only (kept at 100% / unmuted as a pass-through).
The dB conversion is 20 * log10(percent / 100). At 0%, the result is undefined — use Snapcast mute instead.
Must land before #38 (WS3 — CamillaDSP native Loudness filter). The Loudness filter is volume-aware and only functions correctly when CamillaDSP owns the volume signal.
Estimated size: ~600 LOC (UI, client, helpers, tests)
Changes
audera/ui/streamer/pages.py
Add helpers:
import math
def _camilladsp(host: str) -> CamillaDSPClient:
return CamillaDSPClient(host=host, port=1234)
def _percent_to_db(percent: int) -> float:
return 20.0 * math.log10(percent / 100.0)
Replace _build_volume_controls to call CamillaDSP for volume and Snapcast only for mute at 0%:
def _build_volume_controls(self, client: Player) -> None:
async def _on_volume_change(e) -> None:
percent = int(e.value)
if percent == 0:
await _snapserver(self._host).set_client_volume(client.id, 0, muted=True)
else:
await _snapserver(self._host).set_client_volume(client.id, 100, muted=False)
await _camilladsp(client.host).set_volume(_percent_to_db(percent))
ui.slider(min=0, max=100, value=client.volume, on_change=_on_volume_change)
The Snapcast volume is kept at 100% (unmuted) at all non-zero values so that CamillaDSP controls the actual gain. At 0%, Snapcast is muted to suppress audio while CamillaDSP volume may remain at any value.
Remove the existing _snapserver.set_client_volume call for non-mute paths. No Snapcast volume slider is shown — Snapcast volume is an implementation detail.
audera/clients/camilladsp.py
No changes — get_volume() and set_volume() already exist.
Tests
tests/ui/test_streamer.py
Add fixtures:
@pytest.fixture
def mock_camilladsp(monkeypatch):
calls = {}
async def _set_volume(self, level: float) -> None:
calls['set_volume'] = level
monkeypatch.setattr(CamillaDSPClient, 'set_volume', _set_volume)
return calls
@pytest.fixture
def mock_snapserver_volume(monkeypatch):
calls = {}
async def _set_client_volume(self, client_id, volume, muted=False):
calls['set_client_volume'] = (client_id, volume, muted)
monkeypatch.setattr(SnapserverClient, 'set_client_volume', _set_client_volume)
return calls
Add test cases:
test_volume_change_nonzero: slider at 50 → set_volume called with ≈ -6.02 dB; Snapcast called with (id, 100, False).
test_volume_change_zero: slider at 0 → Snapcast called with (id, 0, True); CamillaDSP set_volume not called.
Background
CamillaDSP's
set_volume(level: float)takes a dB value. Snapcast'sset_client_volumetakes a percent integer. Currently the UI calls Snapcast for volume changes. This workstream re-wires volume changes to CamillaDSP and makes Snapcast volume read-only (kept at 100% / unmuted as a pass-through).The dB conversion is
20 * log10(percent / 100). At 0%, the result is undefined — use Snapcast mute instead.Must land before #38 (WS3 — CamillaDSP native Loudness filter). The
Loudnessfilter is volume-aware and only functions correctly when CamillaDSP owns the volume signal.Estimated size: ~600 LOC (UI, client, helpers, tests)
Changes
audera/ui/streamer/pages.pyAdd helpers:
Replace
_build_volume_controlsto call CamillaDSP for volume and Snapcast only for mute at 0%:The Snapcast volume is kept at 100% (unmuted) at all non-zero values so that CamillaDSP controls the actual gain. At 0%, Snapcast is muted to suppress audio while CamillaDSP volume may remain at any value.
Remove the existing
_snapserver.set_client_volumecall for non-mute paths. No Snapcast volume slider is shown — Snapcast volume is an implementation detail.audera/clients/camilladsp.pyNo changes —
get_volume()andset_volume()already exist.Tests
tests/ui/test_streamer.pyAdd fixtures:
Add test cases:
test_volume_change_nonzero: slider at 50 →set_volumecalled with≈ -6.02dB; Snapcast called with(id, 100, False).test_volume_change_zero: slider at 0 → Snapcast called with(id, 0, True); CamillaDSPset_volumenot called.