|  |  |  | #include "selfdrive/ui/soundd/sound.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <cmath>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QAudio>
 | 
					
						
							|  |  |  | #include <QAudioDeviceInfo>
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "cereal/messaging/messaging.h"
 | 
					
						
							|  |  |  | #include "common/util.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TODO: detect when we can't play sounds
 | 
					
						
							|  |  |  | // TODO: detect when we can't display the UI
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) {
 | 
					
						
							|  |  |  |   qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (auto &[alert, fn, loops] : sound_list) {
 | 
					
						
							|  |  |  |     QSoundEffect *s = new QSoundEffect(this);
 | 
					
						
							|  |  |  |     QObject::connect(s, &QSoundEffect::statusChanged, [=]() {
 | 
					
						
							|  |  |  |       assert(s->status() != QSoundEffect::Error);
 | 
					
						
							|  |  |  |     });
 | 
					
						
							|  |  |  |     s->setVolume(Hardware::MIN_VOLUME);
 | 
					
						
							|  |  |  |     s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn));
 | 
					
						
							|  |  |  |     sounds[alert] = {s, loops};
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QTimer *timer = new QTimer(this);
 | 
					
						
							|  |  |  |   QObject::connect(timer, &QTimer::timeout, this, &Sound::update);
 | 
					
						
							|  |  |  |   timer->start(1000 / UI_FREQ);
 | 
					
						
							|  |  |  | };
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Sound::update() {
 | 
					
						
							|  |  |  |   const bool started_prev = sm["deviceState"].getDeviceState().getStarted();
 | 
					
						
							|  |  |  |   sm.update(0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const bool started = sm["deviceState"].getDeviceState().getStarted();
 | 
					
						
							|  |  |  |   if (started && !started_prev) {
 | 
					
						
							|  |  |  |     started_frame = sm.frame;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // no sounds while offroad
 | 
					
						
							|  |  |  |   // also no sounds if nothing is alive in case thermald crashes while offroad
 | 
					
						
							|  |  |  |   const bool crashed = (sm.frame - std::max(sm.rcv_frame("deviceState"), sm.rcv_frame("controlsState"))) > 10*UI_FREQ;
 | 
					
						
							|  |  |  |   if (!started || crashed) {
 | 
					
						
							|  |  |  |     setAlert({});
 | 
					
						
							|  |  |  |     return;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // scale volume with speed
 | 
					
						
							|  |  |  |   if (sm.updated("carState")) {
 | 
					
						
							|  |  |  |     float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.0f);
 | 
					
						
							|  |  |  |     volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale);
 | 
					
						
							|  |  |  |     volume = util::map_val(volume, 0.f, 1.f, Hardware::MIN_VOLUME, Hardware::MAX_VOLUME);
 | 
					
						
							|  |  |  |     for (auto &[s, loops] : sounds) {
 | 
					
						
							|  |  |  |       s->setVolume(std::round(100 * volume) / 100);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setAlert(Alert::get(sm, started_frame));
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Sound::setAlert(const Alert &alert) {
 | 
					
						
							|  |  |  |   if (!current_alert.equal(alert)) {
 | 
					
						
							|  |  |  |     current_alert = alert;
 | 
					
						
							|  |  |  |     // stop sounds
 | 
					
						
							|  |  |  |     for (auto &[s, loops] : sounds) {
 | 
					
						
							|  |  |  |       // Only stop repeating sounds
 | 
					
						
							|  |  |  |       if (s->loopsRemaining() > 1 || s->loopsRemaining() == QSoundEffect::Infinite) {
 | 
					
						
							|  |  |  |         s->stop();
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // play sound
 | 
					
						
							|  |  |  |     if (alert.sound != AudibleAlert::NONE) {
 | 
					
						
							|  |  |  |       auto &[s, loops] = sounds[alert.sound];
 | 
					
						
							|  |  |  |       s->setLoopCount(loops);
 | 
					
						
							|  |  |  |       s->play();
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 |