]> git.mxchange.org Git - flightgear.git/commitdiff
Improvements to spoken ATIS
authorRichard Senior <richard.j.senior@gmail.com>
Fri, 8 Apr 2016 22:33:50 +0000 (23:33 +0100)
committerTorsten Dreyer <torsten@t3r.de>
Sun, 24 Apr 2016 08:42:26 +0000 (10:42 +0200)
- Add section tag to support inclusion of ATIS fragments.

- Add visibility, QNH and cloud tokens to support new ATIS formats.

- Add support for starts-with, ends-with and contains comparisons in
  conditionals, including negated versions.

- Strip and convert case in comparisons.

- Speak VRB wind direction as "variable".

- Speak zeroes in fractional part of QNH inHg.

- Force US voice in US, Canada and Pacific; UK voice in UK.

src/ATC/ATISEncoder.cxx
src/ATC/ATISEncoder.hxx
src/Instrumentation/commradio.cxx

index 45675207b68924cfd471553452a00093c2511d47..f24e4469a471dc00bc951db872ec7abcdc477dce 100644 (file)
@@ -116,6 +116,7 @@ ATISEncoder::ATISEncoder()
   handlerMap.insert( std::make_pair( "text", &ATISEncoder::processTextToken ));
   handlerMap.insert( std::make_pair( "token", &ATISEncoder::processTokenToken ));
   handlerMap.insert( std::make_pair( "if", &ATISEncoder::processIfToken ));
+  handlerMap.insert( std::make_pair( "section", &ATISEncoder::processTokens ));
 
   handlerMap.insert( std::make_pair( "id", &ATISEncoder::getAtisId ));
   handlerMap.insert( std::make_pair( "airport-name", &ATISEncoder::getAirportName ));
@@ -130,13 +131,17 @@ ATISEncoder::ATISEncoder()
   handlerMap.insert( std::make_pair( "wind-speed-kn", &ATISEncoder::getWindspeedKnots ));
   handlerMap.insert( std::make_pair( "gusts", &ATISEncoder::getGustsKnots ));
   handlerMap.insert( std::make_pair( "visibility-metric", &ATISEncoder::getVisibilityMetric ));
+  handlerMap.insert( std::make_pair( "visibility-miles", &ATISEncoder::getVisibilityMiles ));
   handlerMap.insert( std::make_pair( "phenomena", &ATISEncoder::getPhenomena ));
   handlerMap.insert( std::make_pair( "clouds", &ATISEncoder::getClouds ));
+  handlerMap.insert( std::make_pair( "clouds-brief", &ATISEncoder::getCloudsBrief ));
   handlerMap.insert( std::make_pair( "cavok", &ATISEncoder::getCavok ));
   handlerMap.insert( std::make_pair( "temperature-deg", &ATISEncoder::getTemperatureDeg ));
   handlerMap.insert( std::make_pair( "dewpoint-deg", &ATISEncoder::getDewpointDeg ));
   handlerMap.insert( std::make_pair( "qnh", &ATISEncoder::getQnh ));
   handlerMap.insert( std::make_pair( "inhg", &ATISEncoder::getInhg ));
+  handlerMap.insert( std::make_pair( "inhg-integer", &ATISEncoder::getInhgInteger ));
+  handlerMap.insert( std::make_pair( "inhg-fraction", &ATISEncoder::getInhgFraction ));
   handlerMap.insert( std::make_pair( "trend", &ATISEncoder::getTrend ));
 }
 
