diff --git a/cereal/log.capnp b/cereal/log.capnp index 6cf4781228aaa4..6630ffd1cbf96c 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -89,6 +89,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { stockAeb @52; stockLkas @98; lateralManeuver @99; + laneChangeStarted @100; ldw @53; carUnrecognized @54; invalidLkasSetting @55; @@ -771,13 +772,31 @@ struct SelfdriveState { alertStatus @5 :AlertStatus; alertSize @6 :AlertSize; alertType @7 :Text; - alertSound @8 :Car.CarControl.HUDControl.AudibleAlert; + alertSound @13 :AudibleAlert; alertHudVisual @12 :Car.CarControl.HUDControl.VisualAlert; # configurable driving settings experimentalMode @10 :Bool; personality @11 :LongitudinalPersonality; + enum AudibleAlert { + none @0; + + engage @1; + disengage @2; + refuse @3; + + warningSoft @4; + warningImmediate @5; + + prompt @6; + promptRepeat @7; + promptDistracted @8; + + confirmation @9; + preAlert @10; + } + enum OpenpilotState @0xdbe58b96d2d1ac61 { disabled @0; preEnabled @1; @@ -798,6 +817,10 @@ struct SelfdriveState { mid @2; full @3; } + + deprecated :group { + alertSound @8 :Car.CarControl.HUDControl.AudibleAlert; + } } struct ControlsState @0x97ff69c53601abf1 { @@ -918,7 +941,7 @@ struct ControlsState @0x97ff69c53601abf1 { alertStatus @38 :SelfdriveState.AlertStatus; alertSize @39 :SelfdriveState.AlertSize; alertType @44 :Text; - alertSound2 @56 :Car.CarControl.HUDControl.AudibleAlert; + alertSound2 @56 :SelfdriveState.AudibleAlert; engageable @41 :Bool; # can OP be engaged? state @31 :SelfdriveState.OpenpilotState; enabled @19 :Bool; @@ -2431,6 +2454,13 @@ struct SoundPressure @0xdc24138990726023 { } } +struct SoundDebug { + volume @0 :Float32; + ambientDb @1 :Float32; + rawDb @2 :Float32; + alert @3 :SelfdriveState.AudibleAlert; +} + struct AudioData { data @0 :Data; sampleRate @1 :UInt32; @@ -2515,6 +2545,7 @@ struct Event { # microphone data soundPressure @103 :SoundPressure; rawAudioData @147 :AudioData; + soundDebug @152 :SoundDebug; # systems stuff androidLog @20 :AndroidLogEntry; diff --git a/cereal/services.py b/cereal/services.py index c2d38d852db133..44219c5e53959b 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -75,6 +75,7 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "userBookmark": (True, 0., 1), "soundPressure": (True, 10., 10), "rawAudioData": (False, 20.), + "soundDebug": (False, 20.), "bookmarkButton": (True, 0., 1), "audioFeedback": (True, 0., 1), "roadEncodeData": (False, 20., None, QueueSize.BIG), diff --git a/common/params_keys.h b/common/params_keys.h index 7a96da0be792b9..ed64418875379a 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -37,6 +37,7 @@ inline static std::unordered_map keys = { {"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}}, {"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}}, {"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}}, + {"SingleToneSounds", {PERSISTENT, BOOL}}, {"AlphaLongitudinalEnabled", {PERSISTENT | DEVELOPMENT_ONLY, BOOL}}, {"ExperimentalMode", {PERSISTENT, BOOL}}, {"ExperimentalModeConfirmed", {PERSISTENT, BOOL}}, @@ -111,6 +112,7 @@ inline static std::unordered_map keys = { {"RecordFrontLock", {PERSISTENT, BOOL}}, // for the internal fleet {"SecOCKey", {PERSISTENT | DONT_LOG, STRING}}, {"ShowDebugInfo", {PERSISTENT, BOOL}}, + {"ShowSoundDebug", {PERSISTENT, BOOL}}, {"RouteCount", {PERSISTENT, INT, "0"}}, {"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"SshEnabled", {PERSISTENT, BOOL}}, diff --git a/selfdrive/assets/sounds/chime.wav b/selfdrive/assets/sounds/chime.wav new file mode 100644 index 00000000000000..a5472df0657bf4 --- /dev/null +++ b/selfdrive/assets/sounds/chime.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4cd499f0635cae6e09d427a9abac29478ba09c5d770f95cccf8a2d0b70ea7fc +size 83350 diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav index 7bfd97ad715e2e..ac298ede79bad2 100644 --- a/selfdrive/assets/sounds/disengage.wav +++ b/selfdrive/assets/sounds/disengage.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:42bd04a57b527c787a0555503e02a203f7d672c12d448769a3f41f17befbf013 -size 48044 +oid sha256:8adc04b7983e58e6fcfaf89a2d6e318b87b9eb923ed1e819d028d76d288fe6ca +size 83350 diff --git a/selfdrive/assets/sounds/dual_disengage.wav b/selfdrive/assets/sounds/dual_disengage.wav new file mode 100644 index 00000000000000..74a7ea52ef7a06 --- /dev/null +++ b/selfdrive/assets/sounds/dual_disengage.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4136ea734f28a8b7b87b65dbed3423ca1a0835dee35452aed5e9b93b8b59d23 +size 73026 diff --git a/selfdrive/assets/sounds/dual_engage.wav b/selfdrive/assets/sounds/dual_engage.wav new file mode 100644 index 00000000000000..b28eceff77aa1f --- /dev/null +++ b/selfdrive/assets/sounds/dual_engage.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb3b7ce07671df3df1877e508ac46866d1571a2feea2ad05ae84264df48f298 +size 73026 diff --git a/selfdrive/assets/sounds/engage.wav b/selfdrive/assets/sounds/engage.wav index 8633b5ac2d82b1..36a97efad22e8d 100644 --- a/selfdrive/assets/sounds/engage.wav +++ b/selfdrive/assets/sounds/engage.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b1e177499d9439367179cc57a6301b6162393972e3a136cc35c5fdac026bf10a -size 48044 +oid sha256:f4cf683258d75ec30d64d2c1c5644849d4fc99412330cd89aace71bf0b59c960 +size 83350 diff --git a/selfdrive/assets/sounds/pre_alert.wav b/selfdrive/assets/sounds/pre_alert.wav new file mode 100644 index 00000000000000..4443bf7d23425b --- /dev/null +++ b/selfdrive/assets/sounds/pre_alert.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c0af7f5fe57bb36ab96fae868e20feca763541c97a61b3a3a84a0e7fcb81163 +size 83350 diff --git a/selfdrive/assets/sounds/prompt.wav b/selfdrive/assets/sounds/prompt.wav index e482c85a629806..4c832a32245d48 100644 --- a/selfdrive/assets/sounds/prompt.wav +++ b/selfdrive/assets/sounds/prompt.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ad19268e4aaaeac8dd21f6b26c16a121e7b3f50bba867748e7226727643ae682 -size 144642 +oid sha256:e8df0608f9ab3636c8f41b7413375a261cabff3be6c95c60f05f1cfd0fc723a8 +size 73026 diff --git a/selfdrive/assets/sounds/prompt_distracted.wav b/selfdrive/assets/sounds/prompt_distracted.wav index 750d580f04827d..c11993f20c0442 100644 --- a/selfdrive/assets/sounds/prompt_distracted.wav +++ b/selfdrive/assets/sounds/prompt_distracted.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1810ad0418ac234f02dec005883c8f0e1c3e0e5ece7a3157803c5a66cb8e5adc -size 85662 +oid sha256:412ef25d2fb103c1ebd55c667313a5921493305fb4e1f4e1dafc08d3b95d86ab +size 73026 diff --git a/selfdrive/assets/sounds/refuse.wav b/selfdrive/assets/sounds/refuse.wav index 1e0c47697d89cb..719582f55c09e6 100644 --- a/selfdrive/assets/sounds/refuse.wav +++ b/selfdrive/assets/sounds/refuse.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4af81cbf1d96a42cc351878b015298aee82874b46baaf1a615ca91ec36c0ced6 -size 83228 +oid sha256:b5283aec51c3057bd6a4b872bd4ad59180bcca443b1c132aa9c062b9e1e17211 +size 83350 diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav index fcbfed79ed87d6..c0991f2af4111a 100644 --- a/selfdrive/assets/sounds/warning_immediate.wav +++ b/selfdrive/assets/sounds/warning_immediate.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a390831afca3bfc6ea3c2739b872ebf866e70df8ae30653f8587e5cd3993959 -size 68306 +oid sha256:c72cf1c8df57577efc9bf0f0a0840d9fb59cb734c3bb6e5513dbef0d9c831d52 +size 51560 diff --git a/selfdrive/assets/sounds/warning_soft.wav b/selfdrive/assets/sounds/warning_soft.wav index 7db30303d60f5d..7f0d17186cfca9 100644 --- a/selfdrive/assets/sounds/warning_soft.wav +++ b/selfdrive/assets/sounds/warning_soft.wav @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67e636d072703e6b1233a12344c0a6304fd43d64dbb31c66b71c2c8870a339c1 -size 153764 +oid sha256:4b1fb84224372518f5a79d243fdacfa39509d0e91cb161a77ccbd634724f530d +size 62700 diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 99e25571bd1136..c65bc1cdcfdba5 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -18,7 +18,7 @@ AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus VisualAlert = car.CarControl.HUDControl.VisualAlert -AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlert = log.SelfdriveState.AudibleAlert EventName = log.OnroadEvent.EventName @@ -117,7 +117,7 @@ def __init__(self, alert_size: log.SelfdriveState.AlertSize, priority: Priority, visual_alert: car.CarControl.HUDControl.VisualAlert, - audible_alert: car.CarControl.HUDControl.AudibleAlert, + audible_alert: log.SelfdriveState.AudibleAlert, duration: float, creation_delay: float = 0.): @@ -182,7 +182,7 @@ def __init__(self, alert_text_2: str): class EngagementAlert(Alert): - def __init__(self, audible_alert: car.CarControl.HUDControl.AudibleAlert): + def __init__(self, audible_alert: log.SelfdriveState.AudibleAlert): super().__init__("", "", AlertStatus.normal, AlertSize.none, Priority.MID, VisualAlert.none, @@ -517,7 +517,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), + Priority.LOW, VisualAlert.none, AudibleAlert.preAlert, .1), }, EventName.driverDistracted2: { @@ -612,6 +612,14 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), }, + EventName.laneChangeStarted: { + ET.WARNING: Alert( + "", + "", + AlertStatus.normal, AlertSize.none, + Priority.MID, VisualAlert.none, AudibleAlert.confirmation, .2), + }, + EventName.steerSaturated: { ET.WARNING: Alert( "Take Control", @@ -1037,7 +1045,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin "Pay Attention", "", AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), + Priority.LOW, VisualAlert.none, AudibleAlert.preAlert, 2), }, EventName.driverDistracted2: { ET.PERMANENT: Alert( diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index e376ea5c9c2c83..d2a93b9d15db2f 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -293,6 +293,9 @@ def update_events(self, CS): self.events.add(EventName.preLaneChangeRight) elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting, LaneChangeState.laneChangeFinishing): + # one-off event to prevent lanechange alert from being played repeatedly during lanechange + if EventName.laneChange not in self.events_prev: + self.events.add(EventName.laneChangeStarted) self.events.add(EventName.laneChange) for i, pandaState in enumerate(self.sm['pandaStates']): diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 2b17436943798e..3996acc497cef6 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -88,9 +88,17 @@ def __init__(self): initial_state=self._params.get_bool("ShowDebugInfo"), callback=self._on_enable_ui_debug, ) + + self._single_tone_toggle = toggle_item( + lambda: tr("Single Tone Sounds"), + description="", + initial_state=self._params.get_bool("SingleToneSounds"), + callback=self._on_single_tone_sounds, + ) self._on_enable_ui_debug(self._params.get_bool("ShowDebugInfo")) self._scroller = Scroller([ + self._single_tone_toggle, self._adb_toggle, self._ssh_toggle, self._ssh_keys, @@ -147,9 +155,13 @@ def _update_toggles(self): ("LateralManeuverMode", self._lat_maneuver_toggle), ("AlphaLongitudinalEnabled", self._alpha_long_toggle), ("ShowDebugInfo", self._ui_debug_toggle), + ("SingleToneSounds", self._single_tone_toggle), ): item.action_item.set_state(self._params.get_bool(key)) + def _on_single_tone_sounds(self, state: bool): + self._params.put_bool("SingleToneSounds", state, block=True) + def _on_enable_ui_debug(self, state: bool): self._params.put_bool("ShowDebugInfo", state, block=True) gui_app.set_show_touches(state) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 2d7dc0899ddda8..36fad0995aead2 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -82,11 +82,15 @@ def ssh_keys_callback(): self._alpha_long_toggle = BigToggle("alpha longitudinal", initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"), toggle_callback=self._on_alpha_long_enabled) + self._sound_debug_toggle = BigParamControl("show sound debug", "ShowSoundDebug") + self._single_tone_toggle = BigParamControl("single tone sounds", "SingleToneSounds") self._debug_mode_toggle = BigParamControl("ui debug mode", "ShowDebugInfo", toggle_callback=lambda checked: (gui_app.set_show_touches(checked), gui_app.set_show_fps(checked))) self._scroller.add_widgets([ + self._sound_debug_toggle, + self._single_tone_toggle, self._adb_toggle, self._ssh_toggle, self._ssh_keys_btn, diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 0246a0e5973b9d..068cca1047a56d 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -9,6 +9,7 @@ from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall +from openpilot.selfdrive.ui.mici.onroad.sound_debug import SoundDebug from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent from openpilot.system.ui.widgets.label import UnifiedLabel @@ -151,6 +152,7 @@ def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = Visio self._alert_renderer = AlertRenderer() self._driver_state_renderer = DriverStateRenderer() self._confidence_ball = ConfidenceBall() + self._sound_debug = SoundDebug() self._offroad_label = UnifiedLabel("start the car to\nuse openpilot", 54, FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, @@ -243,6 +245,8 @@ def _render(self, _): self._bookmark_icon.render(self.rect) + self._sound_debug.render(self._content_rect) + def _switch_stream_if_needed(self, sm): if sm['selfdriveState'].experimentalMode and WIDE_CAM in self.available_streams: v_ego = sm['carState'].vEgo diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index df5afe2e7c4a7f..8761f5df5eee32 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -1,5 +1,5 @@ import pyray as rl -from cereal import car, log, messaging +from cereal import log, messaging from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer @@ -104,8 +104,9 @@ def _publish_alert_sound(self, dm_state): if self._pm is None: return - AudibleAlert = car.CarControl.HUDControl.AudibleAlert + AudibleAlert = log.SelfdriveState.AudibleAlert ALERT_SOUNDS = { + 'one': AudibleAlert.preAlert, 'two': AudibleAlert.promptDistracted, 'three': AudibleAlert.warningImmediate, } diff --git a/selfdrive/ui/mici/onroad/sound_debug.py b/selfdrive/ui/mici/onroad/sound_debug.py new file mode 100644 index 00000000000000..f9457b3b1fe19b --- /dev/null +++ b/selfdrive/ui/mici/onroad/sound_debug.py @@ -0,0 +1,43 @@ +import pyray as rl +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.ui_state import ui_state + + +class SoundDebug(Widget): + def __init__(self): + super().__init__() + self._params = Params() + self._enabled = self._params.get_bool("ShowSoundDebug") + self._font = gui_app.font(FontWeight.MEDIUM) + + def _render(self, rect): + if gui_app.frame % 30 == 0: + self._enabled = self._params.get_bool("ShowSoundDebug") + if not self._enabled: + return + + sd = ui_state.sm["soundDebug"] + alert = str(sd.alert).split(".")[-1] + rows = [ + ("volume", f"{sd.volume * 100:.0f}%"), + ("ambient", f"{sd.ambientDb:.1f} dB"), + ("mic raw", f"{sd.rawDb:.1f} dB"), + ("alert", alert), + ] + fs, pad, gap, lh = 32, 14, 24, 40 + + label_w = max(measure_text_cached(self._font, lbl, fs).x for lbl, _ in rows) + value_w = max(measure_text_cached(self._font, val, fs).x for _, val in rows) + w = 2 * pad + label_w + gap + value_w + h = 2 * pad + lh * len(rows) + x0, y0 = rect.x + rect.width - w - 6, rect.y + 6 + + rl.draw_rectangle_rounded(rl.Rectangle(x0, y0, w, h), 0.12, 10, rl.Color(0, 0, 0, 210)) + for i, (label, value) in enumerate(rows): + y = y0 + pad + lh * i + rl.draw_text_ex(self._font, label, rl.Vector2(x0 + pad, y), fs, 0, rl.Color(170, 170, 170, 255)) + vx = x0 + w - pad - measure_text_cached(self._font, value, fs).x + rl.draw_text_ex(self._font, value, rl.Vector2(vx, y), fs, 0, rl.WHITE) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index a6f5d2fc27999b..29b7fc3c7db7a9 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -4,9 +4,10 @@ import wave -from cereal import car, messaging +from cereal import log, messaging from openpilot.common.basedir import BASEDIR from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.params import Params from openpilot.common.realtime import Ratekeeper from openpilot.common.utils import retry from openpilot.common.swaglog import cloudlog @@ -22,7 +23,7 @@ SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) -AMBIENT_DB = 24 # DB where MIN_VOLUME is applied +AMBIENT_DB = 22 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied VOLUME_BASE = 20 @@ -30,7 +31,9 @@ AMBIENT_DB = 30 VOLUME_BASE = 10 -AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlert = log.SelfdriveState.AudibleAlert + +DUAL_TONE_SOUNDS = {"engage.wav": "dual_engage.wav", "disengage.wav": "dual_disengage.wav"} sound_list: dict[int, tuple[str, int | None, float]] = { @@ -45,6 +48,9 @@ AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME), AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME), + + AudibleAlert.confirmation: ("chime.wav", 1, MAX_VOLUME), + AudibleAlert.preAlert: ("pre_alert.wav", 1, MAX_VOLUME), } if HARDWARE.get_device_type() == "tizi": sound_list.update({ @@ -64,6 +70,8 @@ def check_selfdrive_timeout_alert(sm): class Soundd: def __init__(self): + self.params = Params() + self.single_tone_sounds = self.params.get_bool("SingleToneSounds") self.load_sounds() self.current_alert = AudibleAlert.none @@ -74,15 +82,18 @@ def __init__(self): self.ramp_start_time = 0. self.selfdrive_timeout_alert = False + self.pending_stop = False self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) def load_sounds(self): - self.loaded_sounds: dict[int, np.ndarray] = {} + loaded_sounds: dict[int, np.ndarray] = {} # Load all sounds for sound in sound_list: filename, play_count, volume = sound_list[sound] + if not self.single_tone_sounds: + filename = DUAL_TONE_SOUNDS.get(filename, filename) with wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r') as wavefile: assert wavefile.getnchannels() == 1 @@ -90,7 +101,10 @@ def load_sounds(self): assert wavefile.getframerate() == SAMPLE_RATE length = wavefile.getnframes() - self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2) + loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2) + + # swap atomically: the audio callback reads this dict from another thread + self.loaded_sounds = loaded_sounds def get_sound_data(self, frames): # get "frames" worth of data from the current alert sound, looping when required @@ -112,6 +126,10 @@ def get_sound_data(self, frames): # get "frames" worth of data from the current self.current_sound_frame += frames_to_write current_sound_frame = self.current_sound_frame % len(sound_data) loops = self.current_sound_frame // len(sound_data) + if self.pending_stop and current_sound_frame == 0: + self.current_alert = AudibleAlert.none + self.pending_stop = False + break return ret * self.current_volume @@ -122,6 +140,14 @@ def callback(self, data_out: np.ndarray, frames: int, time, status) -> None: def update_alert(self, new_alert): current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame >= len(self.loaded_sounds[self.current_alert]) + if new_alert == AudibleAlert.none and self.current_alert != AudibleAlert.none and sound_list[self.current_alert][1] is None: + if current_alert_played_once: + self.pending_stop = True + else: + self.current_alert = AudibleAlert.none + self.current_sound_frame = 0 + return + self.pending_stop = False if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once): if new_alert == AudibleAlert.warningImmediate: self.ramp_start_volume = self.current_volume @@ -156,6 +182,7 @@ def soundd_thread(self): import sounddevice as sd sm = messaging.SubMaster(['selfdriveState', 'soundPressure']) + pm = messaging.PubMaster(['soundDebug']) with self.get_stream(sd) as stream: rk = Ratekeeper(20) @@ -172,12 +199,26 @@ def soundd_thread(self): self.get_audible_alert(sm) + # check once per second if the sound set changed + if rk.frame % 20 == 0: + single_tone_sounds = self.params.get_bool("SingleToneSounds") + if single_tone_sounds != self.single_tone_sounds: + self.single_tone_sounds = single_tone_sounds + self.load_sounds() + # Ramp up immediate warning sound over 4s if self.current_alert == AudibleAlert.warningImmediate: elapsed = time.monotonic() - self.ramp_start_time ramp_vol = float(np.interp(elapsed, [0, ALERT_RAMP_TIME], [self.ramp_start_volume, MAX_VOLUME])) self.current_volume = max(self.current_volume, ramp_vol) + msg = messaging.new_message('soundDebug', valid=True) + msg.soundDebug.volume = float(self.current_volume) + msg.soundDebug.ambientDb = float(self.spl_filter_weighted.x) + msg.soundDebug.rawDb = float(sm["soundPressure"].soundPressureWeightedDb) + msg.soundDebug.alert = self.current_alert + pm.send('soundDebug', msg) + rk.keep_time() assert stream.active diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index a9da8455ebdd24..a4aa053c188f5a 100644 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -1,11 +1,11 @@ -from cereal import car +from cereal import log from cereal import messaging from cereal.messaging import SubMaster, PubMaster from openpilot.selfdrive.ui.soundd import SELFDRIVE_STATE_TIMEOUT, check_selfdrive_timeout_alert import time -AudibleAlert = car.CarControl.HUDControl.AudibleAlert +AudibleAlert = log.SelfdriveState.AudibleAlert class TestSoundd: diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 6309642fd27436..f17e28e3d8ae65 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -58,6 +58,7 @@ def _initialize(self): "liveParameters", "testJoystick", "rawAudioData", + "soundDebug", ] )