Skip to content

Commit

Permalink
Move delta-based volume change support to the backend
Browse files Browse the repository at this point in the history
Add api skip function to the frontend to help avoid race conditions
  • Loading branch information
SteveMicroNova committed Jan 28, 2025
1 parent 7149815 commit 577617f
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 21 deletions.
10 changes: 8 additions & 2 deletions amplipi/ctrl.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@ def set_zone(self, zid, update: models.ZoneUpdate, force_update: bool = False, i
mute, update_mutes = utils.updated_val(update.mute, zone.mute)
vol, update_vol = utils.updated_val(update.vol, zone.vol)
vol_f, update_vol_f = utils.updated_val(update.vol_f, zone.vol_f)
vol_delta_f = update.vol_delta_f
vol_min, update_vol_min = utils.updated_val(update.vol_min, zone.vol_min)
vol_max, update_vol_max = utils.updated_val(update.vol_max, zone.vol_max)
disabled, _ = utils.updated_val(update.disabled, zone.disabled)
Expand Down Expand Up @@ -847,13 +848,18 @@ def set_vol():
""" Update the zone's volume. Could be triggered by a change in
vol, vol_f, vol_min, or vol_max.
"""
# 'vol' (in dB) takes precedence over vol_f
# Order of operations: vol (db) > vol_delta > vol (float)
if update.vol_f is not None and update.vol is None:
vol_db = utils.vol_float_to_db(vol_f, zone.vol_min, zone.vol_max)
vol_f_new = vol_f
elif update.vol_delta_f is not None and update.vol is None:
applied_delta = utils.clamp((vol_delta_f + zone.vol_f), 0, 1)
vol_db = utils.vol_float_to_db(applied_delta, zone.vol_min, zone.vol_max)
vol_f_new = applied_delta
else:
vol_db = utils.clamp(vol, zone.vol_min, zone.vol_max)
vol_f_new = utils.vol_db_to_float(vol_db, zone.vol_min, zone.vol_max)

if self._rt.update_zone_vol(idx, vol_db):
zone.vol = vol_db
zone.vol_f = vol_f_new
Expand All @@ -863,7 +869,7 @@ def set_vol():
# To avoid potential unwanted loud output:
# If muting, mute before setting volumes
# If un-muting, set desired volume first
update_volumes = update_vol or update_vol_f or update_vol_min or update_vol_max
update_volumes = update_vol or update_vol_f or update_vol_min or update_vol_max or vol_delta_f is not None
if force_update or (update_mutes and update_volumes):
if mute:
set_mute()
Expand Down
3 changes: 3 additions & 0 deletions amplipi/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ class fields(SimpleNamespace):
Volume = Field(ge=MIN_VOL_DB, le=MAX_VOL_DB, description='Output volume in dB')
VolumeF = Field(ge=MIN_VOL_F, le=MAX_VOL_F,
description='Output volume as a floating-point scalar from 0.0 to 1.0 representing MIN_VOL_DB to MAX_VOL_DB')
VolumeDeltaF = Field(ge=(MAX_VOL_F * -1), le=MAX_VOL_F,
description='Output volume as a floating-point scalar from -1.0 to 1.0 representing the distance between the current and goal volume')
VolumeMin = Field(ge=MIN_VOL_DB, le=MAX_VOL_DB, description='Min output volume in dB')
VolumeMax = Field(ge=MIN_VOL_DB, le=MAX_VOL_DB, description='Max output volume in dB')
GroupMute = Field(description='Set to true if output is all zones muted')
Expand Down Expand Up @@ -356,6 +358,7 @@ class ZoneUpdate(BaseUpdate):
mute: Optional[bool] = fields.Mute
vol: Optional[int] = fields.Volume
vol_f: Optional[float] = fields.VolumeF
vol_delta_f: Optional[float] = fields.VolumeDeltaF
vol_min: Optional[int] = fields.VolumeMin
vol_max: Optional[int] = fields.VolumeMax
disabled: Optional[bool] = fields.Disabled
Expand Down
3 changes: 3 additions & 0 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const useStatusStore = create((set, get) => ({
skipUpdate: false,
loaded: false, // using this instead of (status === null) because it fixes the re-rendering issue
disconnected: true,
skipNextUpdate: () => {
set({ skipUpdate: true });
},
setZonesVol: (vol, zones, sourceId) => {
set(
produce((s) => {
Expand Down
47 changes: 28 additions & 19 deletions web/src/components/CardVolumeSlider/CardVolumeSlider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,42 +32,49 @@ export const applyPlayerVol = (vol, zones, sourceId, apply) => {
}
};

// Used to handle delta volume changes
let cumulativeDelta = 0;
let sendingPacketCount = 0;

// main volume slider on player and volume slider on player card

const CardVolumeSlider = ({ sourceId }) => {
const zones = useStatusStore((s) => s.status.zones);
const setZonesVol = useStatusStore((s) => s.setZonesVol);
const setZonesMute = useStatusStore((s) => s.setZonesMute);
const skipNextUpdate = useStatusStore((s) => s.skipNextUpdate); // needed to ensure that polling doesn't cause the delta volume to be made inacurrate during long slides

const value = getPlayerVol(sourceId, zones);

const setValue = (vol) => {
setZonesVol(vol, zones, sourceId);
setZonesMute(false, zones, sourceId);
};

const setPlayerVolRaw = (vol) =>
applyPlayerVol(vol, zones, sourceId, (zone_id, new_vol) => {
function setPlayerVol(vol, val) {
cumulativeDelta += vol - val;

if(sendingPacketCount <= 0){
sendingPacketCount += 1;
fetch(`/api/zones/${zone_id}`, {

const delta = cumulativeDelta;

fetch("/api/zones", {
method: "PATCH",
headers: {
"Content-type": "application/json",
},
body: JSON.stringify({ vol_f: new_vol, mute: false }),
body: JSON.stringify({
zones: getSourceZones(sourceId, zones).map((z) => z.id),
update: { vol_delta_f: cumulativeDelta, mute: false },
}),
}).then(() => {
// used to just set cumulativeDelta to 0, that would skip all accumulated delta from fetch start to backend response time
cumulativeDelta -= delta;
sendingPacketCount -= 1;
});
});

const setPlayerVol = (vol, force = false) => {
if (sendingPacketCount <= 0 || force) {
setPlayerVolRaw(vol);
}
};

const value = getPlayerVol(sourceId, zones);

const setValue = (vol) => {
setZonesVol(vol, zones, sourceId);
setZonesMute(false, zones, sourceId);
};

const mute = getSourceZones(sourceId, zones)
.map((z) => z.mute)
.reduce((a, b) => a && b, true);
Expand All @@ -93,9 +100,11 @@ const CardVolumeSlider = ({ sourceId }) => {
vol={value}
mute={mute}
setMute={setMute}
setVol={(val, force = false) => {
setPlayerVol(val, force);
setVol={(val, force) => {
let old_val = value; // Cannot use the const as it can change mid-fetch
setPlayerVol(val, old_val);
setValue(val);
skipNextUpdate();
}}
disabled={getSourceZones(sourceId, zones) == 0}
/>
Expand Down

0 comments on commit 577617f

Please sign in to comment.