Merge pull request #311 from bykoianko/the-turn-after-the-next-fix6

Displaying the turn after the next simultaneously with pronouncing the info about the turn after the next.
This commit is contained in:
Lev Dragunov 2015-10-22 11:50:38 +03:00
commit f948035a01
16 changed files with 709 additions and 561 deletions

View file

@ -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<string> turnNotifications;
fr->GenerateTurnSound(turnNotifications);
fr->GenerateTurnNotifications(turnNotifications);
if (turnNotifications.empty())
return nullptr;

View file

@ -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);

View file

@ -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);
}

View file

@ -128,16 +128,16 @@ extern NSString * const kMwmTextToSpeechDisable = @"MWMTEXTTOSPEECH_DISABLE";
- (void)playTurnNotifications
{
if (![self isValid])
return;
Framework & frm = GetFramework();
if (!frm.IsRoutingActive())
return;
vector<string> notifications;
frm.GenerateTurnSound(notifications);
frm.GenerateTurnNotifications(notifications);
if (![self isValid])
return;
for (auto const & text : notifications)
[self speakOneString:@(text.c_str())];
}

View file

@ -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<string> & turnNotifications)
{ return m_routingSession.GenerateTurnSound(turnNotifications); }
/// GenerateTurnNotifications shall be called by the client when a new position is available.
inline void GenerateTurnNotifications(vector<string> & turnNotifications)
{
return m_routingSession.GenerateTurnNotifications(turnNotifications);
}
private:
void SetRouterImpl(routing::RouterType type);

View file

@ -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 \

View file

@ -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<string> & turnNotifications)
void RoutingSession::GenerateTurnNotifications(vector<string> & turnNotifications)
{
turnNotifications.clear();
@ -324,7 +304,7 @@ void RoutingSession::GenerateTurnSound(vector<string> & turnNotifications)
vector<turns::TurnItemDist> 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)

View file

@ -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<string> & turnNotifications);
void GenerateTurnNotifications(vector<string> & 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;

View file

@ -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<TurnItemDist> turns = {{{5 /* idx */, TurnDirection::TurnRight}, 1000.}};
vector<string> 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<string> 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<string> 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<TurnItemDist> turns = {{{5 /* idx */, TurnDirection::TurnSharpRight}, 800.}};
vector<string> 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<string> 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<string> 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<TurnItemDist> 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<string> 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<TurnItemDist> 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<string> 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<string> 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<string> turnNotifications;
// Starting nearing the first turn.
// 800 meters till the first turn.
vector<TurnItemDist> 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<string> 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<TurnItemDist> 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<string> 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<TurnItemDist> 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<TurnItemDist> const turns6 = {{{10 /* idx */, TurnDirection::EnterRoundAbout}, 10. /* m_distMeters */},
{{15 /* idx */, TurnDirection::ReachedYourDestination}, 1010. /* m_distMeters */}};
vector<string> 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<string> 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<TurnItemDist> const turns2 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */},
@ -441,8 +465,9 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest)
1620. /* m_distMeters */}};
vector<string> 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<TurnItemDist> const turns3 = {{{5 /* idx */, TurnDirection::EnterRoundAbout, 2 /* m_exitNum */},
@ -451,24 +476,27 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest)
1003. /* m_distMeters */}};
vector<string> 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<TurnItemDist> 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<TurnItemDist> 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<TurnItemDist> 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<string> 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<TurnItemDist> const turns7 = {{{15 /* idx */, TurnDirection::EnterRoundAbout, 1 /* m_exitNum */},
@ -486,18 +515,21 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest)
1005. /* m_distMeters */}};
vector<string> 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<TurnItemDist> 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<TurnItemDist> const turns9 = {{{25 /* idx */, TurnDirection::EnterRoundAbout, 4 /* m_exitNum */},
@ -506,8 +538,9 @@ UNIT_TEST(TurnsSoundRoundaboutTurnTest)
920. /* m_distMeters */}};
vector<string> 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)

View file

@ -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

View file

@ -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<TurnItemDist> const & turns,
vector<string> & 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<TurnItemDist> 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<int>(notificationProgress) << ")";
return out.str();
}
} // namespace sound
} // namespace turns
} // namespace routing

View file

@ -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<TurnItemDist> 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<TurnItemDist> const & turns,
vector<string> & 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

View file

@ -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<uint32_t>(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<TurnItemDist> const & turns, vector<string> & 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<int>(notificationProgress) << ")";
return out.str();
}
} // namespace sound
} // namespace turns
} // namespace routing

View file

@ -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<TurnItemDist> const & turns, vector<string> & 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

View file

@ -12,6 +12,18 @@ namespace turns
{
namespace sound
{
void Settings::SetState(uint32_t notificationTimeSeconds, uint32_t minNotificationDistanceUnits,
uint32_t maxNotificationDistanceUnits,
vector<uint32_t> 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<uint32_t>(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<uint32_t>(speedMetersPerSecond * m_startBeforeSeconds);
return my::clamp(startBeforeMeters, m_minStartBeforeMeters, m_maxStartBeforeMeters);
}
string DebugPrint(Notification const & notification)
{
stringstream out;

View file

@ -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<uint32_t> 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<uint32_t> const & soundedDistancesUnits,
uint32_t maxNotificationDistanceUnits, uint32_t startBeforeSeconds,
uint32_t minStartBeforeMeters, uint32_t maxStartBeforeMeters,
uint32_t minDistToSayNotificationMeters, vector<uint32_t> 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<uint32_t> 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.