@@ -250,6 +255,8 @@ string ATISEncoder::processTokenToken( SGPropertyNode_ptr token )
 
 string ATISEncoder::processIfToken( SGPropertyNode_ptr token )
 {
+  using namespace simgear::strutils;
+
   SGPropertyNode_ptr n;
 
   if( (n = token->getChild("empty", false )).valid() ) {
@@ -264,14 +271,50 @@ string ATISEncoder::processIfToken( SGPropertyNode_ptr token )
            processTokens(token->getChild("else",false));
   }
 
+  if( (n = token->getChild("contains", false )).valid() ) {
+    return checkCondition( n, true, &contains, "contains") ?
+           processTokens(token->getChild("then",false)) :
+           processTokens(token->getChild("else",false));
+  }
+
+  if( (n = token->getChild("not-contains", false )).valid() ) {
+    return checkCondition( n, false, &contains, "not-contains") ?
+           processTokens(token->getChild("then",false)) :
+           processTokens(token->getChild("else",false));
+  }
+
+  if( (n = token->getChild("ends-with", false )).valid() ) {
+    return checkCondition( n, true, &ends_with, "ends-with") ?
+           processTokens(token->getChild("then",false)) :
+           processTokens(token->getChild("else",false));
+  }
+
+  if( (n = token->getChild("not-ends-with", false )).valid() ) {
+    return checkCondition( n, false, &ends_with, "not-ends-with") ?
+           processTokens(token->getChild("then",false)) :
+           processTokens(token->getChild("else",false));
+  }
+
   if( (n = token->getChild("equals", false )).valid() ) {
-    return checkEqualsCondition( n, true) ? 
+    return checkCondition( n, true, &equals, "equals") ?
            processTokens(token->getChild("then",false)) : 
            processTokens(token->getChild("else",false));
   }
 
   if( (n = token->getChild("not-equals", false )).valid() ) {
-    return checkEqualsCondition( n, false) ? 
+    return checkCondition( n, false, &equals, "not-equals") ?
+           processTokens(token->getChild("then",false)) :
+           processTokens(token->getChild("else",false));
+  }
+
+  if( (n = token->getChild("starts-with", false )).valid() ) {
+    return checkCondition( n, true, &starts_with, "starts-with") ?
+           processTokens(token->getChild("then",false)) :
+           processTokens(token->getChild("else",false));
+  }
+
+  if( (n = token->getChild("not-starts-with", false )).valid() ) {
+    return checkCondition( n, false, &starts_with, "not-starts-with") ?
            processTokens(token->getChild("then",false)) : 
            processTokens(token->getChild("else",false));
   }
@@ -291,27 +334,31 @@ bool ATISEncoder::checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty )
   return processToken( n1 ).empty() == isEmpty;
 }
 
-bool ATISEncoder::checkEqualsCondition( SGPropertyNode_ptr node, bool isEqual ) 
+bool ATISEncoder::checkCondition( SGPropertyNode_ptr node, bool notInverted,
+    bool (*fp)(const string &, const string &), const string &name )
 {
+  using namespace simgear::strutils;
+
   SGPropertyNode_ptr n1 = node->getNode( "token", 0, false );
   SGPropertyNode_ptr n2 = node->getNode( "token", 1, false );
 
   if( n1.valid() && n2.valid() ) {
-    bool comp = processToken( n1 ).compare( processToken( n2 ) ) == 0;
-    return comp == isEqual;
+    bool comp = fp( processToken( n1 ), processToken( n2 ) );
+    return comp == notInverted;
   }
 
   if( n1.valid() && !n2.valid() ) {
-    SGPropertyNode_ptr t = node->getNode( "text", 0, false );
-    if( t.valid() ) {
-      bool comp = processToken( n1 ).compare( processTextToken( t ) ) == 0;
-      return comp == isEqual;
+    SGPropertyNode_ptr t1 = node->getNode( "text", 0, false );
+    if( t1.valid() ) {
+      string n1s = lowercase( strip( processToken( n1 ) ) );
+      string t1s = lowercase( strip( processTextToken( t1 ) ) );
+      return fp( n1s, t1s ) == notInverted;
     }
-    SG_LOG(SG_ATC, SG_WARN, "missing <token> or <text> node for (not)-equals");
+    SG_LOG(SG_ATC, SG_WARN, "missing <token> or <text> node for " << name);
     return false;
   }
 
-  SG_LOG(SG_ATC, SG_WARN, "missing <token> node for (not)-equals");
+  SG_LOG(SG_ATC, SG_WARN, "missing <token> node for " << name);
   return false;
 }
 
@@ -408,7 +455,10 @@ string ATISEncoder::getTransitionLevel( SGPropertyNode_ptr )
 
 string ATISEncoder::getWindDirection( SGPropertyNode_ptr )
 {
-  return getSpokenNumber( _atis->getWindDeg(), true, 3 );
+  string variable = globals->get_locale()->getLocalizedString("variable", "atc", "variable" );
+
+  bool vrb = _atis->getWindMinDeg() == 0 && _atis->getWindMaxDeg() == 359;
+  return vrb ? variable : getSpokenNumber( _atis->getWindDeg(), true, 3 );
 }
 
 string ATISEncoder::getWindMinDirection( SGPropertyNode_ptr )
@@ -452,6 +502,20 @@ string ATISEncoder::getVisibilityMetric( SGPropertyNode_ptr )
   return reply.append( getSpokenNumber( v/1000 ).SPACE.append( km ) );
 }
 
