mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
fd4ca85d61
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>
72 lines
2.2 KiB
Python
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
|