]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/commradio.cxx
Replace the NOAA METAR URL with the new, updated one
[flightgear.git] / src / Instrumentation / commradio.cxx
1 // commradio.cxx -- class to manage a nav radio instance
2 //
3 // Written by Torsten Dreyer, February 2014
4 //
5 // Copyright (C) 2000 - 2011  Curtis L. Olson - http://www.flightgear.org/~curt
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #include "commradio.hxx"
27
28 #include <assert.h>
29 #include <boost/foreach.hpp>
30
31 #include <simgear/sg_inlines.h>
32 #include <simgear/props/propertyObject.hxx>
33 #include <simgear/misc/strutils.hxx>
34
35 #include <ATC/CommStation.hxx>
36 #include <ATC/MetarPropertiesATISInformationProvider.hxx>
37 #include <ATC/CurrentWeatherATISInformationProvider.hxx>
38 #include <Airports/airport.hxx>
39 #include <Main/fg_props.hxx>
40 #include <Navaids/navlist.hxx>
41
42 #if defined(ENABLE_FLITE)
43 #include <Sound/soundmanager.hxx>
44 #include <simgear/sound/sample_group.hxx>
45 #include <Sound/VoiceSynthesizer.hxx>
46 #endif
47
48 #include "frequencyformatter.hxx"
49
50 namespace Instrumentation {
51
52 using simgear::PropertyObject;
53 using std::string;
54
55 #if defined(ENABLE_FLITE)
56 class AtisSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
57 public:
58   AtisSpeaker();
59   virtual ~AtisSpeaker();
60   virtual void valueChanged(SGPropertyNode * node);
61   virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
62
63   bool hasSpokenAtis()
64   {
65     return _spokenAtis.empty() == false;
66   }
67
68   SGSharedPtr<SGSoundSample> getSpokenAtis()
69   {
70     return _spokenAtis.pop();
71   }
72
73   void setStationId(const string & stationId)
74   {
75     _stationId = stationId;
76   }
77
78
79 private:
80   SynthesizeRequest _synthesizeRequest;
81   SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
82   string _stationId;
83 };
84
85 AtisSpeaker::AtisSpeaker()
86 {
87   _synthesizeRequest.listener = this;
88 }
89
90 AtisSpeaker::~AtisSpeaker()
91 {
92
93 }
94 void AtisSpeaker::valueChanged(SGPropertyNode * node)
95 {
96   using namespace simgear::strutils;
97
98   if (!fgGetBool("/sim/sound/working", false))
99     return;
100
101   string newText = node->getStringValue();
102   if (_synthesizeRequest.text == newText) return;
103
104   _synthesizeRequest.text = newText;
105
106   string voice = "cmu_us_arctic_slt";
107
108   if (!_stationId.empty()) {
109     // lets play a bit with the voice so not every airports atis sounds alike
110     // but every atis of an airport has the same voice
111
112     // create a simple hash from the last two letters of the airport's id
113     unsigned char hash = 0;
114     string::iterator i = _stationId.end() - 1;
115     hash += *i;
116
117     if( i != _stationId.begin() ) {
118       --i;
119       hash += *i;
120     }
121
122     _synthesizeRequest.speed = (hash % 16) / 16.0;
123     _synthesizeRequest.pitch = (hash % 16) / 16.0;
124
125     if( starts_with( _stationId, "K" ) || starts_with( _stationId, "C" ) ||
126             starts_with( _stationId, "P" ) ) {
127         voice = FLITEVoiceSynthesizer::getVoicePath("cmu_us_arctic_slt");
128     } else if ( starts_with( _stationId, "EG" ) ) {
129         voice = FLITEVoiceSynthesizer::getVoicePath("cstr_uk_female");
130     } else {
131         // Pick a random voice from the available voices
132         voice = FLITEVoiceSynthesizer::getVoicePath(
133             static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
134     }
135   }
136
137   FGSoundManager * smgr = globals->get_subsystem<FGSoundManager>();
138   assert(smgr != NULL);
139
140   SG_LOG(SG_INSTR,SG_INFO,"AtisSpeaker voice is " << voice );
141   FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
142
143   synthesizer->synthesize(_synthesizeRequest);
144 }
145
146 void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
147 {
148   // we are now in the synthesizers worker thread!
149   _spokenAtis.push(sample);
150 }
151 #endif
152
153 SignalQualityComputer::~SignalQualityComputer()
154 {
155 }
156
157 class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
158 public:
159   SimpleDistanceSquareSignalQualityComputer()
160       : _altitudeAgl_ft(fgGetNode("/position/altitude-agl-ft", true))
161   {
162   }
163
164   ~SimpleDistanceSquareSignalQualityComputer()
165   {
166   }
167
168   double computeSignalQuality(double distance_nm) const
169   {
170     // Very simple line of sight propagation model. It's cheap but it does the trick for now.
171     // assume transmitter and receiver antennas are at some elevation above ground
172     // so we have at least a range of 5NM. Add the approx. distance to the horizon.
173     double range_nm = 5.0 + 1.23 * ::sqrt(SGMiscd::max(.0, _altitudeAgl_ft));
174     return distance_nm < range_nm ? 1.0 : (range_nm * range_nm / distance_nm / distance_nm);
175   }
176
177 private:
178   PropertyObject<double> _altitudeAgl_ft;
179 };
180
181 class OnExitHandler {
182 public:
183   virtual void onExit() = 0;
184   virtual ~OnExitHandler()
185   {
186   }
187 };
188
189 class OnExit {
190 public:
191   OnExit(OnExitHandler * onExitHandler)
192       : _onExitHandler(onExitHandler)
193   {
194   }
195   ~OnExit()
196   {
197     _onExitHandler->onExit();
198   }
199 private:
200   OnExitHandler * _onExitHandler;
201 };
202
203 class OutputProperties: public OnExitHandler {
204 public:
205   OutputProperties(SGPropertyNode_ptr rootNode)
206       : _rootNode(rootNode), _signalQuality_norm(0.0), _slantDistance_m(0.0), _trueBearingTo_deg(0.0), _trueBearingFrom_deg(0.0), _trackDistance_m(
207           0.0), _heightAboveStation_ft(0.0),
208
209       _PO_stationType(rootNode->getNode("station-type", true)), _PO_stationName(rootNode->getNode("station-name", true)), _PO_airportId(
210           rootNode->getNode("airport-id", true)), _PO_signalQuality_norm(rootNode->getNode("signal-quality-norm", true)), _PO_slantDistance_m(
211           rootNode->getNode("slant-distance-m", true)), _PO_trueBearingTo_deg(rootNode->getNode("true-bearing-to-deg", true)), _PO_trueBearingFrom_deg(
212           rootNode->getNode("true-bearing-from-deg", true)), _PO_trackDistance_m(rootNode->getNode("track-distance-m", true)), _PO_heightAboveStation_ft(
213           rootNode->getNode("height-above-station-ft", true))
214   {
215   }
216   virtual ~OutputProperties()
217   {
218   }
219
220 protected:
221   SGPropertyNode_ptr _rootNode;
222
223   std::string _stationType;
224   std::string _stationName;
225   std::string _airportId;
226   double _signalQuality_norm;
227   double _slantDistance_m;
228   double _trueBearingTo_deg;
229   double _trueBearingFrom_deg;
230   double _trackDistance_m;
231   double _heightAboveStation_ft;
232
233 private:
234   PropertyObject<string> _PO_stationType;
235   PropertyObject<string> _PO_stationName;
236   PropertyObject<string> _PO_airportId;
237   PropertyObject<double> _PO_signalQuality_norm;
238   PropertyObject<double> _PO_slantDistance_m;
239   PropertyObject<double> _PO_trueBearingTo_deg;
240   PropertyObject<double> _PO_trueBearingFrom_deg;
241   PropertyObject<double> _PO_trackDistance_m;
242   PropertyObject<double> _PO_heightAboveStation_ft;
243
244   virtual void onExit()
245   {
246     _PO_stationType = _stationType;
247     _PO_stationName = _stationName;
248     _PO_airportId = _airportId;
249     _PO_signalQuality_norm = _signalQuality_norm;
250     _PO_slantDistance_m = _slantDistance_m;
251     _PO_trueBearingTo_deg = _trueBearingTo_deg;
252     _PO_trueBearingFrom_deg = _trueBearingFrom_deg;
253     _PO_trackDistance_m = _trackDistance_m;
254     _PO_heightAboveStation_ft = _heightAboveStation_ft;
255   }
256 };
257
258 /* ------------- The CommRadio implementation ---------------------- */
259
260 class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
261 public:
262   MetarBridge();
263   ~MetarBridge();
264
265   void bind();
266   void unbind();
267   void requestMetarForId(std::string & id);
268   void clearMetar();
269   void setMetarPropertiesRoot(SGPropertyNode_ptr n)
270   {
271     _metarPropertiesNode = n;
272   }
273   void setAtisNode(SGPropertyNode * n)
274   {
275     _atisNode = n;
276   }
277
278 protected:
279   virtual void valueChanged(SGPropertyNode *);
280
281 private:
282   std::string _requestedId;
283   SGPropertyNode_ptr _realWxEnabledNode;
284   SGPropertyNode_ptr _metarPropertiesNode;
285   SGPropertyNode * _atisNode;
286   ATISEncoder _atisEncoder;
287 };
288 typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
289
290 MetarBridge::MetarBridge()
291     : _atisNode(0)
292 {
293 }
294
295 MetarBridge::~MetarBridge()
296 {
297 }
298
299 void MetarBridge::bind()
300 {
301   _realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
302   _metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
303 }
304
305 void MetarBridge::unbind()
306 {
307   _metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
308 }
309
310 void MetarBridge::requestMetarForId(std::string & id)
311 {
312   std::string uppercaseId = simgear::strutils::uppercase(id);
313   if (_requestedId == uppercaseId) return;
314   _requestedId = uppercaseId;
315
316   if (_realWxEnabledNode->getBoolValue()) {
317     //  trigger a METAR request for the associated metarproperties
318     _metarPropertiesNode->getNode("station-id", true)->setStringValue(uppercaseId);
319     _metarPropertiesNode->getNode("valid", true)->setBoolValue(false);
320     _metarPropertiesNode->getNode("time-to-live", true)->setDoubleValue(0.0);
321   } else {
322     // use the present weather to generate the ATIS. 
323     if ( NULL != _atisNode && false == _requestedId.empty()) {
324       CurrentWeatherATISInformationProvider provider(_requestedId);
325       _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
326     }
327   }
328 }
329
330 void MetarBridge::clearMetar()
331 {
332   string empty;
333   requestMetarForId(empty);
334 }
335
336 void MetarBridge::valueChanged(SGPropertyNode * node)
337 {
338   // check for raising edge of valid flag
339   if ( NULL == node || false == node->getBoolValue() || false == _realWxEnabledNode->getBoolValue()) return;
340
341   std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
342
343   // unrequested metar!?
344   if (responseId != _requestedId) return;
345
346   if ( NULL != _atisNode) {
347     MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
348     _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
349   }
350 }
351
352 /* ------------- 8.3kHz Channel implementation ---------------------- */
353
354 class EightPointThreeFrequencyFormatter :
355                 public FrequencyFormatterBase,
356                 public SGPropertyChangeListener {
357 public:
358         EightPointThreeFrequencyFormatter( SGPropertyNode_ptr root,
359                         const char * channel,
360                         const char * fmt,
361                         const char * width,
362                         const char * frq,
363                         const char * cnum ) :
364                 _channel( root, channel ),
365                 _frequency( root, frq ),
366                 _channelSpacing( root, width ),
367                 _formattedChannel( root, fmt ),
368                 _channelNum( root, cnum )
369         {
370                 // ensure properties exist.
371                 _channel.node(true);
372                 _frequency.node(true);
373                 _channelSpacing.node(true);
374                 _channelNum.node(true);
375                 _formattedChannel.node(true);
376
377                 _channel.node()->addChangeListener( this, true );
378                 _channelNum.node()->addChangeListener( this, true );
379         }
380
381         virtual ~EightPointThreeFrequencyFormatter()
382         {
383                   _channel.node()->removeChangeListener( this );
384                   _channelNum.node()->removeChangeListener( this );
385         }
386
387 private:
388         EightPointThreeFrequencyFormatter( const EightPointThreeFrequencyFormatter & );
389         EightPointThreeFrequencyFormatter & operator = ( const EightPointThreeFrequencyFormatter & );
390
391         void valueChanged (SGPropertyNode * prop) {
392                 if( prop == _channel.node() )
393                         setFrequency(prop->getDoubleValue());
394                 else if( prop == _channelNum.node() )
395                         setChannel(prop->getIntValue());
396         }
397
398         void setChannel( int channel ) {
399                 channel %= 3040;
400                 if( channel < 0 ) channel += 3040;
401                 double f = 118.000 + 0.025*(channel/4) + 0.005*(channel%4);
402                 if( f != _channel )     _channel = f;
403         }
404
405         void setFrequency( double channel ) {
406                 // format as fixed decimal "nnn.nnn"
407                 std::ostringstream buf;
408                 buf << std::fixed
409                     << std::setw(6)
410                     << std::setfill('0')
411                     << std::setprecision(3)
412                     << _channel;
413                 _formattedChannel = buf.str();
414
415                 // sanitize range and round to nearest kHz.
416                 unsigned c = static_cast<int>(SGMiscd::round(SGMiscd::clip( channel, 118.0, 136.99 ) * 1000));
417
418                 if ( (c % 25)  == 0 ) {
419                         // legacy 25kHz channels continue to be just that.
420                         _channelSpacing = 25.0;
421                         _frequency = c / 1000.0;
422
423                         int channelNum = (c-118000)/25*4;
424                         if( channelNum != _channelNum ) _channelNum = channelNum;
425
426                         if( _frequency != channel ) {
427                                 _channel = _frequency; //triggers recursion
428                         }
429                 } else {
430                         _channelSpacing = 8.33;
431
432                         // 25kHz base frequency: xxx.000, xxx.025, xxx.050, xxx.075
433                         unsigned base25 = (c/25) * 25;
434
435                         // add n*8.33 to the 25kHz frequency
436                         unsigned subChannel = SGMisc<unsigned>::clip((c - base25)/5-1, 0, 2 );
437
438                         _frequency = (base25 + 8.33 * subChannel)/1000.0;
439
440                         int channelNum = (base25-118000)/25*4 + subChannel+1;
441                         if( channelNum != _channelNum ) _channelNum = channelNum;
442
443                         // set to correct channel on bogous input
444                         double sanitizedChannel = (base25 + 5*(subChannel+1))/1000.0;
445                         if( sanitizedChannel != channel ) {
446                                 _channel = sanitizedChannel; // triggers recursion
447                         }
448                 }
449         }
450
451         double getFrequency() const {
452                 return _channel;
453         }
454
455
456     PropertyObject<double> _channel;
457     PropertyObject<double> _frequency;
458     PropertyObject<double> _channelSpacing;
459     PropertyObject<string> _formattedChannel;
460     PropertyObject<int>   _channelNum;
461
462 };
463
464
465 /* ------------- The CommRadio implementation ---------------------- */
466
467 class CommRadioImpl: public CommRadio, OutputProperties {
468
469 public:
470   CommRadioImpl(SGPropertyNode_ptr node);
471   virtual ~CommRadioImpl();
472
473   virtual void update(double dt);
474   virtual void init();
475   void bind();
476   void unbind();
477
478 private:
479   string getSampleGroupRefname() const
480   {
481     return _rootNode->getPath();
482   }
483
484   int _num;
485   MetarBridgeRef _metarBridge;
486   #if defined(ENABLE_FLITE)
487   AtisSpeaker _atisSpeaker;
488   #endif
489   SGSharedPtr<FrequencyFormatterBase> _useFrequencyFormatter;
490   SGSharedPtr<FrequencyFormatterBase> _stbyFrequencyFormatter;
491   const SignalQualityComputerRef _signalQualityComputer;
492
493   double _stationTTL;
494   double _frequency;
495   flightgear::CommStationRef _commStationForFrequency;
496   #if defined(ENABLE_FLITE)
497   SGSharedPtr<SGSampleGroup> _sampleGroup;
498   #endif
499
500   PropertyObject<bool> _serviceable;
501   PropertyObject<bool> _power_btn;
502   PropertyObject<bool> _power_good;
503   PropertyObject<double> _volume_norm;
504   PropertyObject<string> _atis;
505   PropertyObject<bool> _addNoise;
506   PropertyObject<double> _cutoffSignalQuality;
507
508 };
509
510 CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node)
511     : OutputProperties(
512         fgGetNode("/instrumentation", true)->getNode(node->getStringValue("name", "comm"), node->getIntValue("number", 0), true)),
513         _num(node->getIntValue("number", 0)),
514         _metarBridge(new MetarBridge()),
515         _signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer()),
516
517         _stationTTL(0.0),
518         _frequency(-1.0),
519         _commStationForFrequency(NULL),
520
521         _serviceable(_rootNode->getNode("serviceable", true)),
522         _power_btn(_rootNode->getNode("power-btn", true)),
523         _power_good(_rootNode->getNode("power-good", true)),
524         _volume_norm(_rootNode->getNode("volume", true)),
525         _atis(_rootNode->getNode("atis", true)),
526         _addNoise(_rootNode->getNode("add-noise", true)),
527         _cutoffSignalQuality(_rootNode->getNode("cutoff-signal-quality", true))
528 {
529   if( node->getBoolValue("eight-point-three", false ) ) {
530           _useFrequencyFormatter = new EightPointThreeFrequencyFormatter(
531                   _rootNode->getNode("frequencies", true),
532                   "selected-mhz",
533                   "selected-mhz-fmt",
534                   "selected-channel-width-khz",
535                   "selected-real-frequency-mhz",
536                   "selected-channel"
537                   );
538           _stbyFrequencyFormatter = new EightPointThreeFrequencyFormatter(
539                   _rootNode->getNode("frequencies", true),
540                   "standby-mhz",
541                   "standby-mhz-fmt",
542                   "standby-channel-width-khz",
543                   "standby-real-frequency-mhz",
544                   "standby-channel"
545                   );
546   } else {
547       _useFrequencyFormatter = new FrequencyFormatter(
548           _rootNode->getNode("frequencies/selected-mhz", true),
549           _rootNode->getNode("frequencies/selected-mhz-fmt", true),
550                   0.025, 118.0, 137.0);
551
552       _stbyFrequencyFormatter = new FrequencyFormatter(
553           _rootNode->getNode("frequencies/standby-mhz", true),
554           _rootNode->getNode("frequencies/standby-mhz-fmt", true),
555                   0.025, 118.0, 137.0);
556
557
558   }
559 }
560
561 CommRadioImpl::~CommRadioImpl()
562 {
563 }
564
565 void CommRadioImpl::bind()
566 {
567   _metarBridge->setAtisNode(_atis.node());
568 #if defined(ENABLE_FLITE)
569   _atis.node()->addChangeListener(&_atisSpeaker);
570 #endif
571   // link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
572   // see FGDATA/Environment/environment.xml
573   _metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", _num + 3, true));
574   _metarBridge->bind();
575 }
576
577 void CommRadioImpl::unbind()
578 {
579 #if defined(ENABLE_FLITE)
580   _atis.node()->removeChangeListener(&_atisSpeaker);
581   if (_sampleGroup.valid()) {
582       globals->get_subsystem<SGSoundMgr>()->remove(getSampleGroupRefname());
583   }
584 #endif
585   _metarBridge->unbind();
586 }
587
588 void CommRadioImpl::init()
589 {
590   // initialize power_btn to true if unset
591   string s = _power_btn.node()->getStringValue();
592   if (s.empty()) _power_btn = true;
593
594   // initialize squelch to a sane value if unset
595   s = _cutoffSignalQuality.node()->getStringValue();
596   if (s.empty()) _cutoffSignalQuality = 0.4;
597
598   // initialize add-noize to true if unset
599   s = _addNoise.node()->getStringValue();
600   if (s.empty()) _addNoise = true;
601 }
602
603 void CommRadioImpl::update(double dt)
604 {
605   if (dt < SGLimitsd::min()) return;
606   _stationTTL -= dt;
607
608   // Ensure all output properties get written on exit of this method
609   OnExit onExit(this);
610
611   SGGeod position;
612   try {
613     position = globals->get_aircraft_position();
614   }
615   catch (std::exception &) {
616     return;
617   }
618
619 #if defined(ENABLE_FLITE)
620   {
621     static const char * atisSampleRefName = "atis";
622     static const char * noiseSampleRefName = "noise";
623
624     if (_atisSpeaker.hasSpokenAtis()) {
625       // the speaker has created a new atis sample
626       if (!_sampleGroup.valid()) {
627         // create a sample group for our instrument on the fly
628           SGSoundMgr * smgr = globals->get_subsystem<SGSoundMgr>();
629
630         _sampleGroup = smgr->find(getSampleGroupRefname(), true);
631         _sampleGroup->tie_to_listener();
632         if (_addNoise) {
633           SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
634           _sampleGroup->add(noise, noiseSampleRefName);
635           _sampleGroup->play_looped(noiseSampleRefName);
636         }
637
638       }
639       // remove previous atis sample
640       _sampleGroup->remove(atisSampleRefName);
641       // add and play the new atis sample
642       SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
643       _sampleGroup->add(sample, atisSampleRefName);
644       _sampleGroup->play_looped(atisSampleRefName);
645     }
646
647     if (_sampleGroup.valid()) {
648       if (_addNoise) {
649         // if noise is configured and there is a noise sample
650         // scale noise and signal volume by signalQuality.
651         SGSoundSample * s = _sampleGroup->find(noiseSampleRefName);
652         if ( NULL != s) {
653           s->set_volume(1.0 - _signalQuality_norm);
654           s = _sampleGroup->find(atisSampleRefName);
655           s->set_volume(_signalQuality_norm);
656         }
657       }
658       // master volume for radio, mute on bad signal quality
659       _sampleGroup->set_volume(_signalQuality_norm >= _cutoffSignalQuality ? _volume_norm : 0.0);
660     }
661   }
662 #endif
663
664   if (false == (_power_btn) || false == (_serviceable)) {
665     _metarBridge->clearMetar();
666     _atis = "";
667     _stationTTL = 0.0;
668     return;
669   }
670
671   if (_frequency != _useFrequencyFormatter->getFrequency()) {
672     _frequency = _useFrequencyFormatter->getFrequency();
673     _stationTTL = 0.0;
674   }
675
676   if (_stationTTL <= 0.0) {
677     _stationTTL = 30.0;
678
679     // Note:  122.375 must be rounded DOWN to 122370
680     // in order to be consistent with apt.dat et cetera.
681     int freqKhz = 10 * static_cast<int>(_frequency * 100 + 0.25);
682     _commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
683
684   }
685
686   if (false == _commStationForFrequency.valid()) return;
687
688   _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
689
690   SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
691
692   _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
693
694   _signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m * SG_METER_TO_NM);
695   _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
696   _stationName = _commStationForFrequency->ident();
697   _airportId = _commStationForFrequency->airport()->getId();
698
699 #if defined(ENABLE_FLITE)
700   _atisSpeaker.setStationId(_airportId);
701 #endif
702   switch (_commStationForFrequency->type()) {
703     case FGPositioned::FREQ_ATIS:
704       case FGPositioned::FREQ_AWOS: {
705       if (_signalQuality_norm > 0.01) {
706         _metarBridge->requestMetarForId(_airportId);
707       } else {
708         _metarBridge->clearMetar();
709         _atis = "";
710       }
711     }
712       break;
713
714     default:
715       _metarBridge->clearMetar();
716       _atis = "";
717       break;
718   }
719 }
720
721 SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
722 {
723   return new CommRadioImpl(rootNode);
724 }
725
726 } // namespace Instrumentation
727