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