+string ATISEncoder::getVisibilityMiles( SGPropertyNode_ptr )
+{
+  string feet = globals->get_locale()->getLocalizedString("feet", "atc", "feet" );
+
+  int v = _atis->getVisibilityMeters();
+  int vft = round( v * SG_METER_TO_FEET / 100 ) * 100; // Rounded to 100 feet
+  int vsm = round( v * SG_METER_TO_SM );
+
+  string reply;
+  if( vsm < 1 ) return reply.append( getSpokenAltitude( vft ) ).SPACE.append( feet );
+  if( v >= 9999 ) return reply.append( getSpokenNumber(10) );
+  return reply.append( getSpokenNumber( vsm ) );
+}
+
 string ATISEncoder::getPhenomena( SGPropertyNode_ptr )
 {
   return _atis->getPhenomena();
@@ -471,6 +535,19 @@ string ATISEncoder::getClouds( SGPropertyNode_ptr )
   return reply;
 }
 
+string ATISEncoder::getCloudsBrief( SGPropertyNode_ptr )
+{
+  string reply;
+
+  ATISInformationProvider::CloudEntries cloudEntries = _atis->getClouds();
+
+  for( ATISInformationProvider::CloudEntries::iterator it = cloudEntries.begin(); it != cloudEntries.end(); it++ ) {
+    if( false == reply.empty() ) reply.append(",").SPACE;
+    reply.append( it->second ).SPACE.append( getSpokenAltitude(it->first) );
+  }
+  return reply;
+}
+
 string ATISEncoder::getTemperatureDeg( SGPropertyNode_ptr )
 {
   return getSpokenNumber( _atis->getTemperatureDeg() );
@@ -486,19 +563,25 @@ string ATISEncoder::getQnh( SGPropertyNode_ptr )
   return getSpokenNumber( _atis->getQnh() );
 }
 
-string ATISEncoder::getInhg( SGPropertyNode_ptr )
+string ATISEncoder::getInhgInteger( SGPropertyNode_ptr )
 {
-  string DECIMAL = globals->get_locale()->getLocalizedString("dp", "atc", "decimal" );
-  double intpart = .0;
-  int fractpart = 1000 * ::modf( _atis->getQnh() * 100.0 / SG_INHG_TO_PA, &intpart );
-  fractpart += 5;
-  fractpart /= 10; 
+  double qnh = _atis->getQnh() * 100 / SG_INHG_TO_PA;
+  return getSpokenNumber( (int)qnh, true, 2 );
+}
 
-  string reply;
-  reply.append( getSpokenNumber( (int)intpart ) )
-       .SPACE.append( DECIMAL ).SPACE
-       .append( getSpokenNumber( fractpart ) );
-  return reply;
+string ATISEncoder::getInhgFraction( SGPropertyNode_ptr )
+{
+  double qnh = _atis->getQnh() * 100 / SG_INHG_TO_PA;
+  int f = round(100 * (qnh - (int)qnh));
+  return getSpokenNumber( f, true, 2 );
+}
+
+string ATISEncoder::getInhg( SGPropertyNode_ptr node)
+{
+  string DECIMAL = globals->get_locale()->getLocalizedString("dp", "atc", "decimal" );
+  return getInhgInteger(node)
+    .SPACE.append(DECIMAL).SPACE
+    .append(getInhgFraction(node));
 }
 
 string ATISEncoder::getTrend( SGPropertyNode_ptr )
