diff --git a/android/jni/com/mapswithme/maps/Framework.cpp b/android/jni/com/mapswithme/maps/Framework.cpp index 702c1107e4..f0213d5f79 100644 --- a/android/jni/com/mapswithme/maps/Framework.cpp +++ b/android/jni/com/mapswithme/maps/Framework.cpp @@ -1262,14 +1262,14 @@ extern "C" } JNIEXPORT jobjectArray JNICALL - Java_com_mapswithme_maps_Framework_nativeGenerateTurnSound(JNIEnv * env, jclass thiz) + Java_com_mapswithme_maps_Framework_nativeGenerateTurnNotifications(JNIEnv * env, jclass thiz) { ::Framework * fr = frm(); if (!fr->IsRoutingActive()) return nullptr; vector turnNotifications; - fr->GenerateTurnSound(turnNotifications); + fr->GenerateTurnNotifications(turnNotifications); if (turnNotifications.empty()) return nullptr; diff --git a/android/src/com/mapswithme/maps/Framework.java b/android/src/com/mapswithme/maps/Framework.java index 7c24196a95..fe7d84094c 100644 --- a/android/src/com/mapswithme/maps/Framework.java +++ b/android/src/com/mapswithme/maps/Framework.java @@ -137,12 +137,12 @@ public class Framework public native static RoutingInfo nativeGetRouteFollowingInfo(); // When an end user is going to a turn he gets sound turn instructions. - // If C++ part wants the client to pronounce an instruction nativeGenerateTurnSound returns + // If C++ part wants the client to pronounce an instruction nativeGenerateTurnNotifications returns // an array of one of more strings. C++ part assumes that all these strings shall be pronounced by the client's TTS. // For example if C++ part wants the client to pronounce "Make a right turn." this method returns // an array with one string "Make a right turn.". The next call of the method returns nothing. - // nativeGenerateTurnSound shall be called by the client when a new position is available. - public native static String[] nativeGenerateTurnSound(); + // nativeGenerateTurnNotifications shall be called by the client when a new position is available. + public native static String[] nativeGenerateTurnNotifications(); public native static void nativeSetRoutingListener(RoutingListener listener); diff --git a/android/src/com/mapswithme/maps/sound/TtsPlayer.java b/android/src/com/mapswithme/maps/sound/TtsPlayer.java index 8958485e9a..e9d1fceb1d 100644 --- a/android/src/com/mapswithme/maps/sound/TtsPlayer.java +++ b/android/src/com/mapswithme/maps/sound/TtsPlayer.java @@ -142,11 +142,10 @@ public enum TtsPlayer public void playTurnNotifications() { - if (!isValid()) - return; // speak() is called while TTS is not ready or could not be initialized. - - final String[] turnNotifications = Framework.nativeGenerateTurnSound(); - if (turnNotifications != null) + // It's necessary to call Framework.nativeGenerateTurnNotifications() even if TtsPlayer is invalid. + final String[] turnNotifications = Framework.nativeGenerateTurnNotifications(); + + if (turnNotifications != null && isValid()) for (String textToSpeak : turnNotifications) speak(textToSpeak); } diff --git a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm index 7af23d7305..f94a2314a7 100644 --- a/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm +++ b/iphone/Maps/Classes/CustomViews/NavigationDashboard/Sound/MWMTextToSpeech.mm @@ -128,16 +128,16 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE"; - (void)playTurnNotifications { - if (![self isValid]) - return; - Framework & frm = GetFramework(); if (!frm.IsRoutingActive()) return; vector notifications; - frm.GenerateTurnSound(notifications); - + frm.GenerateTurnNotifications(notifications); + + if (![self isValid]) + return; + for (auto const & text : notifications) [self speakOneString:@(text.c_str())]; } diff --git a/map/framework.hpp b/map/framework.hpp index ecfd2ee871..966400997f 100644 --- a/map/framework.hpp +++ b/map/framework.hpp @@ -613,13 +613,17 @@ public: /// If not, it returns an empty string. inline string GetTurnNotificationsLocale() const { return m_routingSession.GetTurnNotificationsLocale(); } /// \brief When an end user is going to a turn he gets sound turn instructions. - /// If C++ part wants the client to pronounce an instruction GenerateTurnSound (in turnNotifications) returns - /// an array of one of more strings. C++ part assumes that all these strings shall be pronounced by the client's TTS. + /// If C++ part wants the client to pronounce an instruction GenerateTurnNotifications (in + /// turnNotifications) returns + /// an array of one of more strings. C++ part assumes that all these strings shall be pronounced + /// by the client's TTS. /// For example if C++ part wants the client to pronounce "Make a right turn." this method returns /// an array with one string "Make a right turn.". The next call of the method returns nothing. - /// GenerateTurnSound shall be called by the client when a new position is available. - inline void GenerateTurnSound(vector & turnNotifications) - { return m_routingSession.GenerateTurnSound(turnNotifications); } + /// GenerateTurnNotifications shall be called by the client when a new position is available. + inline void GenerateTurnNotifications(vector & turnNotifications) + { + return m_routingSession.GenerateTurnNotifications(turnNotifications); + } private: void SetRouterImpl(routing::RouterType type); diff --git a/routing/routing.pro b/routing/routing.pro index 9d904325b6..5eeef3d594 100644 --- a/routing/routing.pro +++ b/routing/routing.pro @@ -39,7 +39,7 @@ SOURCES += \ speed_camera.cpp \ turns.cpp \ turns_generator.cpp \ - turns_sound.cpp \ + turns_notification_manager.cpp \ turns_sound_settings.cpp \ turns_tts_text.cpp \ vehicle_model.cpp \ @@ -75,7 +75,7 @@ HEADERS += \ speed_camera.hpp \ turns.hpp \ turns_generator.hpp \ - turns_sound.hpp \ + turns_notification_manager.hpp \ turns_sound_settings.hpp \ turns_tts_text.hpp \ vehicle_model.hpp \ diff --git a/routing/routing_session.cpp b/routing/routing_session.cpp index 8f95290a88..a693243493 100644 --- a/routing/routing_session.cpp +++ b/routing/routing_session.cpp @@ -21,10 +21,6 @@ int constexpr kOnRouteMissedCount = 5; // @TODO(vbykoianko) The distance should depend on the current speed. double constexpr kShowLanesDistInMeters = 500.; -// @TODO(vbykoianko) The distance should depend on the current speed. -// The distance before the next turn in meters when notification -// about the turn after the next one will be shown if available. -double constexpr kShowTheTurnAfterTheNextM = 500.; // @todo(kshalnev) The distance may depend on the current speed. double constexpr kShowPedestrianTurnInMeters = 5.; @@ -110,7 +106,7 @@ void RoutingSession::RemoveRouteImpl() m_state = RoutingNotActive; m_lastDistance = 0.0; m_moveAwayCounter = 0; - m_turnsSound.Reset(); + m_turnNotificationsMgr.Reset(); Route(string()).Swap(m_route); } @@ -154,7 +150,7 @@ RoutingSession::State RoutingSession::OnLocationPositionChanged(m2::PointD const UNUSED_VALUE(guard); ASSERT(m_route.IsValid(), ()); - m_turnsSound.SetSpeedMetersPerSecond(info.m_speed); + m_turnNotificationsMgr.SetSpeedMetersPerSecond(info.m_speed); if (m_route.MoveIterator(info)) { @@ -245,33 +241,17 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const formatDistFn(m_route.GetCurrentDistanceToEndMeters(), info.m_distToTarget, info.m_targetUnitsSuffix); double distanceToTurnMeters = 0.; - double distanceToNextTurnMeters = 0.; turns::TurnItem turn; - turns::TurnItem nextTurn; m_route.GetCurrentTurn(distanceToTurnMeters, turn); formatDistFn(distanceToTurnMeters, info.m_distToTurn, info.m_turnUnitsSuffix); info.m_turn = turn.m_turn; // The turn after the next one. - if (m_route.GetNextTurn(distanceToNextTurnMeters, nextTurn)) - { - double const distBetweenTurnsM = distanceToNextTurnMeters - distanceToTurnMeters; - ASSERT_LESS_OR_EQUAL(0, distBetweenTurnsM, ()); - - if (m_routingSettings.m_showTurnAfterNext && - distanceToTurnMeters < kShowTheTurnAfterTheNextM && distBetweenTurnsM < turns::kMaxTurnDistM) - { - info.m_nextTurn = nextTurn.m_turn; - } - else - { - info.m_nextTurn = routing::turns::TurnDirection::NoTurn; - } - } + if (m_routingSettings.m_showTurnAfterNext) + info.m_nextTurn = m_turnNotificationsMgr.GetSecondTurnNotification(); else - { info.m_nextTurn = routing::turns::TurnDirection::NoTurn; - } + info.m_exitNum = turn.m_exitNum; info.m_time = m_route.GetCurrentTimeToEndSec(); info.m_sourceName = turn.m_sourceName; @@ -309,7 +289,7 @@ void RoutingSession::GetRouteFollowingInfo(FollowingInfo & info) const (distanceToTurnMeters < kShowPedestrianTurnInMeters) ? turn.m_pedestrianTurn : turns::PedestrianDirection::None; } -void RoutingSession::GenerateTurnSound(vector & turnNotifications) +void RoutingSession::GenerateTurnNotifications(vector & turnNotifications) { turnNotifications.clear(); @@ -324,7 +304,7 @@ void RoutingSession::GenerateTurnSound(vector & turnNotifications) vector turns; if (m_route.GetNextTurns(turns)) - m_turnsSound.GenerateTurnSound(turns, turnNotifications); + m_turnNotificationsMgr.GenerateTurnNotifications(turns, turnNotifications); } void RoutingSession::AssignRoute(Route & route, IRouter::ResultCode e) @@ -393,21 +373,21 @@ void RoutingSession::EnableTurnNotifications(bool enable) { threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); - m_turnsSound.Enable(enable); + m_turnNotificationsMgr.Enable(enable); } bool RoutingSession::AreTurnNotificationsEnabled() const { threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); - return m_turnsSound.IsEnabled(); + return m_turnNotificationsMgr.IsEnabled(); } void RoutingSession::SetTurnNotificationsUnits(Settings::Units const units) { threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); - m_turnsSound.SetLengthUnits(units); + m_turnNotificationsMgr.SetLengthUnits(units); } void RoutingSession::SetTurnNotificationsLocale(string const & locale) @@ -415,14 +395,14 @@ void RoutingSession::SetTurnNotificationsLocale(string const & locale) LOG(LINFO, ("The language for turn notifications is", locale)); threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); - m_turnsSound.SetLocale(locale); + m_turnNotificationsMgr.SetLocale(locale); } string RoutingSession::GetTurnNotificationsLocale() const { threads::MutexGuard guard(m_routeSessionMutex); UNUSED_VALUE(guard); - return m_turnsSound.GetLocale(); + return m_turnNotificationsMgr.GetLocale(); } double RoutingSession::GetDistanceToCurrentCamM(SpeedCameraRestriction & camera, Index const & index) diff --git a/routing/routing_session.hpp b/routing/routing_session.hpp index f4b913aec9..d5eade5763 100644 --- a/routing/routing_session.hpp +++ b/routing/routing_session.hpp @@ -4,7 +4,7 @@ #include "routing/route.hpp" #include "routing/router.hpp" #include "routing/turns.hpp" -#include "routing/turns_sound.hpp" +#include "routing/turns_notification_manager.hpp" #include "platform/location.hpp" @@ -106,7 +106,7 @@ public: void SetTurnNotificationsUnits(Settings::Units const units); void SetTurnNotificationsLocale(string const & locale); string GetTurnNotificationsLocale() const; - void GenerateTurnSound(vector & turnNotifications); + void GenerateTurnNotifications(vector & turnNotifications); private: struct DoReadyCallback @@ -154,7 +154,7 @@ private: m2::PointD m_lastGoodPosition; // Sound turn notification parameters. - turns::sound::TurnsSound m_turnsSound; + turns::sound::NotificationManager m_turnNotificationsMgr; RoutingSettings m_routingSettings; diff --git a/routing/routing_tests/turns_sound_test.cpp b/routing/routing_tests/turns_sound_test.cpp index b5105597f6..a8eccfe5c1 100644 --- a/routing/routing_tests/turns_sound_test.cpp +++ b/routing/routing_tests/turns_sound_test.cpp @@ -1,6 +1,6 @@ #include "testing/testing.hpp" -#include "routing/turns_sound.hpp" +#include "routing/turns_notification_manager.hpp" #include "routing/turns_sound_settings.hpp" #include "platform/location.hpp" @@ -9,8 +9,6 @@ namespace { // A error to compare two double after conversion feet to meters. double const kEps = 1.; -// A error to compare two doubles which are almost equal. -double const kSmallEps = .001; } // namespace namespace routing @@ -23,9 +21,10 @@ using namespace location; UNIT_TEST(TurnNotificationSettingsMetersTest) { - Settings const settings(20 /* notificationTimeSeconds */, - 200 /* minNotificationDistanceUnits */, - 700 /* maxNotificationDistanceUnits */, + Settings const settings(20 /* notificationTimeSeconds */, 200 /* minNotificationDistanceUnits */, + 700 /* maxNotificationDistanceUnits */, 5 /* m_startBeforeSeconds */, + 25 /* m_minStartBeforeMeters */, 150 /* m_maxStartBeforeMeters */, + 170 /* m_minDistToSayNotificationMeters */, {100, 200, 300, 400, 500, 600, 700} /* soundedDistancesUnits */, ::Settings::Metric /* lengthUnits */); @@ -38,18 +37,25 @@ UNIT_TEST(TurnNotificationSettingsMetersTest) TEST_EQUAL(settings.RoundByPresetSoundedDistancesUnits(300 /* distanceInUnits */), 300, ()); TEST_EQUAL(settings.RoundByPresetSoundedDistancesUnits(0 /* distanceInUnits */), 100, ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(0. /* distanceInUnits */), 200., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(10. /* distanceInUnits */), 200., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(20. /* distanceInUnits */), 400., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(35. /* distanceInUnits */), 700., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(200. /* distanceInUnits */), 700., kSmallEps), ()); + TEST_EQUAL(settings.ComputeTurnDistanceM(0. /* speedMetersPerSecond */), 200., ()); + TEST_EQUAL(settings.ComputeTurnDistanceM(10. /* speedMetersPerSecond */), 200., ()); + TEST_EQUAL(settings.ComputeTurnDistanceM(20. /* speedMetersPerSecond */), 400., ()); + TEST_EQUAL(settings.ComputeTurnDistanceM(35. /* speedMetersPerSecond */), 700., ()); + TEST_EQUAL(settings.ComputeTurnDistanceM(200. /* speedMetersPerSecond */), 700., ()); + + TEST_EQUAL(settings.ComputeDistToPronounceDistM(0. /* speedMetersPerSecond */), 25., ()); + TEST_EQUAL(settings.ComputeDistToPronounceDistM(10. /* speedMetersPerSecond */), 50., ()); + TEST_EQUAL(settings.ComputeDistToPronounceDistM(20. /* speedMetersPerSecond */), 100., ()); + TEST_EQUAL(settings.ComputeDistToPronounceDistM(35. /* speedMetersPerSecond */), 150., ()); + TEST_EQUAL(settings.ComputeDistToPronounceDistM(200. /* speedMetersPerSecond */), 150., ()); } UNIT_TEST(TurnNotificationSettingsFeetTest) { - Settings const settings(20 /* notificationTimeSeconds */, - 500 /* minNotificationDistanceUnits */, - 2000 /* maxNotificationDistanceUnits */, + Settings const settings(20 /* notificationTimeSeconds */, 500 /* minNotificationDistanceUnits */, + 2000 /* maxNotificationDistanceUnits */, 5 /* m_startBeforeSeconds */, + 25 /* m_minStartBeforeMeters */, 150 /* m_maxStartBeforeMeters */, + 170 /* m_minDistToSayNotificationMeters */, {200, 400, 600, 800, 1000, 1500, 2000} /* soundedDistancesUnits */, ::Settings::Foot /* lengthUnits */); @@ -61,62 +67,60 @@ UNIT_TEST(TurnNotificationSettingsFeetTest) TEST(my::AlmostEqualAbs(settings.ConvertUnitsToMeters(300. /* distanceInUnits */), 91., kEps), ()); TEST_EQUAL(settings.RoundByPresetSoundedDistancesUnits(500 /* distanceInUnits */), 600, ()); TEST_EQUAL(settings.RoundByPresetSoundedDistancesUnits(0 /* distanceInUnits */), 200, ()); - - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(0. /* distanceInUnits */), 500., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(10. /* distanceInUnits */), 500., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(30. /* distanceInUnits */), 600., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(40. /* distanceInUnits */), 800., kSmallEps), ()); - TEST(my::AlmostEqualAbs(settings.ComputeTurnDistance(200. /* distanceInUnits */), 2000., kSmallEps), ()); } UNIT_TEST(TurnNotificationSettingsNotValidTest) { Settings settings1(20 /* notificationTimeSeconds */, 500 /* minNotificationDistanceUnits */, - 2000 /* maxNotificationDistanceUnits */, + 2000 /* maxNotificationDistanceUnits */, 5 /* m_startBeforeSeconds */, + 25 /* m_minStartBeforeMeters */, 150 /* m_maxStartBeforeMeters */, + 170 /* m_minDistToSayNotificationMeters */, {200, 400, 800, 600, 1000, 1500, 2000} /* soundedDistancesUnits */, ::Settings::Foot /* lengthUnits */); TEST(!settings1.IsValid(), ()); Settings settings2(20 /* notificationTimeSeconds */, 5000 /* minNotificationDistanceUnits */, - 2000 /* maxNotificationDistanceUnits */, + 2000 /* maxNotificationDistanceUnits */, 5 /* m_startBeforeSeconds */, + 25 /* m_minStartBeforeMeters */, 150 /* m_maxStartBeforeMeters */, + 170 /* m_minDistToSayNotificationMeters */, {200, 400, 600, 800, 1000, 1500, 2000} /* soundedDistancesUnits */, ::Settings::Metric /* lengthUnits */); TEST(!settings2.IsValid(), ()); - - Settings settings3; - TEST(!settings3.IsValid(), ()); } UNIT_TEST(TurnsSoundMetersTest) { - TurnsSound turnSound(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, - 100 /* maxStartBeforeMeters */, 100 /* minDistToSayNotificationMeters */); - turnSound.Enable(true); - turnSound.SetLengthUnits(::Settings::Metric); + NotificationManager notificationManager(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, + 100 /* maxStartBeforeMeters */, + 100 /* minDistToSayNotificationMeters */); + notificationManager.Enable(true); + notificationManager.SetLengthUnits(::Settings::Metric); string const engShortJson = "\ {\ \"in_600_meters\":\"In 600 meters.\",\ \"make_a_right_turn\":\"Make a right turn.\"\ }"; - turnSound.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); - turnSound.m_settings.ForTestingSetNotificationTimeSecond(20); + notificationManager.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); + notificationManager.m_settings.ForTestingSetNotificationTimeSecond(20); - turnSound.Reset(); - turnSound.SetSpeedMetersPerSecond(30.); + notificationManager.Reset(); + notificationManager.SetSpeedMetersPerSecond(30.); vector turns = {{{5 /* idx */, TurnDirection::TurnRight}, 1000.}}; vector turnNotifications; // Starting nearing the turnItem. // 1000 meters till the turn. No sound notifications is required. - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 700 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 700.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 699 meters till the turn. It's time to pronounce the first voice notification. // Why? The current speed is 30 meters per seconds. According to correctSettingsMeters @@ -125,49 +129,57 @@ UNIT_TEST(TurnsSoundMetersTest) // So we start playing the first notification when the distance till the turn is less // then 20 seconds * 30 meters per seconds + 100 meters = 700 meters. turns.front().m_distMeters = 699.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); vector const expectedNotification1 = {{"In 600 meters. Make a right turn."}}; TEST_EQUAL(turnNotifications, expectedNotification1, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 650 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 650.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); - turnSound.SetSpeedMetersPerSecond(32.); + notificationManager.SetSpeedMetersPerSecond(32.); // 150 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 150.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 100 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 100.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 99 meters till the turn. It's time to pronounce the second voice notification. turns.front().m_distMeters = 99.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); vector const expectedNotification2 = {{"Make a right turn."}}; TEST_EQUAL(turnNotifications, expectedNotification2, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 99 meters till the turn again. No sound notifications is required. turns.front().m_distMeters = 99.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 50 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 50.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 0 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 0.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); - TEST(turnSound.IsEnabled(), ()); + TEST(notificationManager.IsEnabled(), ()); } // Test case: @@ -176,10 +188,11 @@ UNIT_TEST(TurnsSoundMetersTest) // So the first notification of the second turn shall be skipped. UNIT_TEST(TurnsSoundMetersTwoTurnsTest) { - TurnsSound turnSound(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, - 100 /* maxStartBeforeMeters */, 100 /* minDistToSayNotificationMeters */); - turnSound.Enable(true); - turnSound.SetLengthUnits(::Settings::Metric); + NotificationManager notificationManager(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, + 100 /* maxStartBeforeMeters */, + 100 /* minDistToSayNotificationMeters */); + notificationManager.Enable(true); + notificationManager.SetLengthUnits(::Settings::Metric); string const engShortJson = "\ {\ @@ -187,85 +200,87 @@ UNIT_TEST(TurnsSoundMetersTwoTurnsTest) \"make_a_sharp_right_turn\":\"Make a sharp right turn.\",\ \"enter_the_roundabout\":\"Enter the roundabout.\"\ }"; - turnSound.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); - turnSound.m_settings.ForTestingSetNotificationTimeSecond(20); + notificationManager.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); + notificationManager.m_settings.ForTestingSetNotificationTimeSecond(20); - turnSound.Reset(); - turnSound.SetSpeedMetersPerSecond(35.); + notificationManager.Reset(); + notificationManager.SetSpeedMetersPerSecond(35.); vector turns = {{{5 /* idx */, TurnDirection::TurnSharpRight}, 800.}}; vector turnNotifications; // Starting nearing the first turn. // 800 meters till the turn. No sound notifications is required. - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 700 meters till the turn. It's time to pronounce the first voice notification. // The speed is high. - // The compensation of TurnsSound::m_startBeforeSeconds/TurnsSound::m_minStartBeforeMeters/ - // TurnsSound::m_maxStartBeforeMeters is not enough. + // The compensation of + // NotificationManager::m_startBeforeSeconds/NotificationManager::m_minStartBeforeMeters/ + // NotificationManager::m_maxStartBeforeMeters is not enough. // The user will be closer to the turn while pronouncing despite the compensation. // So it should be pronounced "In 600 meters." turns.front().m_distMeters = 700.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); vector const expectedNotification1 = {{"In 600 meters. Make a sharp right turn."}}; TEST_EQUAL(turnNotifications, expectedNotification1, ()); - turnSound.SetSpeedMetersPerSecond(32.); + notificationManager.SetSpeedMetersPerSecond(32.); // 150 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 150.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 99 meters till the turn. It's time to pronounce the second voice notification. turns.front().m_distMeters = 99.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); vector const expectedNotification2 = {{"Make a sharp right turn."}}; TEST_EQUAL(turnNotifications, expectedNotification2, ()); - turnSound.SetSpeedMetersPerSecond(10.); + notificationManager.SetSpeedMetersPerSecond(10.); // 0 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 0.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); vector turns2 = {{{11 /* idx */, TurnDirection::EnterRoundAbout, 2 /* exitNum */}, 60.}}; // Starting nearing the second turn. - turnSound.GenerateTurnSound(turns2, turnNotifications); + notificationManager.GenerateTurnNotifications(turns2, turnNotifications); TEST(turnNotifications.empty(), ()); // 40 meters till the second turn. It's time to pronounce the second voice notification // without the first one. turns2.front().m_distMeters = 40.; - turnSound.GenerateTurnSound(turns2, turnNotifications); + notificationManager.GenerateTurnNotifications(turns2, turnNotifications); vector const expectedNotification3 = {{"Enter the roundabout."}}; TEST_EQUAL(turnNotifications, expectedNotification3, ()); - TEST(turnSound.IsEnabled(), ()); + TEST(notificationManager.IsEnabled(), ()); } UNIT_TEST(TurnsSoundFeetTest) { - TurnsSound turnSound(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, - 100 /* maxStartBeforeMeters */, 100 /* minDistToSayNotificationMeters */); - turnSound.Enable(true); - turnSound.SetLengthUnits(::Settings::Foot); + NotificationManager notificationManager(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, + 100 /* maxStartBeforeMeters */, + 100 /* minDistToSayNotificationMeters */); + notificationManager.Enable(true); + notificationManager.SetLengthUnits(::Settings::Foot); string const engShortJson = "\ {\ \"in_2000_feet\":\"In 2000 feet.\",\ \"enter_the_roundabout\":\"Enter the roundabout.\"\ }"; - turnSound.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); - turnSound.m_settings.ForTestingSetNotificationTimeSecond(20); + notificationManager.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); + notificationManager.m_settings.ForTestingSetNotificationTimeSecond(20); - turnSound.Reset(); - turnSound.SetSpeedMetersPerSecond(30.); + notificationManager.Reset(); + notificationManager.SetSpeedMetersPerSecond(30.); vector turns = {{{7 /* idx */, TurnDirection::EnterRoundAbout, 3 /* exitNum */}, 1000.}}; @@ -273,12 +288,12 @@ UNIT_TEST(TurnsSoundFeetTest) // Starting nearing the turnItem. // 1000 meters till the turn. No sound notifications is required. - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 700 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 700.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 699 meters till the turn. It's time to pronounce the first voice notification. @@ -288,55 +303,56 @@ UNIT_TEST(TurnsSoundFeetTest) // So we start playing the first notification when the distance till the turn is less // then 20 seconds * 30 meters per seconds + 100 meters = 700 meters. turns.front().m_distMeters = 699.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); vector const expectedNotification1 = {{"In 2000 feet. Enter the roundabout."}}; TEST_EQUAL(turnNotifications, expectedNotification1, ()); // 650 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 650.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 150 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 150.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 100 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 100.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 99 meters till the turn. It's time to pronounce the second voice notification. turns.front().m_distMeters = 99.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); vector const expectedNotification2 = {{"Enter the roundabout."}}; TEST_EQUAL(turnNotifications, expectedNotification2, ()); // 99 meters till the turn again. No sound notifications is required. turns.front().m_distMeters = 99.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 50 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 50.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); // 0 meters till the turn. No sound notifications is required. turns.front().m_distMeters = 0.; - turnSound.GenerateTurnSound(turns, turnNotifications); + notificationManager.GenerateTurnNotifications(turns, turnNotifications); TEST(turnNotifications.empty(), ()); - TEST(turnSound.IsEnabled(), ()); + TEST(notificationManager.IsEnabled(), ()); } UNIT_TEST(TurnsSoundComposedTurnTest) { - TurnsSound turnSound(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, - 100 /* maxStartBeforeMeters */, 100 /* minDistToSayNotificationMeters */); - turnSound.Enable(true); - turnSound.SetLengthUnits(::Settings::Metric); + NotificationManager notificationManager(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, + 100 /* maxStartBeforeMeters */, + 100 /* minDistToSayNotificationMeters */); + notificationManager.Enable(true); + notificationManager.SetLengthUnits(::Settings::Metric); string const engShortJson = "\ {\ @@ -346,19 +362,20 @@ UNIT_TEST(TurnsSoundComposedTurnTest) \"then\":\"Then.\",\ \"you_have_reached_the_destination\":\"You have reached the destination.\"\ }"; - turnSound.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); - turnSound.m_settings.ForTestingSetNotificationTimeSecond(30); + notificationManager.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); + notificationManager.m_settings.ForTestingSetNotificationTimeSecond(30); - turnSound.Reset(); - turnSound.SetSpeedMetersPerSecond(20.); + notificationManager.Reset(); + notificationManager.SetSpeedMetersPerSecond(20.); vector turnNotifications; // Starting nearing the first turn. // 800 meters till the first turn. vector const turns1 = {{{5 /* idx */, TurnDirection::TurnRight}, 800. /* m_distMeters */}, {{10 /* idx */, TurnDirection::EnterRoundAbout}, 1000. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns1, turnNotifications); + notificationManager.GenerateTurnNotifications(turns1, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 620 meters till the first turn. turnNotifications.clear(); @@ -366,15 +383,17 @@ UNIT_TEST(TurnsSoundComposedTurnTest) {{10 /* idx */, TurnDirection::EnterRoundAbout}, 820. /* m_distMeters */}}; vector const expectedNotification2 = {{"In 600 meters. Turn right."}, {"Then. Enter the roundabout."}}; - turnSound.GenerateTurnSound(turns2, turnNotifications); + notificationManager.GenerateTurnNotifications(turns2, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification2, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::EnterRoundAbout, ()); // 300 meters till the first turn. turnNotifications.clear(); vector const turns3 = {{{5 /* idx */, TurnDirection::TurnRight}, 300. /* m_distMeters */}, {{10 /* idx */, TurnDirection::EnterRoundAbout}, 500. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns3, turnNotifications); + notificationManager.GenerateTurnNotifications(turns3, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::EnterRoundAbout, ()); // 20 meters till the first turn. turnNotifications.clear(); @@ -382,31 +401,35 @@ UNIT_TEST(TurnsSoundComposedTurnTest) {{10 /* idx */, TurnDirection::EnterRoundAbout}, 220. /* m_distMeters */}}; vector const expectedNotification4 = {{"Turn right."}, {"Then. Enter the roundabout."}}; - turnSound.GenerateTurnSound(turns4, turnNotifications); + notificationManager.GenerateTurnNotifications(turns4, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification4, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::EnterRoundAbout, ()); // After the first turn. turnNotifications.clear(); vector const turns5 = {{{10 /* idx */, TurnDirection::EnterRoundAbout}, 180. /* m_distMeters */}, {{15 /* idx */, TurnDirection::ReachedYourDestination}, 1180. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns5, turnNotifications); + notificationManager.GenerateTurnNotifications(turns5, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // Just before the second turn. turnNotifications.clear(); vector const turns6 = {{{10 /* idx */, TurnDirection::EnterRoundAbout}, 10. /* m_distMeters */}, {{15 /* idx */, TurnDirection::ReachedYourDestination}, 1010. /* m_distMeters */}}; vector const expectedNotification6 = {{"Enter the roundabout."}}; - turnSound.GenerateTurnSound(turns6, turnNotifications); + notificationManager.GenerateTurnNotifications(turns6, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification6, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); } UNIT_TEST(TurnsSoundRoundaboutTurnTest) { - TurnsSound turnSound(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, - 100 /* maxStartBeforeMeters */, 100 /* minDistToSayNotificationMeters */); - turnSound.Enable(true); - turnSound.SetLengthUnits(::Settings::Metric); + NotificationManager notificationManager(5 /* startBeforeSeconds */, 10 /* minStartBeforeMeters */, + 100 /* maxStartBeforeMeters */, + 100 /* minDistToSayNotificationMeters */); + notificationManager.Enable(true); + notificationManager.SetLengthUnits(::Settings::Metric); string const engShortJson = "\ {\ @@ -418,11 +441,11 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) \"in_600_meters\":\"In 600 meters.\",\ \"then\":\"Then.\"\ }"; - turnSound.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); - turnSound.m_settings.ForTestingSetNotificationTimeSecond(30); + notificationManager.m_getTtsText.ForTestingSetLocaleWithJson(engShortJson, "en"); + notificationManager.m_settings.ForTestingSetNotificationTimeSecond(30); - turnSound.Reset(); - turnSound.SetSpeedMetersPerSecond(20.); + notificationManager.Reset(); + notificationManager.SetSpeedMetersPerSecond(20.); vector turnNotifications; // Starting nearing the first turn. @@ -431,8 +454,9 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) 1000. /* m_distMeters */}, {{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, 2000. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns1, turnNotifications); + notificationManager.GenerateTurnNotifications(turns1, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 620 meters till the first turn. vector const turns2 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */}, @@ -441,8 +465,9 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) 1620. /* m_distMeters */}}; vector const expectedNotification2 = {{"In 600 meters. Enter the roundabout."}, {"Then. Take the second exit."}}; - turnSound.GenerateTurnSound(turns2, turnNotifications); + notificationManager.GenerateTurnNotifications(turns2, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification2, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 3 meters till the first turn. vector const turns3 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */}, @@ -451,24 +476,27 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) 1003. /* m_distMeters */}}; vector const expectedNotification3 = {{"Enter the roundabout."}, {"Then. Take the second exit."}}; - turnSound.GenerateTurnSound(turns3, turnNotifications); + notificationManager.GenerateTurnNotifications(turns3, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification3, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 900 meters till the second turn. vector const turns4 = {{{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, 900. /* m_distMeters */}, {{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, 1900. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns4, turnNotifications); + notificationManager.GenerateTurnNotifications(turns4, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 300 meters till the second turn. vector const turns5 = {{{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, 300. /* m_distMeters */}, {{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, 1300. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns5, turnNotifications); + notificationManager.GenerateTurnNotifications(turns5, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 3 meters till the second turn. vector const turns6 = {{{10 /* idx */, TurnDirection::LeaveRoundAbout, 2 /* m_exitNum */}, @@ -476,8 +504,9 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) {{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, 1003. /* m_distMeters */}}; vector const expectedNotification6 = {{"Leave the roundabout."}}; - turnSound.GenerateTurnSound(turns6, turnNotifications); + notificationManager.GenerateTurnNotifications(turns6, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification6, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 5 meters till the third turn. vector const turns7 = {{{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */}, @@ -486,18 +515,21 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) 1005. /* m_distMeters */}}; vector const expectedNotification7 = {{"Enter the roundabout."}, {"Then. Take the first exit."}}; - turnSound.GenerateTurnSound(turns7, turnNotifications); // The first notification fast forwarding. - turnSound.GenerateTurnSound(turns7, turnNotifications); + notificationManager.GenerateTurnNotifications( + turns7, turnNotifications); // The first notification fast forwarding. + notificationManager.GenerateTurnNotifications(turns7, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification7, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 900 meters till the 4th turn. - turnSound.Reset(); + notificationManager.Reset(); vector const turns8 = {{{25 /* idx */, TurnDirection::EnterRoundAbout, 4 /* m_exitNum */}, 900. /* m_distMeters */}, {{30 /* idx */, TurnDirection::LeaveRoundAbout, 4 /* m_exitNum */}, 1200. /* m_distMeters */}}; - turnSound.GenerateTurnSound(turns8, turnNotifications); + notificationManager.GenerateTurnNotifications(turns8, turnNotifications); TEST(turnNotifications.empty(), ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::NoTurn, ()); // 620 meters till the 4th turn. vector const turns9 = {{{25 /* idx */, TurnDirection::EnterRoundAbout, 4 /* m_exitNum */}, @@ -506,8 +538,9 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest) 920. /* m_distMeters */}}; vector const expectedNotification9 = {{"In 600 meters. Enter the roundabout."}, {"Then. Take the fourth exit."}}; - turnSound.GenerateTurnSound(turns9, turnNotifications); + notificationManager.GenerateTurnNotifications(turns9, turnNotifications); TEST_EQUAL(turnNotifications, expectedNotification9, ()); + TEST_EQUAL(notificationManager.GetSecondTurnNotification(), TurnDirection::LeaveRoundAbout, ()); } UNIT_TEST(GetJsonBufferTest) diff --git a/routing/turns.hpp b/routing/turns.hpp index a91a2123b6..9a1f95a28b 100644 --- a/routing/turns.hpp +++ b/routing/turns.hpp @@ -16,11 +16,6 @@ namespace turns /// @todo(vbykoianko) It's a good idea to gather all the turns information into one entity. /// For the time being several separate entities reflect the turn information. Like Route::TTurns -// If the distance between two sequential turns is more than kMaxTurnDistM -// the information about the second turn will be shown or pronounced when the user is -// approaching to the first one. -double constexpr kMaxTurnDistM = 400.; - /*! * \warning The order of values below shall not be changed. * TurnRight(TurnLeft) must have a minimal value and diff --git a/routing/turns_notification_manager.cpp b/routing/turns_notification_manager.cpp new file mode 100644 index 0000000000..a183a2dd6c --- /dev/null +++ b/routing/turns_notification_manager.cpp @@ -0,0 +1,250 @@ +#include "routing/turns_notification_manager.hpp" + +#include "platform/location.hpp" + +namespace +{ +// If the distance between two sequential turns is less than kMaxTurnDistM +// the information about the second turn will be shown or pronounced when the user is +// approaching to the first one. +double constexpr kMaxTurnDistM = 400.; + +// Returns true if the closest turn is an entrance to a roundabout and the second is +// an exit form a roundabout. +// Note. There are some cases when another turn (besides an exit from roundabout) +// follows an entrance to a roundabout. It could happend in case of turns inside a roundabout. +// Returns false otherwise. +bool IsClassicEntranceToRoundabout(routing::turns::TurnItemDist const & firstTurn, + routing::turns::TurnItemDist const & secondTurn) +{ + return firstTurn.m_turnItem.m_turn == routing::turns::TurnDirection::EnterRoundAbout && + secondTurn.m_turnItem.m_turn == routing::turns::TurnDirection::LeaveRoundAbout; +} +} // namespace + +namespace routing +{ +namespace turns +{ +namespace sound +{ +string NotificationManager::GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, + bool useThenInsteadOfDistance, TurnDirection turnDir, + ::Settings::Units lengthUnits) const +{ + Notification const notification(distanceUnits, exitNum, useThenInsteadOfDistance, turnDir, + lengthUnits); + return m_getTtsText(notification); +} + +void NotificationManager::GenerateTurnNotifications(vector const & turns, + vector & turnNotifications) +{ + m_secondTurnNotification = GenerateSecondTurnNotification(turns); + + turnNotifications.clear(); + if (!m_enabled || turns.empty()) + return; + + TurnItemDist const & firstTurn = turns.front(); + string firstNotification = GenerateFirstTurnSound(firstTurn.m_turnItem, firstTurn.m_distMeters); + if (m_nextTurnNotificationProgress == PronouncedNotification::Nothing) + return; + if (firstNotification.empty()) + return; + turnNotifications.emplace_back(move(firstNotification)); + + // Generating notifications like "Then turn left" if necessary. + if (turns.size() < 2) + return; + TurnItemDist const & secondTurn = turns[1]; + ASSERT_LESS_OR_EQUAL(firstTurn.m_distMeters, secondTurn.m_distMeters, ()); + if (secondTurn.m_distMeters - firstTurn.m_distMeters > kMaxTurnDistM && + !IsClassicEntranceToRoundabout(firstTurn, secondTurn)) + { + return; + } + string secondNotification = GenerateTurnText( + 0 /* distanceUnits is not used because of "Then" is used */, secondTurn.m_turnItem.m_exitNum, + true, secondTurn.m_turnItem.m_turn, m_settings.GetLengthUnits()); + if (secondNotification.empty()) + return; + turnNotifications.emplace_back(move(secondNotification)); + // Turn notification with word "Then" (about the second turn) will be pronounced. + // When this second turn become the first one the first notification about the turn + // shall be skipped. + m_turnNotificationWithThen = true; +} + +string NotificationManager::GenerateFirstTurnSound(TurnItem const & turn, + double distanceToTurnMeters) +{ + if (m_nextTurnIndex != turn.m_index) + { + m_nextTurnNotificationProgress = PronouncedNotification::Nothing; + m_nextTurnIndex = turn.m_index; + } + + uint32_t const distanceToPronounceNotificationM = + m_settings.ComputeDistToPronounceDistM(m_speedMetersPerSecond); + if (m_nextTurnNotificationProgress == PronouncedNotification::Nothing) + { + if (!m_settings.TooCloseForFisrtNotification(distanceToTurnMeters)) + { + uint32_t const startPronounceDistMeters = + m_settings.ComputeTurnDistanceM(m_speedMetersPerSecond) + + distanceToPronounceNotificationM; + if (distanceToTurnMeters < startPronounceDistMeters) + { + if (m_turnNotificationWithThen) + { + FastForwardFirstTurnNotification(); + } + else + { + double const distToPronounceMeters = distanceToTurnMeters - distanceToPronounceNotificationM; + if (distToPronounceMeters < 0) + { + FastForwardFirstTurnNotification(); + return string(); // The current position is too close to the turn for the first + // notification. + } + + // Pronouncing first turn sound notification. + double const distToPronounceUnits = + m_settings.ConvertMetersToUnits(distToPronounceMeters); + uint32_t const roundedDistToPronounceUnits = + m_settings.RoundByPresetSoundedDistancesUnits(distToPronounceUnits); + m_nextTurnNotificationProgress = PronouncedNotification::First; + return GenerateTurnText(roundedDistToPronounceUnits, turn.m_exitNum, + false /* useThenInsteadOfDistance */, turn.m_turn, + m_settings.GetLengthUnits()); + } + } + } + else + { + // The first notification has not been pronounced but the distance to the turn is too short. + // It happens if one turn follows shortly behind another one. + m_nextTurnNotificationProgress = PronouncedNotification::First; + FastForwardFirstTurnNotification(); + } + return string(); + } + + if (m_nextTurnNotificationProgress == PronouncedNotification::First && + distanceToTurnMeters < distanceToPronounceNotificationM) + { + m_nextTurnNotificationProgress = PronouncedNotification::Second; + FastForwardFirstTurnNotification(); + return GenerateTurnText(0 /* distanceUnits */, turn.m_exitNum, + false /* useThenInsteadOfDistance */, turn.m_turn, + m_settings.GetLengthUnits()); + } + return string(); +} + +void NotificationManager::Enable(bool enable) +{ + if (enable && !m_enabled) + Reset(); + m_enabled = enable; +} + +void NotificationManager::SetLengthUnits(::Settings::Units units) +{ + m_settings.SetLengthUnits(units); + switch (units) + { + case ::Settings::Metric: + m_settings.SetState(30 /* notificationTimeSeconds */, 200 /* minNotificationDistanceUnits */, + 2000 /* maxNotificationDistanceUnits */, + GetSoundedDistMeters() /* soundedDistancesUnits */, + ::Settings::Metric /* lengthUnits */); + return; + case ::Settings::Foot: + m_settings.SetState(30 /* notificationTimeSeconds */, 500 /* minNotificationDistanceUnits */, + 5000 /* maxNotificationDistanceUnits */, + GetSoundedDistFeet() /* soundedDistancesUnits */, + ::Settings::Foot /* lengthUnits */); + return; + } +} + +void NotificationManager::SetSpeedMetersPerSecond(double speed) +{ + // When the quality of GPS data is bad the current speed may be less then zero. + // It's easy to reproduce at an office with Nexus 5. + // In that case zero meters per second is used. + m_speedMetersPerSecond = max(0., speed); +} + +void NotificationManager::Reset() +{ + m_nextTurnNotificationProgress = PronouncedNotification::Nothing; + m_nextTurnIndex = 0; + m_turnNotificationWithThen = false; + m_secondTurnNotification = TurnDirection::NoTurn; + m_secondTurnNotificationIndex = 0; +} + +void NotificationManager::FastForwardFirstTurnNotification() +{ + m_turnNotificationWithThen = false; + if (m_nextTurnNotificationProgress == PronouncedNotification::Nothing) + m_nextTurnNotificationProgress = PronouncedNotification::First; +} + +TurnDirection NotificationManager::GenerateSecondTurnNotification(vector const & turns) +{ + if (turns.size() < 2) + { + m_secondTurnNotificationIndex = 0; + return TurnDirection::NoTurn; + } + + TurnItemDist const & firstTurn = turns[0]; + TurnItemDist const & secondTurn = turns[1]; + + if (firstTurn.m_turnItem.m_index != m_secondTurnNotificationIndex) + m_secondTurnNotificationIndex = 0; // It's a new closest(fisrt) turn. + else if (m_secondTurnNotificationIndex != 0) + return secondTurn.m_turnItem.m_turn; // m_secondTurnNotificationIndex was set to true before. + + double const distBetweenTurnsMeters = secondTurn.m_distMeters - firstTurn.m_distMeters; + ASSERT_LESS_OR_EQUAL(0., distBetweenTurnsMeters, ()); + + if (distBetweenTurnsMeters > kMaxTurnDistM) + return TurnDirection::NoTurn; + + uint32_t const startPronounceDistMeters = + m_settings.ComputeTurnDistanceM(m_speedMetersPerSecond) + + m_settings.ComputeDistToPronounceDistM(m_speedMetersPerSecond); + if (firstTurn.m_distMeters <= startPronounceDistMeters) + { + m_secondTurnNotificationIndex = firstTurn.m_turnItem.m_index; + return secondTurn.m_turnItem.m_turn; // It's time to inform about the turn after the next one. + } + return TurnDirection::NoTurn; +} + +string DebugPrint(PronouncedNotification const notificationProgress) +{ + switch (notificationProgress) + { + case PronouncedNotification::Nothing: + return "Nothing"; + case PronouncedNotification::First: + return "First"; + case PronouncedNotification::Second: + return "Second"; + } + + ASSERT(false, ()); + stringstream out; + out << "unknown PronouncedNotification (" << static_cast(notificationProgress) << ")"; + return out.str(); +} +} // namespace sound +} // namespace turns +} // namespace routing diff --git a/routing/turns_notification_manager.hpp b/routing/turns_notification_manager.hpp new file mode 100644 index 0000000000..463e14c959 --- /dev/null +++ b/routing/turns_notification_manager.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include "routing/turns_sound_settings.hpp" +#include "routing/turns_tts_text.hpp" + +#include "platform/settings.hpp" + +#include "std/string.hpp" + +namespace location +{ +class FollowingInfo; +} + +namespace routing +{ +namespace turns +{ +namespace sound +{ +/// \brief The PronouncedNotification enum represents which sound notifications +/// for turns were heard. +enum class PronouncedNotification +{ + Nothing, + First, + Second /** The second notification just before the turn was pronounced. */ +}; + +string DebugPrint(PronouncedNotification const notificationProgress); + +/// \brief The TurnsSound class is responsible for all route turn sound notifications functionality. +/// To be able to generate turn sound notification the class needs to have correct Settings +/// and relevant speed. +class NotificationManager +{ + friend void UnitTest_TurnsSoundMetersTest(); + friend void UnitTest_TurnsSoundMetersTwoTurnsTest(); + friend void UnitTest_TurnsSoundFeetTest(); + friend void UnitTest_TurnsSoundComposedTurnTest(); + friend void UnitTest_TurnsSoundRoundaboutTurnTest(); + + /// \brief The private contructor is used only for testing. + NotificationManager(uint32_t startBeforeSeconds, uint32_t minStartBeforeMeters, + uint32_t maxStartBeforeMeters, uint32_t minDistToSayNotificationMeters) + : m_enabled(false) + , m_speedMetersPerSecond(0.) + , m_settings(startBeforeSeconds, minStartBeforeMeters, maxStartBeforeMeters, + minDistToSayNotificationMeters) + , m_nextTurnNotificationProgress(PronouncedNotification::Nothing) + , m_turnNotificationWithThen(false) + , m_nextTurnIndex(0) + , m_secondTurnNotification(TurnDirection::NoTurn) + { + } + + /// m_enabled == true when tts is turned on. + /// Important! Clients (iOS/Android) implies that m_enabled is false by default. + bool m_enabled; + /// In m_speedMetersPerSecond is intended for some speed which will be used for + /// convertion a distance in seconds to distance in meters. It could be a current + /// an end user speed or an average speed for last several seconds. + /// @TODO(vbykoianko) It's better to use an average speed + /// for last several seconds instead of the current speed here. + double m_speedMetersPerSecond; + + Settings m_settings; + /// m_nextTurnNotificationProgress keeps a status which is being changing while + /// an end user is coming to the closest (the next) turn along the route. + /// When an end user is far from the next turn + /// m_nextTurnNotificationProgress == Nothing. + /// After the first turn notification has been pronounced + /// m_nextTurnNotificationProgress == First. + /// After the second notification has been pronounced + /// m_nextTurnNotificationProgress == Second. + PronouncedNotification m_nextTurnNotificationProgress; + /// The flag is set to true if notification about the second turn was pronounced. + /// It could happen in expressions like "Turn right. Then turn left." + /// This flag is used to pass the information if second turn notification was pronounced + /// between two calls of GenerateTurnNotifications() method. + bool m_turnNotificationWithThen; + + uint32_t m_nextTurnIndex; + /// getTtsText is a convector form turn notification information and locale to + /// notification string. + GetTtsText m_getTtsText; + /// if m_secondTurnNotification == true it's time to display the second turn notification + /// visual informer, and false otherwise. + /// m_secondTurnNotification is a direction of the turn after the closest one + /// if an end user shall be informed about it. If not, m_secondTurnNotification == + /// TurnDirection::NoTurn + TurnDirection m_secondTurnNotification; + /// m_secondTurnNotificationIndex is an index of the closest turn on the route polyline + /// where m_secondTurnNotification was set to true last time for a turn. + /// If the closest turn is changed m_secondTurnNotification is set to 0. + /// \note 0 is a valid index. But in this context it could be considered as invalid + /// because if firstTurnIndex == 0 that means we're at very beginning of the route + /// and we're about to making a turn. In that case it's no use to inform a user about + /// the turn after the next one. + uint32_t m_secondTurnNotificationIndex; + + string GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, bool useThenInsteadOfDistance, + TurnDirection turnDir, ::Settings::Units lengthUnits) const; + /// Generates turn sound notification for the nearest to the current position turn. + string GenerateFirstTurnSound(TurnItem const & turn, double distanceToTurnMeters); + /// Changes the state of the class to emulate that first turn notification is pronouned + /// without pronunciation. + void FastForwardFirstTurnNotification(); + /// \param turns contains information about the next turns staring from closest turn. + /// That means turns[0] is the closest turn (if available). + /// @return the second closest turn or TurnDirection::NoTurn. + /// \note If GenerateSecondTurnNotification returns a value different form TurnDirection::NoTurn + /// for a turn once it will return the same value until the turn is changed. + /// \note This method works independent from m_enabled value. + /// So it works when the class enable and disable. + TurnDirection GenerateSecondTurnNotification(vector const & turns); + +public: + NotificationManager() + : m_enabled(false) + , m_speedMetersPerSecond(0.) + , m_settings(5 /* m_startBeforeSeconds */, 25 /* m_minStartBeforeMeters */, + 150 /* m_maxStartBeforeMeters */, 170 /* m_minDistToSayNotificationMeters */) + , m_nextTurnNotificationProgress(PronouncedNotification::Nothing) + , m_turnNotificationWithThen(false) + , m_nextTurnIndex(0) + , m_secondTurnNotification(TurnDirection::NoTurn) + { + } + + bool IsEnabled() const { return m_enabled; } + void Enable(bool enable); + void SetLengthUnits(::Settings::Units units); + inline ::Settings::Units GetLengthUnits() const { return m_settings.GetLengthUnits(); } + inline void SetLocale(string const & locale) { m_getTtsText.SetLocale(locale); } + inline string GetLocale() const { return m_getTtsText.GetLocale(); } + void SetSpeedMetersPerSecond(double speed); + + /// \brief GenerateTurnNotifications updates information about the next turn notification. + /// It also fills turnNotifications when it's necessary. + /// If this TurnsSound wants to play a sound message once it should push one item to + /// the vector turnNotifications once when GenerateTurnNotifications is called. + /// \param turns contains information about the next turns starting from the closest one. + /// \param distanceToTurnMeters is distance to the next turn in meters. + /// \param turnNotifications is a parameter to fill it if it's necessary. + /// \note The client implies turnNotifications does not contain empty strings. + void GenerateTurnNotifications(vector const & turns, + vector & turnNotifications); + /// Reset states which reflects current route position. + /// The method shall be called after creating a new route or after rerouting. + void Reset(); + /// \brief The method was implemented to display the second turn notification + /// in an appropriate moment. + /// @return the second closest turn. + /// The return value is different form TurnDirection::NoTurn + /// if an end user is close enough to the first (current) turn and + /// the distance between the closest and the second closest turn is not large. + /// (That means a notification about turn after the closest one was pronounced.) + /// For example, if while closing to the closest turn was pronounced + /// "Turn right. Then turn left." 500 meters before the closest turn, after that moment + /// GetSecondTurnNotification returns TurnDirection::TurnLeft if distance to first turn < 500 + /// meters. + /// After the closest composed turn was passed GetSecondTurnNotification returns + /// TurnDirection::NoTurn. + /// \note If the returning value is TurnDirection::NoTurn no turn shall be displayed. + /// \note If GetSecondTurnNotification returns a value different form TurnDirection::NoTurn + /// for a turn once it continues returning the same value until the turn is changed. + /// \note This method works independent from m_enabled value. + /// So it works when the class enable and disable. + TurnDirection GetSecondTurnNotification() const { return m_secondTurnNotification; } +}; +} // namespace sound +} // namespace turns +} // namespace routing diff --git a/routing/turns_sound.cpp b/routing/turns_sound.cpp deleted file mode 100644 index dd1686cbb4..0000000000 --- a/routing/turns_sound.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "routing/turns_sound.hpp" - -#include "platform/location.hpp" - -namespace -{ -uint32_t CalculateDistBeforeMeters(double speedMetersPerSecond, uint32_t startBeforeSeconds, - uint32_t minStartBeforeMeters, uint32_t maxStartBeforeMeters) -{ - ASSERT_LESS_OR_EQUAL(0, speedMetersPerSecond, ()); - uint32_t const startBeforeMeters = - static_cast(speedMetersPerSecond * startBeforeSeconds); - return my::clamp(startBeforeMeters, minStartBeforeMeters, maxStartBeforeMeters); -} - -// Returns true if the closest turn is an entrance to a roundabout and the second is -// an exit form a roundabout. -// Note. There are some cases when another turn (besides an exit from roundabout) -// follows an entrance to a roundabout. It could happend in case of turns inside a roundabout. -// Returns false otherwise. -bool IsClassicEntranceToRoundabout(routing::turns::TurnItemDist const & firstTurn, - routing::turns::TurnItemDist const & secondTurn) -{ - return firstTurn.m_turnItem.m_turn == routing::turns::TurnDirection::EnterRoundAbout - && secondTurn.m_turnItem.m_turn == routing::turns::TurnDirection::LeaveRoundAbout; -} -} // namespace - -namespace routing -{ -namespace turns -{ -namespace sound -{ -string TurnsSound::GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, bool useThenInsteadOfDistance, - TurnDirection turnDir, ::Settings::Units lengthUnits) const -{ - Notification const notification(distanceUnits, exitNum, useThenInsteadOfDistance, turnDir, lengthUnits); - return m_getTtsText(notification); -} - -void TurnsSound::GenerateTurnSound(vector const & turns, vector & turnNotifications) -{ - turnNotifications.clear(); - - if (!m_enabled || turns.empty()) - return; - - TurnItemDist const & firstTurn = turns.front(); - string firstNotification = GenerateFirstTurnSound(firstTurn.m_turnItem, firstTurn.m_distMeters); - if (m_nextTurnNotificationProgress == PronouncedNotification::Nothing) - return; - if (firstNotification.empty()) - return; - turnNotifications.emplace_back(move(firstNotification)); - - // Generating notifications like "Then turn left" if necessary. - if (turns.size() < 2) - return; - TurnItemDist const & secondTurn = turns[1]; - ASSERT_LESS_OR_EQUAL(firstTurn.m_distMeters, secondTurn.m_distMeters, ()); - if (secondTurn.m_distMeters - firstTurn.m_distMeters > kMaxTurnDistM - && !IsClassicEntranceToRoundabout(firstTurn, secondTurn)) - { - return; - } - string secondNotification = GenerateTurnText(0 /* distanceUnits is not used because of "Then" is used */, - secondTurn.m_turnItem.m_exitNum, true, - secondTurn.m_turnItem.m_turn, - m_settings.GetLengthUnits()); - if (secondNotification.empty()) - return; - turnNotifications.emplace_back(move(secondNotification)); - // Turn notification with word "Then" (about the second turn) will be pronounced. - // When this second turn become the first one the first notification about the turn - // shall be skipped. - m_turnNotificationWithThen = true; -} - -string TurnsSound::GenerateFirstTurnSound(TurnItem const & turn, double distanceToTurnMeters) -{ - if (m_nextTurnIndex != turn.m_index) - { - m_nextTurnNotificationProgress = PronouncedNotification::Nothing; - m_nextTurnIndex = turn.m_index; - } - - uint32_t const distanceToPronounceNotificationMeters = - CalculateDistBeforeMeters(m_speedMetersPerSecond, m_startBeforeSeconds, - m_minStartBeforeMeters, m_maxStartBeforeMeters); - - if (m_nextTurnNotificationProgress == PronouncedNotification::Nothing) - { - if (distanceToTurnMeters > m_minDistToSayNotificationMeters) - { - double const currentSpeedUntisPerSecond = - m_settings.ConvertMetersPerSecondToUnitsPerSecond(m_speedMetersPerSecond); - double const turnNotificationDistUnits = - m_settings.ComputeTurnDistance(currentSpeedUntisPerSecond); - uint32_t const startPronounceDistMeters = - m_settings.ConvertUnitsToMeters(turnNotificationDistUnits) + distanceToPronounceNotificationMeters; - - if (distanceToTurnMeters < startPronounceDistMeters) - { - if (m_turnNotificationWithThen) - { - FastForwardFirstTurnNotification(); - } - else - { - double const distToPronounceMeters = distanceToTurnMeters - distanceToPronounceNotificationMeters; - if (distToPronounceMeters < 0) - { - FastForwardFirstTurnNotification(); - return string(); // The current position is too close to the turn for the first notification. - } - - // Pronouncing first turn sound notification. - double const distToPronounceUnits = m_settings.ConvertMetersToUnits(distToPronounceMeters); - uint32_t const roundedDistToPronounceUnits = - m_settings.RoundByPresetSoundedDistancesUnits(distToPronounceUnits); - m_nextTurnNotificationProgress = PronouncedNotification::First; - return GenerateTurnText(roundedDistToPronounceUnits, turn.m_exitNum, false /* useThenInsteadOfDistance */, - turn.m_turn, m_settings.GetLengthUnits()); - } - } - } - else - { - // The first notification has not been pronounced but the distance to the turn is too short. - // It happens if one turn follows shortly behind another one. - m_nextTurnNotificationProgress = PronouncedNotification::First; - FastForwardFirstTurnNotification(); - } - return string(); - } - - if (m_nextTurnNotificationProgress == PronouncedNotification::First && - distanceToTurnMeters < distanceToPronounceNotificationMeters) - { - m_nextTurnNotificationProgress = PronouncedNotification::Second; - FastForwardFirstTurnNotification(); - return GenerateTurnText(0 /* distanceUnits */, turn.m_exitNum, - false /* useThenInsteadOfDistance */, - turn.m_turn, m_settings.GetLengthUnits()); - } - return string(); -} - -void TurnsSound::Enable(bool enable) -{ - if (enable && !m_enabled) - Reset(); - m_enabled = enable; -} - -void TurnsSound::SetLengthUnits(::Settings::Units units) -{ - m_settings.SetLengthUnits(units); - switch(units) - { - case ::Settings::Metric: - m_settings = Settings(30 /* notificationTimeSeconds */, 200 /* minNotificationDistanceUnits */, - 2000 /* maxNotificationDistanceUnits */, - GetSoundedDistMeters() /* soundedDistancesUnits */, - ::Settings::Metric /* lengthUnits */); - return; - case ::Settings::Foot: - m_settings = Settings(30 /* notificationTimeSeconds */, 500 /* minNotificationDistanceUnits */, - 5000 /* maxNotificationDistanceUnits */, - GetSoundedDistFeet() /* soundedDistancesUnits */, - ::Settings::Foot /* lengthUnits */); - return; - } -} - -void TurnsSound::SetSpeedMetersPerSecond(double speed) -{ - // When the quality of GPS data is bad the current speed may be less then zero. - // It's easy to reproduce at an office with Nexus 5. - // In that case zero meters per second is used. - m_speedMetersPerSecond = max(0., speed); -} - -void TurnsSound::Reset() -{ - m_nextTurnNotificationProgress = PronouncedNotification::Nothing; - m_nextTurnIndex = 0; - m_turnNotificationWithThen = false; -} - -void TurnsSound::FastForwardFirstTurnNotification() -{ - m_turnNotificationWithThen = false; - if (m_nextTurnNotificationProgress == PronouncedNotification::Nothing) - m_nextTurnNotificationProgress = PronouncedNotification::First; -} - -string DebugPrint(PronouncedNotification const notificationProgress) -{ - switch (notificationProgress) - { - case PronouncedNotification::Nothing: - return "Nothing"; - case PronouncedNotification::First: - return "First"; - case PronouncedNotification::Second: - return "Second"; - } - - ASSERT(false, ()); - stringstream out; - out << "unknown PronouncedNotification (" << static_cast(notificationProgress) << ")"; - return out.str(); -} -} // namespace sound -} // namespace turns -} // namespace routing diff --git a/routing/turns_sound.hpp b/routing/turns_sound.hpp deleted file mode 100644 index ff2855673f..0000000000 --- a/routing/turns_sound.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once - -#include "routing/turns_sound_settings.hpp" -#include "routing/turns_tts_text.hpp" - -#include "platform/settings.hpp" - -#include "std/string.hpp" - -namespace location -{ -class FollowingInfo; -} - -namespace routing -{ -namespace turns -{ -namespace sound -{ -/// \brief The PronouncedNotification enum represents which sound notifications -/// for turns were heard. -enum class PronouncedNotification -{ - Nothing, - First, - Second /** The second notification just before the turn was pronounced. */ -}; - -string DebugPrint(PronouncedNotification const notificationProgress); - -/// \brief The TurnsSound class is responsible for all route turn sound notifications functionality. -/// To be able to generate turn sound notification the class needs to have correct Settings -/// and relevant speed. -class TurnsSound -{ - friend void UnitTest_TurnsSoundMetersTest(); - friend void UnitTest_TurnsSoundMetersTwoTurnsTest(); - friend void UnitTest_TurnsSoundFeetTest(); - friend void UnitTest_TurnsSoundComposedTurnTest(); - friend void UnitTest_TurnsSoundRoundaboutTurnTest(); - - /// \brief The private contructor is used only for testing. - TurnsSound(uint32_t startBeforeSeconds, uint32_t minStartBeforeMeters, - uint32_t maxStartBeforeMeters, uint32_t minDistToSayNotificationMeters) - : m_enabled(false), m_speedMetersPerSecond(0.), m_settings(), - m_nextTurnNotificationProgress(PronouncedNotification::Nothing), - m_turnNotificationWithThen(false), m_nextTurnIndex(0), - m_startBeforeSeconds(startBeforeSeconds), m_minStartBeforeMeters(minStartBeforeMeters), - m_maxStartBeforeMeters(maxStartBeforeMeters), - m_minDistToSayNotificationMeters(minDistToSayNotificationMeters) - { - ASSERT_LESS_OR_EQUAL(m_minStartBeforeMeters, m_maxStartBeforeMeters, ()); - } - - /// m_enabled == true when tts is turned on. - /// Important! Clients (iOS/Android) implies that m_enabled is false by default. - bool m_enabled; - /// In m_speedMetersPerSecond is intended for some speed which will be used for - /// convertion a distance in seconds to distance in meters. It could be a current - /// an end user speed or an average speed for last several seconds. - /// @TODO(vbykoianko) It's better to use an average speed - /// for last several seconds instead of the current speed here. - double m_speedMetersPerSecond; - Settings m_settings; - /// m_nextTurnNotificationProgress keeps a status which is being changing while - /// an end user is coming to the closest (the next) turn along the route. - /// When an end user is far from the next turn - /// m_nextTurnNotificationProgress == Nothing. - /// After the first turn notification has been pronounced - /// m_nextTurnNotificationProgress == First. - /// After the second notification has been pronounced - /// m_nextTurnNotificationProgress == Second. - PronouncedNotification m_nextTurnNotificationProgress; - /// The flag is set to true if notification about the second turn was pronounced. - /// It could happen in expressions like "Turn right. Then turn left." - /// This flag is used to pass the information if second turn notification was pronounced - /// between two calls of GenerateTurnSound() method. - bool m_turnNotificationWithThen; - uint32_t m_nextTurnIndex; - /// getTtsText is a convector form turn notification information and locale to - /// notification string. - GetTtsText m_getTtsText; - - string GenerateTurnText(uint32_t distanceUnits, uint8_t exitNum, bool useThenInsteadOfDistance, - TurnDirection turnDir, ::Settings::Units lengthUnits) const; - /// Generates turn sound notification for the nearest to the current position turn. - string GenerateFirstTurnSound(TurnItem const & turn, double distanceToTurnMeters); - /// Changes the state of the class to emulate that first turn notification is pronouned - /// without pronunciation. - void FastForwardFirstTurnNotification(); - - // To inform an end user about the next turn with the help of an voice information message - // an operation system needs: - // - to launch TTS subsystem; - // - to pronounce the message. - // So to inform the user in time it's necessary to start - // m_startBeforeSeconds before the time. It is used in the following way: - // we start playing voice notice in m_startBeforeSeconds * TurnsSound::m_speedMetersPerSecond - // meters before the turn (for the second voice notification). - // When m_startBeforeSeconds * TurnsSound::m_speedMetersPerSecond is too small or too large - // we use m_minStartBeforeMeters or m_maxStartBeforeMeters correspondingly. - uint32_t const m_startBeforeSeconds; - uint32_t const m_minStartBeforeMeters; - uint32_t const m_maxStartBeforeMeters; - - // m_minDistToSayNotificationMeters is minimum distance between two turns - // when pronouncing the first notification about the second turn makes sense. - uint32_t const m_minDistToSayNotificationMeters; - -public: - TurnsSound() : m_enabled(false), m_speedMetersPerSecond(0.), m_settings(), - m_nextTurnNotificationProgress(PronouncedNotification::Nothing), - m_turnNotificationWithThen(false), m_nextTurnIndex(0), - m_startBeforeSeconds(5), m_minStartBeforeMeters(25), m_maxStartBeforeMeters(150), - m_minDistToSayNotificationMeters(170) - { - ASSERT_LESS_OR_EQUAL(m_minStartBeforeMeters, m_maxStartBeforeMeters, ()); - } - - bool IsEnabled() const { return m_enabled; } - void Enable(bool enable); - void SetLengthUnits(::Settings::Units units); - inline ::Settings::Units GetLengthUnits() const { return m_settings.GetLengthUnits(); } - inline void SetLocale(string const & locale) { m_getTtsText.SetLocale(locale); } - inline string GetLocale() const { return m_getTtsText.GetLocale(); } - void SetSpeedMetersPerSecond(double speed); - - /// \brief GenerateTurnSound updates information about the next turn notification. - /// It also fills turnNotifications when it's necessary. - /// If this TurnsSound wants to play a sound message once it should push one item to - /// the vector turnNotifications once when GenerateTurnSound is called. - /// \param turn contains information about the next turn. - /// \param distanceToTurnMeters is distance to the next turn in meters. - /// \param turnNotifications is a parameter to fill it if it's necessary. - /// \note The client implies turnNotifications does not contain empty strings. - void GenerateTurnSound(vector const & turns, vector & turnNotifications); - /// Reset states which reflects current route position. - /// The method shall be called after creating a new route or after rerouting. - void Reset(); -}; -} // namespace sound -} // namespace turns -} // namespace routing diff --git a/routing/turns_sound_settings.cpp b/routing/turns_sound_settings.cpp index 4cc717e0b1..98f8db6e1e 100644 --- a/routing/turns_sound_settings.cpp +++ b/routing/turns_sound_settings.cpp @@ -12,6 +12,18 @@ namespace turns { namespace sound { +void Settings::SetState(uint32_t notificationTimeSeconds, uint32_t minNotificationDistanceUnits, + uint32_t maxNotificationDistanceUnits, + vector const & soundedDistancesUnits, + ::Settings::Units lengthUnits) +{ + m_timeSeconds = notificationTimeSeconds; + m_minDistanceUnits = minNotificationDistanceUnits; + m_maxDistanceUnits = maxNotificationDistanceUnits; + m_soundedDistancesUnits = soundedDistancesUnits; + m_lengthUnits = lengthUnits; +} + bool Settings::IsValid() const { return m_minDistanceUnits <= m_maxDistanceUnits && @@ -19,12 +31,19 @@ bool Settings::IsValid() const is_sorted(m_soundedDistancesUnits.cbegin(), m_soundedDistancesUnits.cend()); } -double Settings::ComputeTurnDistance(double speedUnitsPerSecond) const +uint32_t Settings::ComputeTurnDistanceM(double speedMetersPerSecond) const { ASSERT(IsValid(), ()); - double const turnNotificationDistance = m_timeSeconds * speedUnitsPerSecond; - return my::clamp(turnNotificationDistance, m_minDistanceUnits, m_maxDistanceUnits); + double const turnNotificationDistanceM = m_timeSeconds * speedMetersPerSecond; + return static_cast(my::clamp(turnNotificationDistanceM, + ConvertUnitsToMeters(m_minDistanceUnits), + ConvertUnitsToMeters(m_maxDistanceUnits))); +} + +bool Settings::TooCloseForFisrtNotification(double distToTurnMeters) const +{ + return m_minDistToSayNotificationMeters >= distToTurnMeters; } uint32_t Settings::RoundByPresetSoundedDistancesUnits(uint32_t turnNotificationUnits) const @@ -83,6 +102,14 @@ double Settings::ConvertMetersToUnits(double distanceInMeters) const return 0.; } +uint32_t Settings::ComputeDistToPronounceDistM(double speedMetersPerSecond) const +{ + ASSERT_LESS_OR_EQUAL(0, speedMetersPerSecond, ()); + uint32_t const startBeforeMeters = + static_cast(speedMetersPerSecond * m_startBeforeSeconds); + return my::clamp(startBeforeMeters, m_minStartBeforeMeters, m_maxStartBeforeMeters); +} + string DebugPrint(Notification const & notification) { stringstream out; diff --git a/routing/turns_sound_settings.hpp b/routing/turns_sound_settings.hpp index a2e1708ceb..3e7442f7bd 100644 --- a/routing/turns_sound_settings.hpp +++ b/routing/turns_sound_settings.hpp @@ -14,47 +14,95 @@ namespace sound { /// \brief The Settings struct is a structure for gathering information about turn sound /// notifications settings. -/// All distance parameters shall be set in m_lengthUnits. (Meters of feet for the time being.) +/// Part distance parameters shall be set in m_lengthUnits. (Meters of feet for the time being.) +/// Another part in meters. See the suffix to understand which units are used. class Settings { + friend void UnitTest_TurnNotificationSettingsMetersTest(); + friend void UnitTest_TurnNotificationSettingsFeetTest(); + friend void UnitTest_TurnNotificationSettingsNotValidTest(); + uint32_t m_timeSeconds; uint32_t m_minDistanceUnits; uint32_t m_maxDistanceUnits; + /// To inform an end user about the next turn with the help of an voice information message + /// an operation system needs: + /// - to launch TTS subsystem; + /// - to pronounce the message. + /// So to inform the user in time it's necessary to start + /// m_startBeforeSeconds before the time. It is used in the following way: + /// we start playing voice notice in m_startBeforeSeconds * TurnsSound::m_speedMetersPerSecond + /// meters before the turn (for the second voice notification). + /// When m_startBeforeSeconds * TurnsSound::m_speedMetersPerSecond is too small or too large + /// we use m_{min|max}StartBeforeMeters to clamp the value. + uint32_t m_startBeforeSeconds; + uint32_t m_minStartBeforeMeters; + uint32_t m_maxStartBeforeMeters; + + /// m_minDistToSayNotificationMeters is minimum distance between two turns + /// when pronouncing the first notification about the second turn makes sense. + uint32_t m_minDistToSayNotificationMeters; + /// \brief m_distancesToPronounce is a list of distances in m_lengthUnits /// which are ready to be pronounced. vector m_soundedDistancesUnits; ::Settings::Units m_lengthUnits; -public: - Settings() - : m_minDistanceUnits(0), - m_maxDistanceUnits(0), - m_soundedDistancesUnits(), - m_lengthUnits(::Settings::Metric) {} + // This constructor is for testing only. Settings(uint32_t notificationTimeSeconds, uint32_t minNotificationDistanceUnits, - uint32_t maxNotificationDistanceUnits, vector const & soundedDistancesUnits, + uint32_t maxNotificationDistanceUnits, uint32_t startBeforeSeconds, + uint32_t minStartBeforeMeters, uint32_t maxStartBeforeMeters, + uint32_t minDistToSayNotificationMeters, vector const & soundedDistancesUnits, ::Settings::Units lengthUnits) - : m_timeSeconds(notificationTimeSeconds), - m_minDistanceUnits(minNotificationDistanceUnits), - m_maxDistanceUnits(maxNotificationDistanceUnits), - m_soundedDistancesUnits(soundedDistancesUnits), - m_lengthUnits(lengthUnits) + : m_timeSeconds(notificationTimeSeconds) + , m_minDistanceUnits(minNotificationDistanceUnits) + , m_maxDistanceUnits(maxNotificationDistanceUnits) + , m_startBeforeSeconds(startBeforeSeconds) + , m_minStartBeforeMeters(minStartBeforeMeters) + , m_maxStartBeforeMeters(maxStartBeforeMeters) + , m_minDistToSayNotificationMeters(minDistToSayNotificationMeters) + , m_soundedDistancesUnits(soundedDistancesUnits) + , m_lengthUnits(lengthUnits) { ASSERT(!m_soundedDistancesUnits.empty(), ()); } +public: + Settings(uint32_t startBeforeSeconds, uint32_t minStartBeforeMeters, + uint32_t maxStartBeforeMeters, uint32_t minDistToSayNotificationMeters) + : m_timeSeconds(0) + , m_minDistanceUnits(0) + , m_maxDistanceUnits(0) + , m_startBeforeSeconds(startBeforeSeconds) + , m_minStartBeforeMeters(minStartBeforeMeters) + , m_maxStartBeforeMeters(maxStartBeforeMeters) + , m_minDistToSayNotificationMeters(minDistToSayNotificationMeters) + , m_lengthUnits(::Settings::Metric) + { + } + + void SetState(uint32_t notificationTimeSeconds, uint32_t minNotificationDistanceUnits, + uint32_t maxNotificationDistanceUnits, + vector const & soundedDistancesUnits, ::Settings::Units lengthUnits); + /// \brief IsValid checks if Settings data is consistent. /// \warning The complexity is up to linear in size of m_soundedDistancesUnits. /// \note For any instance created by default constructor IsValid() returns false. bool IsValid() const; - /// \brief computes the distance an end user shall be informed about the future turn - /// before it, taking into account speedMetersPerSecond and fields of the structure. + /// \brief computes the distance an end user shall be informed about the future turn. /// \param speedMetersPerSecond is a speed. For example it could be a current speed of an end /// user. - /// \return distance in units which are set in m_lengthUnits. (Meters of feet for the time being.) - double ComputeTurnDistance(double speedUnitsPerSecond) const; + /// \return distance in meters. + uint32_t ComputeTurnDistanceM(double speedMetersPerSecond) const; + + /// \brief computes the distance which will be passed at the |speedMetersPerSecond| + /// while pronouncing turn sound notification. + uint32_t ComputeDistToPronounceDistM(double speedMetersPerSecond) const; + + /// @return true if distToTurnMeters is too short to start pronouncing first turn notification. + bool TooCloseForFisrtNotification(double distToTurnMeters) const; /// \brief RoundByPresetSoundedDistancesUnits rounds off its parameter by /// m_soundedDistancesUnits.