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