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 
30 #include <simgear/sg_inlines.h>
31 #include <simgear/props/propertyObject.hxx>
32 #include <simgear/misc/strutils.hxx>
33 
34 #include <ATC/CommStation.hxx>
35 #include <ATC/MetarPropertiesATISInformationProvider.hxx>
36 #include <ATC/CurrentWeatherATISInformationProvider.hxx>
37 #include <Airports/airport.hxx>
38 #include <Main/fg_props.hxx>
39 #include <Navaids/navlist.hxx>
40 
41 #include <Sound/soundmanager.hxx>
42 #include <simgear/sound/sample_group.hxx>
43 #include <Sound/VoiceSynthesizer.hxx>
44 
45 #include "frequencyformatter.hxx"
46 
47 namespace Instrumentation {
48 
49 using simgear::PropertyObject;
50 using std::string;
51 
52 class AtisSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
53 public:
54   AtisSpeaker();
55   virtual ~AtisSpeaker();
56   virtual void valueChanged(SGPropertyNode * node);
57   virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
58 
hasSpokenAtis()59   bool hasSpokenAtis()
60   {
61     return _spokenAtis.empty() == false;
62   }
63 
getSpokenAtis()64   SGSharedPtr<SGSoundSample> getSpokenAtis()
65   {
66     return _spokenAtis.pop();
67   }
68 
setStationId(const string & stationId)69   void setStationId(const string & stationId)
70   {
71     _stationId = stationId;
72   }
73 
74 
75 private:
76   SynthesizeRequest _synthesizeRequest;
77   SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
78   string _stationId;
79 };
80 
AtisSpeaker()81 AtisSpeaker::AtisSpeaker()
82 {
83   _synthesizeRequest.listener = this;
84 }
85 
~AtisSpeaker()86 AtisSpeaker::~AtisSpeaker()
87 {
88 
89 }
valueChanged(SGPropertyNode * node)90 void AtisSpeaker::valueChanged(SGPropertyNode * node)
91 {
92   using namespace simgear::strutils;
93 
94   if (!fgGetBool("/sim/sound/working", false))
95     return;
96 
97   string newText = node->getStringValue();
98   if (_synthesizeRequest.text == newText) return;
99 
100   _synthesizeRequest.text = newText;
101 
102   string voice = "cmu_us_arctic_slt";
103 
104   if (!_stationId.empty()) {
105     // lets play a bit with the voice so not every airports atis sounds alike
106     // but every atis of an airport has the same voice
107 
108     // create a simple hash from the last two letters of the airport's id
109     unsigned char hash = 0;
110     string::iterator i = _stationId.end() - 1;
111     hash += *i;
112 
113     if( i != _stationId.begin() ) {
114       --i;
115       hash += *i;
116     }
117 
118     _synthesizeRequest.speed = (hash % 16) / 16.0;
119     _synthesizeRequest.pitch = (hash % 16) / 16.0;
120 
121     if( starts_with( _stationId, "K" ) || starts_with( _stationId, "C" ) ||
122             starts_with( _stationId, "P" ) ) {
123         voice = FLITEVoiceSynthesizer::getVoicePath("cmu_us_arctic_slt");
124     } else if ( starts_with( _stationId, "EG" ) ) {
125         voice = FLITEVoiceSynthesizer::getVoicePath("cstr_uk_female");
126     } else {
127         // Pick a random voice from the available voices
128         voice = FLITEVoiceSynthesizer::getVoicePath(
129             static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
130     }
131   }
132 
133   FGSoundManager * smgr = globals->get_subsystem<FGSoundManager>();
134   if (!smgr) {
135       return;
136   }
137 
138   SG_LOG(SG_INSTR, SG_DEBUG,"AtisSpeaker voice is " << voice );
139   FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
140 
141   synthesizer->synthesize(_synthesizeRequest);
142 }
143 
SoundSampleReady(SGSharedPtr<SGSoundSample> sample)144 void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
145 {
146   // we are now in the synthesizers worker thread!
147   _spokenAtis.push(sample);
148 }
149 
~SignalQualityComputer()150 SignalQualityComputer::~SignalQualityComputer()
151 {
152 }
153 
154 class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
155 public:
SimpleDistanceSquareSignalQualityComputer()156   SimpleDistanceSquareSignalQualityComputer()
157       : _altitudeAgl_ft(fgGetNode("/position/altitude-agl-ft", true))
158   {
159   }
160 
~SimpleDistanceSquareSignalQualityComputer()161   ~SimpleDistanceSquareSignalQualityComputer()
162   {
163   }
164 
computeSignalQuality(double distance_nm) const165   double computeSignalQuality(double distance_nm) const
166   {
167     // Very simple line of sight propagation model. It's cheap but it does the trick for now.
168     // assume transmitter and receiver antennas are at some elevation above ground
169     // so we have at least a range of 5NM. Add the approx. distance to the horizon.
170     double range_nm = 5.0 + 1.23 * ::sqrt(SGMiscd::max(.0, _altitudeAgl_ft));
171     return distance_nm < range_nm ? 1.0 : (range_nm * range_nm / distance_nm / distance_nm);
172   }
173 
174 private:
175   PropertyObject<double> _altitudeAgl_ft;
176 };
177 
178 class OnExitHandler {
179 public:
180   virtual void onExit() = 0;
~OnExitHandler()181   virtual ~OnExitHandler()
182   {
183   }
184 };
185 
186 class OnExit {
187 public:
OnExit(OnExitHandler * onExitHandler)188   OnExit(OnExitHandler * onExitHandler)
189       : _onExitHandler(onExitHandler)
190   {
191   }
~OnExit()192   ~OnExit()
193   {
194     _onExitHandler->onExit();
195   }
196 private:
197   OnExitHandler * _onExitHandler;
198 };
199 
200 class OutputProperties: public OnExitHandler {
201 public:
202 
bind(SGPropertyNode * rn)203   void bind(SGPropertyNode* rn)
204   {
205       _rootNode = rn;
206 
207       _PO_stationType = PropertyObject<string>(_rootNode->getNode("station-type", true));
208       _PO_stationName = PropertyObject<string>(_rootNode->getNode("station-name", true));
209       _PO_airportId = PropertyObject<string>(_rootNode->getNode("airport-id", true));
210       _PO_signalQuality_norm = PropertyObject<double>(_rootNode->getNode("signal-quality-norm", true));
211       _PO_slantDistance_m = PropertyObject<double>(_rootNode->getNode("slant-distance-m", true));
212       _PO_trueBearingTo_deg = PropertyObject<double>(_rootNode->getNode("true-bearing-to-deg", true));
213       _PO_trueBearingFrom_deg = PropertyObject<double>(_rootNode->getNode("true-bearing-from-deg", true));
214       _PO_trackDistance_m = PropertyObject<double>(_rootNode->getNode("track-distance-m", true));
215       _PO_heightAboveStation_ft = PropertyObject<double>(_rootNode->getNode("height-above-station-ft", true));
216   }
217 
~OutputProperties()218   virtual ~OutputProperties()
219   {
220   }
221 
222 protected:
223   SGPropertyNode_ptr _rootNode;
224 
225   std::string _stationType;
226   std::string _stationName;
227   std::string _airportId;
228   double _signalQuality_norm = 0.0;
229   double _slantDistance_m = 0.0;
230   double _trueBearingTo_deg = 0.0;
231   double _trueBearingFrom_deg = 0.0;
232   double _trackDistance_m = 0.0;
233   double _heightAboveStation_ft = 0.0;
234 
235 private:
236   PropertyObject<string> _PO_stationType;
237   PropertyObject<string> _PO_stationName;
238   PropertyObject<string> _PO_airportId;
239   PropertyObject<double> _PO_signalQuality_norm;
240   PropertyObject<double> _PO_slantDistance_m;
241   PropertyObject<double> _PO_trueBearingTo_deg;
242   PropertyObject<double> _PO_trueBearingFrom_deg;
243   PropertyObject<double> _PO_trackDistance_m;
244   PropertyObject<double> _PO_heightAboveStation_ft;
245 
onExit()246   virtual void onExit()
247   {
248     _PO_stationType = _stationType;
249     _PO_stationName = _stationName;
250     _PO_airportId = _airportId;
251     _PO_signalQuality_norm = _signalQuality_norm;
252     _PO_slantDistance_m = _slantDistance_m;
253     _PO_trueBearingTo_deg = _trueBearingTo_deg;
254     _PO_trueBearingFrom_deg = _trueBearingFrom_deg;
255     _PO_trackDistance_m = _trackDistance_m;
256     _PO_heightAboveStation_ft = _heightAboveStation_ft;
257   }
258 };
259 
260 /* ------------- The CommRadio implementation ---------------------- */
261 
262 class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
263 public:
264 
265   void bind();
266   void unbind();
267   void requestMetarForId(std::string & id);
268   void clearMetar();
setMetarPropertiesRoot(SGPropertyNode_ptr n)269   void setMetarPropertiesRoot(SGPropertyNode_ptr n)
270   {
271     _metarPropertiesNode = n;
272   }
setAtisNode(SGPropertyNode * n)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 = nullptr;
286   ATISEncoder _atisEncoder;
287 };
288 
289 typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
290 
bind()291 void MetarBridge::bind()
292 {
293   _realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
294   _metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
295 }
296 
unbind()297 void MetarBridge::unbind()
298 {
299   _metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
300 }
301 
requestMetarForId(std::string & id)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 
clearMetar()322 void MetarBridge::clearMetar()
323 {
324   string empty;
325   requestMetarForId(empty);
326 }
327 
valueChanged(SGPropertyNode * node)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:
EightPointThreeFrequencyFormatter(SGPropertyNode_ptr root,const char * channel,const char * fmt,const char * width,const char * frq,const char * cnum)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 
~EightPointThreeFrequencyFormatter()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 
valueChanged(SGPropertyNode * prop)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 
setChannel(int channel)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 
setFrequency(double channel)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                 const double channelValue = c / 1000.0;
420                 _channel = channelValue;
421             }
422 		} else {
423 			_channelSpacing = 8.33;
424 
425 			// 25kHz base frequency: xxx.000, xxx.025, xxx.050, xxx.075
426 			unsigned base25 = (c/25) * 25;
427 
428 			// add n*8.33 to the 25kHz frequency
429 			unsigned subChannel = SGMisc<unsigned>::clip((c - base25)/5-1, 0, 2 );
430 
431 			_frequency = (base25 + 8.33 * subChannel)/1000.0;
432 
433 			int channelNum = (base25-118000)/25*4 + subChannel+1;
434 			if( channelNum != _channelNum ) _channelNum = channelNum;
435 
436 			// set to correct channel on bogous input
437 			double sanitizedChannel = (base25 + 5*(subChannel+1))/1000.0;
438 			if( sanitizedChannel != channel ) {
439 				_channel = sanitizedChannel; // triggers recursion
440 			}
441 		}
442 	}
443 
getFrequency() const444 	double getFrequency() const {
445 		return _channel;
446 	}
447 
448 
449     PropertyObject<double> _channel;
450     PropertyObject<double> _frequency;
451     PropertyObject<double> _channelSpacing;
452     PropertyObject<string> _formattedChannel;
453     PropertyObject<int>   _channelNum;
454 
455 };
456 
457 
458 /* ------------- The CommRadio implementation ---------------------- */
459 
460 class CommRadioImpl: public CommRadio,
461                      OutputProperties
462 {
463 public:
464     CommRadioImpl(SGPropertyNode_ptr node);
465     virtual ~CommRadioImpl();
466 
467     // Subsystem API.
468     void bind() override;
469     void init() override;
470     void unbind() override;
471     void update(double dt) override;
472 
473 private:
474     bool _useEightPointThree = false;
475     MetarBridgeRef _metarBridge;
476     AtisSpeaker _atisSpeaker;
477     SGSharedPtr<FrequencyFormatterBase> _useFrequencyFormatter;
478     SGSharedPtr<FrequencyFormatterBase> _stbyFrequencyFormatter;
479     const SignalQualityComputerRef _signalQualityComputer;
480 
481     double _stationTTL = 0.0;
482     double _frequency = -1.0;
483     flightgear::CommStationRef _commStationForFrequency;
484 
485     PropertyObject<double> _volume_norm;
486     PropertyObject<string> _atis;
487     PropertyObject<bool> _addNoise;
488     PropertyObject<double> _cutoffSignalQuality;
489 
490     std::string _soundPrefix;
491     void stopAudio();
492     void updateAudio();
493 
494     SGSampleGroup* _sampleGroup = nullptr;
495 };
496 
CommRadioImpl(SGPropertyNode_ptr node)497 CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node) :
498   _metarBridge(new MetarBridge),
499   _signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer)
500 {
501   // set a special value to indicate we don't require a power supply node
502   // by default
503   setDefaultPowerSupplyPath("NO_DEFAULT");
504   readConfig(node, "comm");
505   _soundPrefix = name() + "_" + std::to_string(number()) + "_";
506   _useEightPointThree = node->getBoolValue("eight-point-three", false );
507 }
508 
~CommRadioImpl()509 CommRadioImpl::~CommRadioImpl()
510 {
511 }
512 
bind()513 void CommRadioImpl::bind()
514 {
515   SGPropertyNode_ptr n = fgGetNode(nodePath(), true);
516   OutputProperties::bind(n);
517 
518   _volume_norm = PropertyObject<double>(_rootNode->getNode("volume", true));
519   _atis = PropertyObject<string>(_rootNode->getNode("atis", true));
520   _addNoise = PropertyObject<bool>(_rootNode->getNode("add-noise", true));
521   _cutoffSignalQuality = PropertyObject<double>(_rootNode->getNode("cutoff-signal-quality", true));
522 
523   _metarBridge->setAtisNode(_atis.node());
524   _atis.node()->addChangeListener(&_atisSpeaker);
525 
526   // link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
527   // see FGDATA/Environment/environment.xml
528   _metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", number() + 3, true));
529   _metarBridge->bind();
530 
531   if (_useEightPointThree) {
532     _useFrequencyFormatter = new EightPointThreeFrequencyFormatter(
533                                                                    _rootNode->getNode("frequencies", true),
534                                                                    "selected-mhz",
535                                                                    "selected-mhz-fmt",
536                                                                    "selected-channel-width-khz",
537                                                                    "selected-real-frequency-mhz",
538                                                                    "selected-channel"
539                                                                    );
540     _stbyFrequencyFormatter = new EightPointThreeFrequencyFormatter(
541                                                                     _rootNode->getNode("frequencies", true),
542                                                                     "standby-mhz",
543                                                                     "standby-mhz-fmt",
544                                                                     "standby-channel-width-khz",
545                                                                     "standby-real-frequency-mhz",
546                                                                     "standby-channel"
547                                                                     );
548   } else {
549     _useFrequencyFormatter = new FrequencyFormatter(
550                                                     _rootNode->getNode("frequencies/selected-mhz", true),
551                                                     _rootNode->getNode("frequencies/selected-mhz-fmt", true),
552                                                     0.025, 118.0, 137.0);
553 
554     _stbyFrequencyFormatter = new FrequencyFormatter(
555                                                      _rootNode->getNode("frequencies/standby-mhz", true),
556                                                      _rootNode->getNode("frequencies/standby-mhz-fmt", true),
557                                                      0.025, 118.0, 137.0);
558 
559 
560   }
561 }
562 
unbind()563 void CommRadioImpl::unbind()
564 {
565   _atis.node()->removeChangeListener(&_atisSpeaker);
566 
567   stopAudio();
568 
569   _metarBridge->unbind();
570   AbstractInstrument::unbind();
571 }
572 
init()573 void CommRadioImpl::init()
574 {
575   initServicePowerProperties(_rootNode);
576 
577   string s;
578   // initialize squelch to a sane value if unset
579   s = _cutoffSignalQuality.node()->getStringValue();
580   if (s.empty()) _cutoffSignalQuality = 0.4;
581 
582   // initialize add-noize to true if unset
583   s = _addNoise.node()->getStringValue();
584   if (s.empty()) _addNoise = true;
585 
586   auto soundManager = globals->get_subsystem<SGSoundMgr>();
587   if (soundManager) {
588     _sampleGroup = soundManager->find("atc", true);
589   }
590 }
591 
update(double dt)592 void CommRadioImpl::update(double dt)
593 {
594   if (dt < SGLimitsd::min()) return;
595   _stationTTL -= dt;
596 
597   // Ensure all output properties get written on exit of this method
598   OnExit onExit(this);
599 
600   SGGeod position;
601   try {
602     position = globals->get_aircraft_position();
603   }
604   catch (std::exception &) {
605     return;
606   }
607 
608   if (!isServiceableAndPowered()) {
609     _metarBridge->clearMetar();
610     _atis = "";
611     _stationTTL = 0.0;
612     stopAudio();
613     return;
614   }
615 
616   if (_frequency != _useFrequencyFormatter->getFrequency()) {
617     _frequency = _useFrequencyFormatter->getFrequency();
618     _stationTTL = 0.0;
619   }
620 
621   if (_stationTTL <= 0.0) {
622     _stationTTL = 30.0;
623 
624     int freqKhz = static_cast<int>(_frequency * 1000 + 0.5);
625     // make sure the frequency is integral multiple of 25, if not using 8.333 kHz spacing
626     if (!_useEightPointThree && freqKhz % 25) {
627         freqKhz += 5;
628     }
629 
630     _commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
631   }
632 
633   if (false == _commStationForFrequency.valid()) {
634     stopAudio();
635     return;
636   }
637 
638   _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
639 
640   SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
641 
642   _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
643 
644   _signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m * SG_METER_TO_NM);
645   _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
646   _stationName = _commStationForFrequency->ident();
647   _airportId = _commStationForFrequency->airport()->getId();
648 
649   _atisSpeaker.setStationId(_airportId);
650 
651   switch (_commStationForFrequency->type()) {
652     case FGPositioned::FREQ_ATIS:
653       case FGPositioned::FREQ_AWOS: {
654       if (_signalQuality_norm > 0.01) {
655         _metarBridge->requestMetarForId(_airportId);
656       } else {
657         _metarBridge->clearMetar();
658         _atis = "";
659       }
660     }
661       break;
662 
663     default:
664       _metarBridge->clearMetar();
665       _atis = "";
666       break;
667   }
668 
669   updateAudio();
670 }
671 
updateAudio()672 void CommRadioImpl::updateAudio()
673 {
674   if (!_sampleGroup)
675     return;
676 
677   const string noiseRef = _soundPrefix + "_noise";
678   const string atisRef = _soundPrefix + "_atis";
679 
680   SGSoundSample* noiseSample = _sampleGroup->find(noiseRef);
681 
682   // create noise sample if necessary, and play forever
683   if (_addNoise && !noiseSample) {
684     SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
685     _sampleGroup->add(noise, noiseRef);
686     _sampleGroup->play_looped(noiseRef);
687     noiseSample = noise;
688   }
689 
690   if (_atisSpeaker.hasSpokenAtis()) {
691     // the speaker has created a new atis sample
692     // remove previous atis sample
693     _sampleGroup->remove(atisRef);
694 
695     SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
696     _sampleGroup->add(sample, atisRef);
697     _sampleGroup->play_looped(atisRef);
698   }
699 
700   // adjust volumes
701   const bool doSquelch = (_signalQuality_norm < _cutoffSignalQuality);
702   double atisVolume = doSquelch ? 0.0 : _volume_norm;
703   if (_addNoise) {
704     const double noiseVol = (1.0 - _signalQuality_norm) * _volume_norm;
705     atisVolume = _signalQuality_norm * _volume_norm;
706     noiseSample->set_volume(doSquelch ? 0.0: noiseVol);
707   }
708 
709   SGSoundSample* s = _sampleGroup->find(atisRef);
710   if (s) {
711     s->set_volume(atisVolume);
712   }
713 }
714 
stopAudio()715 void CommRadioImpl::stopAudio()
716 {
717   if (_sampleGroup) {
718     _sampleGroup->remove(_soundPrefix + "_noise");
719     _sampleGroup->remove(_soundPrefix + "_atis");
720   }
721 }
722 
createInstance(SGPropertyNode_ptr rootNode)723 SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
724 {
725   return new CommRadioImpl(rootNode);
726 }
727 
728 
729 // Register the subsystem.
730 #if 0
731 SGSubsystemMgr::InstancedRegistrant<CommRadio> registrantCommRadio(
732     SGSubsystemMgr::FDM,
733     {{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
734 #endif
735 
736 } // namespace Instrumentation
737 
738