|  |  |  | #include <sys/resource.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <map>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QApplication>
 | 
					
						
							|  |  |  | #include <QString>
 | 
					
						
							|  |  |  | #include <QSoundEffect>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "cereal/messaging/messaging.h"
 | 
					
						
							|  |  |  | #include "selfdrive/common/util.h"
 | 
					
						
							|  |  |  | #include "selfdrive/hardware/hw.h"
 | 
					
						
							|  |  |  | #include "selfdrive/ui/ui.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // TODO: detect when we can't play sounds
 | 
					
						
							|  |  |  | // TODO: detect when we can't display the UI
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Sound : public QObject {
 | 
					
						
							|  |  |  | public:
 | 
					
						
							|  |  |  |   explicit Sound(QObject *parent = 0) {
 | 
					
						
							|  |  |  |     // TODO: merge again and add EQ in the amp config
 | 
					
						
							|  |  |  |     const QString sound_asset_path = Hardware::TICI() ? "../assets/sounds_tici/" : "../assets/sounds/";
 | 
					
						
							|  |  |  |     std::tuple<AudibleAlert, QString, bool> sound_list[] = {
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_DISENGAGE, sound_asset_path + "disengaged.wav", false},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_ENGAGE, sound_asset_path + "engaged.wav", false},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_WARNING1, sound_asset_path + "warning_1.wav", false},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_WARNING2, sound_asset_path + "warning_2.wav", false},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_WARNING2_REPEAT, sound_asset_path + "warning_2.wav", true},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_WARNING_REPEAT, sound_asset_path + "warning_repeat.wav", true},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_ERROR, sound_asset_path + "error.wav", false},
 | 
					
						
							|  |  |  |       {AudibleAlert::CHIME_PROMPT, sound_asset_path + "error.wav", false}
 | 
					
						
							|  |  |  |     };
 | 
					
						
							|  |  |  |     for (auto &[alert, fn, loops] : sound_list) {
 | 
					
						
							|  |  |  |       QSoundEffect *s = new QSoundEffect(this);
 | 
					
						
							|  |  |  |       QObject::connect(s, &QSoundEffect::statusChanged, this, &Sound::checkStatus);
 | 
					
						
							|  |  |  |       s->setSource(QUrl::fromLocalFile(fn));
 | 
					
						
							|  |  |  |       sounds[alert] = {s, loops ? QSoundEffect::Infinite : 0};
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sm = new SubMaster({"carState", "controlsState"});
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QTimer *timer = new QTimer(this);
 | 
					
						
							|  |  |  |     QObject::connect(timer, &QTimer::timeout, this, &Sound::update);
 | 
					
						
							|  |  |  |     timer->start();
 | 
					
						
							|  |  |  |   };
 | 
					
						
							|  |  |  |   ~Sound() {
 | 
					
						
							|  |  |  |     delete sm;
 | 
					
						
							|  |  |  |   };
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private slots:
 | 
					
						
							|  |  |  |   void checkStatus() {
 | 
					
						
							|  |  |  |     QSoundEffect *s = qobject_cast<QSoundEffect*>(sender());
 | 
					
						
							|  |  |  |     assert(s->status() != QSoundEffect::Error);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void update() {
 | 
					
						
							|  |  |  |     sm->update(100);
 | 
					
						
							|  |  |  |     if (sm->updated("carState")) {
 | 
					
						
							|  |  |  |       // scale volume with speed
 | 
					
						
							|  |  |  |       volume = util::map_val((*sm)["carState"].getCarState().getVEgo(), 0.f, 20.f,
 | 
					
						
							|  |  |  |                              Hardware::MIN_VOLUME, Hardware::MAX_VOLUME);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     if (sm->updated("controlsState")) {
 | 
					
						
							|  |  |  |       const cereal::ControlsState::Reader &cs = (*sm)["controlsState"].getControlsState();
 | 
					
						
							|  |  |  |       setAlert({QString::fromStdString(cs.getAlertText1()),
 | 
					
						
							|  |  |  |                 QString::fromStdString(cs.getAlertText2()),
 | 
					
						
							|  |  |  |                 QString::fromStdString(cs.getAlertType()),
 | 
					
						
							|  |  |  |                 cs.getAlertSize(), cs.getAlertSound()});
 | 
					
						
							|  |  |  |     } else if (sm->rcv_frame("controlsState") > 0 && (*sm)["controlsState"].getControlsState().getEnabled() &&
 | 
					
						
							|  |  |  |                ((nanos_since_boot() - sm->rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT)) {
 | 
					
						
							|  |  |  |       setAlert(CONTROLS_UNRESPONSIVE_ALERT);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void setAlert(Alert a) {
 | 
					
						
							|  |  |  |     if (!alert.equal(a)) {
 | 
					
						
							|  |  |  |       alert = a;
 | 
					
						
							|  |  |  |       // stop sounds
 | 
					
						
							|  |  |  |       for (auto &[s, loops] : sounds) {
 | 
					
						
							|  |  |  |         // Only stop repeating sounds
 | 
					
						
							|  |  |  |         if (s->loopsRemaining() == QSoundEffect::Infinite) {
 | 
					
						
							|  |  |  |           s->stop();
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // play sound
 | 
					
						
							|  |  |  |       if (alert.sound != AudibleAlert::NONE) {
 | 
					
						
							|  |  |  |         auto &[s, loops] = sounds[alert.sound];
 | 
					
						
							|  |  |  |         s->setLoopCount(loops);
 | 
					
						
							|  |  |  |         s->setVolume(volume);
 | 
					
						
							|  |  |  |         s->play();
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private:
 | 
					
						
							|  |  |  |   Alert alert;
 | 
					
						
							|  |  |  |   float volume = Hardware::MIN_VOLUME;
 | 
					
						
							|  |  |  |   QMap<AudibleAlert, QPair<QSoundEffect*, int>> sounds;
 | 
					
						
							|  |  |  |   SubMaster *sm;
 | 
					
						
							|  |  |  | };
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int main(int argc, char **argv) {
 | 
					
						
							|  |  |  |   setpriority(PRIO_PROCESS, 0, -20);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QApplication a(argc, argv);
 | 
					
						
							|  |  |  |   Sound sound;
 | 
					
						
							|  |  |  |   return a.exec();
 | 
					
						
							|  |  |  | }
 |