diff --git a/selfdrive/clearpilot/sounds/ding.wav b/selfdrive/clearpilot/sounds/ding.wav new file mode 100644 index 0000000..374f2d4 Binary files /dev/null and b/selfdrive/clearpilot/sounds/ding.wav differ diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 69263b7..617671f 100755 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -93,6 +93,29 @@ class Soundd: self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) + # CLEARPILOT ding (plays independently of alerts; triggered by writers + # setting ClearpilotPlayDing="1" in /dev/shm/params). + self.ding_sound = None + self.ding_frame = 0 + self.ding_playing = False + self.ding_check_counter = 0 + self._load_ding() + + def _load_ding(self): + import sys + ding_path = BASEDIR + "/selfdrive/clearpilot/sounds/ding.wav" + try: + wavefile = wave.open(ding_path, 'r') + assert wavefile.getnchannels() == 1 + assert wavefile.getsampwidth() == 2 + assert wavefile.getframerate() == SAMPLE_RATE + length = wavefile.getnframes() + self.ding_sound = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2) + print(f"CLP soundd: ClearPilot ding loaded: {length} frames", file=sys.stderr, flush=True) + except Exception as e: + print(f"CLP soundd: failed to load ding sound: {e}", file=sys.stderr, flush=True) + self.ding_sound = None + def load_sounds(self): self.loaded_sounds: dict[int, np.ndarray] = {} @@ -137,7 +160,20 @@ class Soundd: written_frames += frames_to_write self.current_sound_frame += frames_to_write - return ret * self.current_volume + ret = ret * self.current_volume + + # CLEARPILOT: mix in ding (independent of alerts, always max volume) + if self.ding_playing and self.ding_sound is not None: + ding_remaining = len(self.ding_sound) - self.ding_frame + if ding_remaining > 0: + frames_to_write = min(ding_remaining, frames) + ret[:frames_to_write] += self.ding_sound[self.ding_frame:self.ding_frame + frames_to_write] * MAX_VOLUME + self.ding_frame += frames_to_write + else: + self.ding_playing = False + self.ding_frame = 0 + + return ret def callback(self, data_out: np.ndarray, frames: int, time, status) -> None: if status: @@ -205,6 +241,14 @@ class Soundd: if self.params_memory.get_bool("FrogPilotTogglesUpdated"): self.update_frogpilot_params() + # CLEARPILOT: poll ClearpilotPlayDing at ~2Hz; clear it on read. + self.ding_check_counter += 1 + if self.ding_check_counter % 10 == 0 and self.ding_sound is not None: + if self.params_memory.get("ClearpilotPlayDing") == b"1": + self.params_memory.put("ClearpilotPlayDing", "0") + self.ding_playing = True + self.ding_frame = 0 + def update_frogpilot_params(self): self.alert_volume_control = self.params.get_bool("AlertVolumeControl")