Files
StarPilot/openpilot/starpilot/audio/alert_volume.py
T
firestar5683 fd4ca85d61 Audio
Provide alert playback and volume control with a native audio callback that does not depend on the Python GIL.

Co-authored-by: Daniel Koepping <elkoled@gmail.com>
2026-10-14 20:02:39 -05:00

72 lines
2.2 KiB
Python

"""Validated saved volume choices for the existing stock audible alerts."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
AUTO = 101
MAX_RAW_BYTES = 16
# The first two warnings retain an audible floor. The immediate warning still
# ramps to full volume in soundd, including the selfdrive timeout warning.
VOLUMES = (
("WarningImmediateVolume", "Immediate Warning", 25),
("WarningSoftVolume", "Soft Warning", 25),
("RefuseVolume", "Engagement Refused", 0),
("PromptDistractedVolume", "Distracted Driver", 0),
("EngageVolume", "Engagement Chime", 0),
("DisengageVolume", "Disengagement Alert", 0),
("PromptVolume", "General Prompt", 0),
)
SPECS = {key: (label, minimum) for key, label, minimum in VOLUMES}
@dataclass(frozen=True)
class SavedVolume:
raw: bytes | None
value: int | None
readable: bool = True
@property
def valid(self) -> bool:
return self.readable and self.value is not None
def parse_volume(key: str, raw: bytes | None) -> int | None:
if key not in SPECS:
raise KeyError(key)
if raw is None:
return AUTO
if not raw or len(raw) > MAX_RAW_BYTES or not raw.isascii() or not raw.isdigit():
return None
value = int(raw)
minimum = SPECS[key][1]
return value if value == AUTO or minimum <= value <= 100 else None
def read_volume(params: Any, key: str) -> SavedVolume:
if key not in SPECS:
raise KeyError(key)
try:
with Path(params.get_param_path(key)).open("rb") as file:
raw = file.read(MAX_RAW_BYTES + 1)
except FileNotFoundError:
raw = None
except (OSError, ValueError):
return SavedVolume(None, None, False)
if raw is not None and len(raw) > MAX_RAW_BYTES:
return SavedVolume(raw, None, False)
return SavedVolume(raw, parse_volume(key, raw))
def effective_volume(key: str, saved: int | None, ambient: float, *, immediate_ramp: float | None = None) -> float:
"""Return gain without changing the stock Auto path or immediate-warning ramp."""
if key not in SPECS:
raise KeyError(key)
if saved is None or saved == AUTO:
return ambient
selected = saved / 100.0
return max(selected, immediate_ramp) if immediate_ramp is not None else selected