From 2f3f1d76a56abcd52fcbf420ca1ee2d4540ede45 Mon Sep 17 00:00:00 2001 From: royjr Date: Mon, 7 Sep 2026 02:59:10 -0400 Subject: [PATCH] fade --- .../sunnypilot/selfdrive/ui/custom_audio.py | 18 +++++------------- .../selfdrive/ui/docs/custom_audio.md | 3 ++- openpilot/sunnypilot/selfdrive/ui/soundd.py | 13 +++++++------ 3 files changed, 14 insertions(+), 20 deletions(-) diff --git a/openpilot/sunnypilot/selfdrive/ui/custom_audio.py b/openpilot/sunnypilot/selfdrive/ui/custom_audio.py index 1654ded544..dcb354ddc2 100644 --- a/openpilot/sunnypilot/selfdrive/ui/custom_audio.py +++ b/openpilot/sunnypilot/selfdrive/ui/custom_audio.py @@ -11,7 +11,8 @@ AUDIO_DIR = Path("/data/media/0/custom_audio") SAMPLE_RATE = 48000 MAX_SECONDS = 300 GEAR_TIMEOUT = 0.5 -RESUME_FRAMES = int(0.15 * SAMPLE_RATE) +DUCK_FRAMES = int(0.05 * SAMPLE_RATE) +RESTORE_FRAMES = int(0.15 * SAMPLE_RATE) class GearTransition: @@ -54,7 +55,6 @@ class AudioPlayer: self.samples = np.empty(0, dtype=np.float32) self.position = 0 self.gain = 1.0 - self.paused = False def render(self, frames, alert_active): command = self.command @@ -63,23 +63,15 @@ class AudioPlayer: self.samples = command if command is not None else np.empty(0, dtype=np.float32) self.position = 0 self.gain = 1.0 - self.paused = False out = np.zeros(frames, dtype=np.float32) if self.position == len(self.samples): return out - if alert_active: - self.paused = True - self.gain = 0.0 - return out - if self.paused: - self.paused = False - self.gain = 0.0 n = min(frames, len(self.samples) - self.position) - gains = np.minimum(1, self.gain + np.arange(n) / RESUME_FRAMES) + step = -0.5 / DUCK_FRAMES if alert_active else 0.5 / RESTORE_FRAMES + gains = np.clip(self.gain + np.arange(n) * step, 0.5, 1.0) out[:n] = self.samples[self.position:self.position + n] * gains self.position += n - if n: - self.gain = float(gains[-1]) + self.gain = float(np.clip(self.gain + n * step, 0.5, 1.0)) return out diff --git a/openpilot/sunnypilot/selfdrive/ui/docs/custom_audio.md b/openpilot/sunnypilot/selfdrive/ui/docs/custom_audio.md index 5586019ad4..095fb789ca 100644 --- a/openpilot/sunnypilot/selfdrive/ui/docs/custom_audio.md +++ b/openpilot/sunnypilot/selfdrive/ui/docs/custom_audio.md @@ -7,7 +7,8 @@ Each gear change plays `.wav`: `park.wav`, `drive.wav`, `sport.wav`, `reve - Entering a gear starts its audio from the beginning at full volume, with no fade-in. - Changing gears cuts the previous audio without a fade-out. If the new file is missing or invalid, playback stays silent. - Returning to a previous gear restarts its audio from the beginning. Staying in the same gear does not replay it. -- Safety alerts pause custom audio immediately. After the alert, it resumes from its saved position with a **150 ms fade-in**. Audio triggered during an alert waits, then fades in. +- While any alert sounds, custom audio keeps playing and fades to **50% volume over 50 ms**. Afterward it fades back to full volume over **150 ms**. +- Alert volume is unchanged. Custom audio may be reduced further to prevent clipping when both sounds are loud. - Startup and recovery from unknown/invalid gear data or data gaps establish a silent baseline. No settings or dismiss control. Replace or remove files to change future playback. diff --git a/openpilot/sunnypilot/selfdrive/ui/soundd.py b/openpilot/sunnypilot/selfdrive/ui/soundd.py index e75d8f45e0..f2e936376a 100644 --- a/openpilot/sunnypilot/selfdrive/ui/soundd.py +++ b/openpilot/sunnypilot/selfdrive/ui/soundd.py @@ -1,7 +1,7 @@ import numpy as np from openpilot.cereal import messaging -from openpilot.selfdrive.ui.soundd import AudibleAlert, Soundd +from openpilot.selfdrive.ui.soundd import Soundd from openpilot.sunnypilot.selfdrive.ui.custom_audio import CustomAudio @@ -12,12 +12,13 @@ class SounddSP(Soundd): self.gear_sock = messaging.sub_sock("carState", conflate=False) def callback(self, data_out: np.ndarray, frames: int, time, status) -> None: - alert_active = self.current_alert != AudibleAlert.none super().callback(data_out, frames, time, status) - alert_active = alert_active or self.current_alert != AudibleAlert.none or bool(np.any(data_out[:frames, 0])) - custom_data = self.custom_audio.player.render(frames, alert_active) - if not alert_active: - data_out[:frames, 0] = custom_data + alert_data = data_out[:frames, 0] + alert_peak = float(np.max(np.abs(alert_data))) + custom_data = self.custom_audio.player.render(frames, alert_peak > 0) + custom_peak = float(np.max(np.abs(custom_data))) + gain = min(1.0, max(0.0, 1.0 - alert_peak) / max(custom_peak, 1e-9)) + alert_data += custom_data * gain def get_audible_alert(self, sm): super().get_audible_alert(sm)