File indexing completed on 2024-04-28 03:49:31
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2012 Dennis Nienhüser <nienhueser@kde.org> 0004 // 0005 0006 #include "VoiceNavigationModel.h" 0007 0008 #include "Route.h" 0009 0010 #include "MarbleDirs.h" 0011 #include "MarbleDebug.h" 0012 0013 namespace Marble 0014 { 0015 0016 class VoiceNavigationModelPrivate 0017 { 0018 public: 0019 0020 struct Announcement 0021 { 0022 bool announcementDone; 0023 bool turnInstructionDone; 0024 0025 Announcement(){ 0026 announcementDone = false; 0027 turnInstructionDone = false; 0028 } 0029 }; 0030 0031 VoiceNavigationModel* m_parent; 0032 0033 QString m_speaker; 0034 0035 bool m_speakerEnabled; 0036 0037 QMap<Maneuver::Direction, QString> m_turnTypeMap; 0038 0039 QMap<Maneuver::Direction, QString> m_announceMap; 0040 0041 qreal m_lastDistance; 0042 0043 qreal m_lastDistanceTraversed; 0044 0045 GeoDataLineString m_lastRoutePath; 0046 0047 Maneuver::Direction m_lastTurnType; 0048 0049 GeoDataCoordinates m_lastTurnPoint; 0050 0051 QStringList m_queue; 0052 0053 QString m_announcementText; 0054 0055 bool m_destinationReached; 0056 0057 bool m_deviated; 0058 0059 QVector<Announcement> m_announcementList; 0060 0061 explicit VoiceNavigationModelPrivate( VoiceNavigationModel* parent ); 0062 0063 void reset(); 0064 0065 QString audioFile(const QString &name) const; 0066 0067 QString distanceAudioFile( qreal dest ) const; 0068 0069 QString turnTypeAudioFile( Maneuver::Direction turnType, qreal distance ); 0070 0071 QString announcementText( Maneuver::Direction turnType, qreal distance ); 0072 0073 void updateInstruction(const RouteSegment &segment, qreal distance, Maneuver::Direction turnType ); 0074 0075 void updateInstruction( const QString &name ); 0076 0077 void initializeMaps(); 0078 }; 0079 0080 VoiceNavigationModelPrivate::VoiceNavigationModelPrivate( VoiceNavigationModel* parent ) : 0081 m_parent( parent ), 0082 m_speakerEnabled( true ), 0083 m_lastDistance( 0.0 ), 0084 m_lastDistanceTraversed( 0.0 ), 0085 m_lastTurnType( Maneuver::Unknown ), 0086 m_destinationReached( false ), 0087 m_deviated( false ) 0088 { 0089 initializeMaps(); 0090 } 0091 0092 void VoiceNavigationModelPrivate::reset() 0093 { 0094 m_lastDistance = 0.0; 0095 m_lastDistanceTraversed = 0.0; 0096 } 0097 0098 QString VoiceNavigationModelPrivate::audioFile( const QString &name ) const 0099 { 0100 #ifdef Q_OS_ANDROID 0101 return name; 0102 #else 0103 QStringList const formats = QStringList() << "ogg" << "mp3" << "wav"; 0104 if ( m_speakerEnabled ) { 0105 QString const audioTemplate = "%1/%2.%3"; 0106 for( const QString &format: formats ) { 0107 QString const result = audioTemplate.arg( m_speaker, name, format ); 0108 QFileInfo audioFile( result ); 0109 if ( audioFile.exists() ) { 0110 return result; 0111 } 0112 } 0113 } 0114 0115 QString const audioTemplate = "audio/%1.%2"; 0116 for( const QString &format: formats ) { 0117 QString const result = MarbleDirs::path( audioTemplate.arg( name, format ) ); 0118 if ( !result.isEmpty() ) { 0119 return result; 0120 } 0121 } 0122 0123 return QString(); 0124 #endif 0125 } 0126 0127 QString VoiceNavigationModelPrivate::distanceAudioFile( qreal dest ) const 0128 { 0129 if ( dest > 0.0 && dest < 900.0 ) { 0130 qreal minDistance = 0.0; 0131 int targetDistance = 0; 0132 QVector<int> distances; 0133 distances << 50 << 80 << 100 << 200 << 300 << 400 << 500 << 600 << 700 << 800; 0134 for( int distance: distances ) { 0135 QString file = audioFile( QString::number( distance ) ); 0136 qreal currentDistance = qAbs( distance - dest ); 0137 if ( !file.isEmpty() && ( minDistance == 0.0 || currentDistance < minDistance ) ) { 0138 minDistance = currentDistance; 0139 targetDistance = distance; 0140 } 0141 } 0142 0143 if ( targetDistance > 0 ) { 0144 return audioFile( QString::number( targetDistance ) ); 0145 } 0146 } 0147 0148 return QString(); 0149 } 0150 0151 QString VoiceNavigationModelPrivate::turnTypeAudioFile( Maneuver::Direction turnType, qreal distance ) 0152 { 0153 bool const announce = distance >= 75; 0154 QMap<Maneuver::Direction, QString> const & map = announce ? m_announceMap : m_turnTypeMap; 0155 if ( m_speakerEnabled && map.contains( turnType ) ) { 0156 return audioFile( map[turnType] ); 0157 } 0158 0159 return audioFile( announce ? "ListEnd" : "AppPositive" ); 0160 } 0161 0162 QString VoiceNavigationModelPrivate::announcementText( Maneuver::Direction turnType, qreal distance ) 0163 { 0164 QString announcementText = QString(""); 0165 if (distance >= 75) { 0166 announcementText = QString("In "+distanceAudioFile(distance)+" meters, "); 0167 } 0168 switch (turnType) { 0169 case Maneuver::Continue: 0170 case Maneuver::Straight: 0171 announcementText += QString("Continue straight"); 0172 break; 0173 case Maneuver::SlightRight: 0174 announcementText += QString("Turn slight right"); 0175 break; 0176 case Maneuver::SlightLeft: 0177 announcementText += QString("Turn slight left"); 0178 break; 0179 case Maneuver::Right: 0180 case Maneuver::SharpRight: 0181 announcementText += QString("Turn right"); 0182 break; 0183 case Maneuver::Left: 0184 case Maneuver::SharpLeft: 0185 announcementText += QString("Turn left"); 0186 break; 0187 case Maneuver::TurnAround: 0188 announcementText += QString("Take a U-turn"); 0189 break; 0190 case Maneuver::ExitLeft: 0191 announcementText += QString("Exit left"); 0192 break; 0193 case Maneuver::ExitRight: 0194 announcementText += QString("Exit right"); 0195 break; 0196 case Maneuver::RoundaboutFirstExit: 0197 announcementText += QString("Take the first exit"); 0198 break; 0199 case Maneuver::RoundaboutSecondExit: 0200 announcementText += QString("Take the second exit"); 0201 break; 0202 case Maneuver::RoundaboutThirdExit: 0203 announcementText += QString("Take the third exit"); 0204 break; 0205 default: 0206 announcementText = QString(""); 0207 break; 0208 } 0209 return announcementText; 0210 } 0211 0212 void VoiceNavigationModelPrivate::updateInstruction( const RouteSegment & segment, qreal distance, Maneuver::Direction turnType ) 0213 { 0214 QString turnTypeAudio = turnTypeAudioFile( turnType, distance ); 0215 if ( turnTypeAudio.isEmpty() ) { 0216 mDebug() << "Missing audio file for turn type " << turnType << " and speaker " << m_speaker; 0217 return; 0218 } 0219 0220 m_queue.clear(); 0221 m_queue << turnTypeAudio; 0222 m_announcementText = announcementText(turnType, distance); 0223 qreal nextSegmentDistance = segment.nextRouteSegment().distance(); 0224 Maneuver::Direction nextSegmentDirection = segment.nextRouteSegment().nextRouteSegment().maneuver().direction(); 0225 if (!m_announcementText.isEmpty() && distance < 75 && nextSegmentDistance != 0 && nextSegmentDistance < 75) { 0226 QString nextSegmentAnnouncementText = announcementText(nextSegmentDirection, nextSegmentDistance); 0227 if (!nextSegmentAnnouncementText.isEmpty()) { 0228 m_announcementText += QLatin1String(", then ") + nextSegmentAnnouncementText; 0229 } 0230 } 0231 emit m_parent->instructionChanged(); 0232 } 0233 0234 void VoiceNavigationModelPrivate::updateInstruction( const QString &name ) 0235 { 0236 m_queue.clear(); 0237 m_queue << audioFile( name ); 0238 m_announcementText = name; 0239 emit m_parent->instructionChanged(); 0240 } 0241 0242 void VoiceNavigationModelPrivate::initializeMaps() 0243 { 0244 m_turnTypeMap.clear(); 0245 m_announceMap.clear(); 0246 0247 m_announceMap[Maneuver::Continue] = "Straight"; 0248 // none of our voice navigation commands fits, so leave out 0249 // Maneuver::Merge here to have a sound play instead 0250 m_announceMap[Maneuver::Straight] = "Straight"; 0251 m_announceMap[Maneuver::SlightRight] = "AhKeepRight"; 0252 m_announceMap[Maneuver::Right] = "AhRightTurn"; 0253 m_announceMap[Maneuver::SharpRight] = "AhRightTurn"; 0254 m_announceMap[Maneuver::TurnAround] = "AhUTurn"; 0255 m_announceMap[Maneuver::SharpLeft] = "AhLeftTurn"; 0256 m_announceMap[Maneuver::Left] = "AhLeftTurn"; 0257 m_announceMap[Maneuver::SlightLeft] = "AhKeepLeft"; 0258 m_announceMap[Maneuver::RoundaboutFirstExit] = "RbExit1"; 0259 m_announceMap[Maneuver::RoundaboutSecondExit] = "RbExit2"; 0260 m_announceMap[Maneuver::RoundaboutThirdExit] = "RbExit3"; 0261 m_announceMap[Maneuver::ExitLeft] = "AhExitLeft"; 0262 m_announceMap[Maneuver::ExitRight] = "AhExitRight"; 0263 0264 m_turnTypeMap[Maneuver::Continue] = "Straight"; 0265 // none of our voice navigation commands fits, so leave out 0266 // Maneuver::Merge here to have a sound play instead 0267 m_turnTypeMap[Maneuver::Straight] = "Straight"; 0268 m_turnTypeMap[Maneuver::SlightRight] = "BearRight"; 0269 m_turnTypeMap[Maneuver::Right] = "TurnRight"; 0270 m_turnTypeMap[Maneuver::SharpRight] = "SharpRight"; 0271 m_turnTypeMap[Maneuver::TurnAround] = "UTurn"; 0272 m_turnTypeMap[Maneuver::SharpLeft] = "SharpLeft"; 0273 m_turnTypeMap[Maneuver::Left] = "TurnLeft"; 0274 m_turnTypeMap[Maneuver::SlightLeft] = "BearLeft"; 0275 m_turnTypeMap[Maneuver::RoundaboutFirstExit] = ""; 0276 m_turnTypeMap[Maneuver::RoundaboutSecondExit] = ""; 0277 m_turnTypeMap[Maneuver::RoundaboutThirdExit] = ""; 0278 m_turnTypeMap[Maneuver::ExitLeft] = "TurnLeft"; 0279 m_turnTypeMap[Maneuver::ExitRight] = "TurnRight"; 0280 } 0281 0282 VoiceNavigationModel::VoiceNavigationModel( QObject *parent ) : 0283 QObject( parent ), d( new VoiceNavigationModelPrivate( this ) ) 0284 { 0285 // nothing to do 0286 } 0287 0288 VoiceNavigationModel::~VoiceNavigationModel() 0289 { 0290 delete d; 0291 } 0292 0293 QString VoiceNavigationModel::speaker() const 0294 { 0295 return d->m_speaker; 0296 } 0297 0298 void VoiceNavigationModel::setSpeaker(const QString &speaker) 0299 { 0300 if ( speaker != d->m_speaker ) { 0301 QFileInfo speakerDir = QFileInfo( speaker ); 0302 if ( !speakerDir.exists() ) { 0303 d->m_speaker = MarbleDirs::path(QLatin1String("/audio/speakers/") + speaker); 0304 } else { 0305 d->m_speaker = speaker; 0306 } 0307 0308 emit speakerChanged(); 0309 emit previewChanged(); 0310 } 0311 } 0312 0313 bool VoiceNavigationModel::isSpeakerEnabled() const 0314 { 0315 return d->m_speakerEnabled; 0316 } 0317 0318 void VoiceNavigationModel::setSpeakerEnabled( bool enabled ) 0319 { 0320 if ( enabled != d->m_speakerEnabled ) { 0321 d->m_speakerEnabled = enabled; 0322 emit isSpeakerEnabledChanged(); 0323 emit previewChanged(); 0324 } 0325 } 0326 0327 void VoiceNavigationModel::reset() 0328 { 0329 d->reset(); 0330 } 0331 0332 void VoiceNavigationModel::update(const Route &route, qreal distanceManuever, qreal distanceTarget, bool deviated ) 0333 { 0334 if (d->m_lastRoutePath != route.path()){ 0335 d->m_announcementList.clear(); 0336 d->m_announcementList.resize(route.size()); 0337 d->m_lastRoutePath = route.path(); 0338 } 0339 0340 if ( d->m_destinationReached && distanceTarget < 250 ) { 0341 return; 0342 } 0343 0344 if ( !d->m_destinationReached && distanceTarget < 50 ) { 0345 d->m_destinationReached = true; 0346 d->updateInstruction( d->m_speakerEnabled ? "You have arrived at your destination" : "AppPositive" ); 0347 return; 0348 } 0349 0350 if ( distanceTarget > 150 ) { 0351 d->m_destinationReached = false; 0352 } 0353 0354 if ( deviated && !d->m_deviated ) { 0355 d->updateInstruction( d->m_speakerEnabled ? "Deviated from the route" : "ListEnd" ); 0356 } 0357 d->m_deviated = deviated; 0358 if ( deviated ) { 0359 return; 0360 } 0361 0362 Maneuver::Direction turnType = route.currentSegment().nextRouteSegment().maneuver().direction(); 0363 if ( !( d->m_lastTurnPoint == route.currentSegment().nextRouteSegment().maneuver().position() ) || turnType != d->m_lastTurnType ) { 0364 d->m_lastTurnPoint = route.currentSegment().nextRouteSegment().maneuver().position(); 0365 d->reset(); 0366 } 0367 0368 int index = route.indexOf(route.currentSegment()); 0369 0370 qreal const distanceTraversed = route.currentSegment().distance() - distanceManuever; 0371 bool const minDistanceTraversed = d->m_lastDistanceTraversed < 40 && distanceTraversed >= 40; 0372 bool const announcementAfterTurn = minDistanceTraversed && distanceManuever >= 75; 0373 bool const announcement = ( d->m_lastDistance > 850 || announcementAfterTurn ) && distanceManuever <= 850; 0374 bool const turn = ( d->m_lastDistance == 0 || d->m_lastDistance > 75 ) && distanceManuever <= 75; 0375 0376 bool const announcementDone = d->m_announcementList[index].announcementDone; 0377 bool const turnInstructionDone = d->m_announcementList[index].turnInstructionDone; 0378 0379 if ( ( announcement && !announcementDone ) || ( turn && !turnInstructionDone ) ) { 0380 d->updateInstruction( route.currentSegment(), distanceManuever, turnType ); 0381 VoiceNavigationModelPrivate::Announcement & curAnnouncement = d->m_announcementList[index]; 0382 if (announcement){ 0383 curAnnouncement.announcementDone = true; 0384 } 0385 if (turn){ 0386 curAnnouncement.turnInstructionDone = true; 0387 } 0388 } 0389 0390 d->m_lastTurnType = turnType; 0391 d->m_lastDistance = distanceManuever; 0392 d->m_lastDistanceTraversed = distanceTraversed; 0393 } 0394 0395 QString VoiceNavigationModel::preview() const 0396 { 0397 return d->audioFile( d->m_speakerEnabled ? "The Marble team wishes you a pleasant and safe journey!" : "AppPositive" ); 0398 } 0399 0400 QString VoiceNavigationModel::instruction() const 0401 { 0402 return d->m_announcementText; 0403 } 0404 0405 } 0406 0407 #include "moc_VoiceNavigationModel.cpp"