index 410eb842ca3387d52a002aecce74b7aeaa4f9525..57958a1a1dd4470a2a221051426923375eeb87e4 100644 (file)
@@ -85,11 +85,15 @@ protected:
   virtual std::string getGustsKnots( SGPropertyNode_ptr );
   virtual std::string getCavok( SGPropertyNode_ptr );
   virtual std::string getVisibilityMetric( SGPropertyNode_ptr );
+  virtual std::string getVisibilityMiles( SGPropertyNode_ptr );
   virtual std::string getPhenomena( SGPropertyNode_ptr );
   virtual std::string getClouds( SGPropertyNode_ptr );
+  virtual std::string getCloudsBrief( SGPropertyNode_ptr );
   virtual std::string getTemperatureDeg( SGPropertyNode_ptr );
   virtual std::string getDewpointDeg( SGPropertyNode_ptr );
   virtual std::string getQnh( SGPropertyNode_ptr );
+  virtual std::string getInhgInteger( SGPropertyNode_ptr );
+  virtual std::string getInhgFraction( SGPropertyNode_ptr );
   virtual std::string getInhg( SGPropertyNode_ptr );
   virtual std::string getTrend( SGPropertyNode_ptr );
 
@@ -105,11 +109,24 @@ protected:
   std::string processTextToken( SGPropertyNode_ptr baseNode );
   std::string processTokenToken( SGPropertyNode_ptr baseNode );
   std::string processIfToken( SGPropertyNode_ptr baseNode );
+
   bool checkEmptyCondition( SGPropertyNode_ptr node, bool isEmpty );
-  bool checkEqualsCondition( SGPropertyNode_ptr node, bool isEmpty );
+
+  // Wrappers that can be passed as function pointers to checkCondition
+  // @see simgear::strutils::starts_with
+  // @see simgear::strutils::ends_with
+  static bool contains(const string &s, const string &substring)
+    { return s.find(substring) != std::string::npos; };
+  static bool equals(const string &s1, const string &s2)
+    { return s1 == s2; };
+
+  bool checkCondition( SGPropertyNode_ptr node, bool notInverted,
+      bool (*fp)(const std::string &, const std::string &),
+      const std::string &name );
 
   FGAirportRef airport;
   ATISInformationProvider * _atis;
+
 };
 
 #endif
index 854ee87e60cec1acd7a2524c5ec0b78e0fe0d853..ee81cb581fd7dcdb1003db9fc928ac58307fdb78 100644 (file)
@@ -93,6 +93,8 @@ AtisSpeaker::~AtisSpeaker()
 }
 void AtisSpeaker::valueChanged(SGPropertyNode * node)
 {
+  using namespace simgear::strutils;
+
   if (!fgGetBool("/sim/sound/working", false))
     return;
 
@@ -120,13 +122,19 @@ void AtisSpeaker::valueChanged(SGPropertyNode * node)
     _synthesizeRequest.speed = (hash % 16) / 16.0;
     _synthesizeRequest.pitch = (hash % 16) / 16.0;
 
-    // pick a voice
-    voice = FLITEVoiceSynthesizer::getVoicePath(
-        static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
+    if( starts_with( _stationId, "K" ) || starts_with( _stationId, "C" ) ||
+            starts_with( _stationId, "P" ) ) {
+        voice = FLITEVoiceSynthesizer::getVoicePath("cmu_us_arctic_slt");
+    } else if ( starts_with( _stationId, "EG" ) ) {
+        voice = FLITEVoiceSynthesizer::getVoicePath("cstr_uk_female");
+    } else {
+        // Pick a random voice from the available voices
+        voice = FLITEVoiceSynthesizer::getVoicePath(
+            static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
+    }
   }
 
-
-    FGSoundManager * smgr = globals->get_subsystem<FGSoundManager>();
+  FGSoundManager * smgr = globals->get_subsystem<FGSoundManager>();
   assert(smgr != NULL);
 
   SG_LOG(SG_INSTR,SG_INFO,"AtisSpeaker voice is " << voice );