FlightGear next
commradio.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileComment: class to manage a comm radio instance
3 * SPDX-FileCopyrightText: Copyright (C) 2014 Torsten Dreyer
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include <config.h>
8
9#include "commradio.hxx"
10
11#include <assert.h>
12
13#include <simgear/sg_inlines.h>
14#include <simgear/props/propertyObject.hxx>
15#include <simgear/misc/strutils.hxx>
16
17#include <ATC/CommStation.hxx>
20#include <Airports/airport.hxx>
21#include <Main/fg_props.hxx>
22#include <Navaids/navlist.hxx>
23
25#include <simgear/sound/sample_group.hxx>
27
29
30namespace Instrumentation {
31
32using simgear::PropertyObject;
33using std::string;
34
35class AtisSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
36public:
38 virtual ~AtisSpeaker();
39 virtual void valueChanged(SGPropertyNode * node);
40 virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
41
43 {
44 return _spokenAtis.empty() == false;
45 }
46
47 SGSharedPtr<SGSoundSample> getSpokenAtis()
48 {
49 return _spokenAtis.pop();
50 }
51
52 void setStationId(const string & stationId)
53 {
54 _stationId = stationId;
55 }
56
57
58private:
59 SynthesizeRequest _synthesizeRequest;
60 SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
61 string _stationId;
62};
63
65{
66 _synthesizeRequest.listener = this;
67 if (!fgHasNode("/sim/atis/speed")) fgSetDouble("/sim/atis/speed", 1);
68 if (!fgHasNode("/sim/atis/pitch")) fgSetDouble("/sim/atis/pitch", 1);
69 if (!fgHasNode("/sim/atis/enabled")) fgSetBool("/sim/atis/enabled", true);
70}
71
76void AtisSpeaker::valueChanged(SGPropertyNode * node)
77{
78 using namespace simgear::strutils;
79
80 if (!fgGetBool("/sim/sound/working", false))
81 return;
82
83 string newText = node->getStringValue();
84 if (_synthesizeRequest.text == newText) return;
85
86 _synthesizeRequest.text = newText;
87
88 string voice = "cmu_us_arctic_slt";
89
90 if (!_stationId.empty()) {
91 // lets play a bit with the voice so not every airports atis sounds alike
92 // but every atis of an airport has the same voice
93
94 // create a simple hash from the last two letters of the airport's id
95 unsigned char hash = 0;
96 string::iterator i = _stationId.end() - 1;
97 hash += *i;
98
99 if( i != _stationId.begin() ) {
100 --i;
101 hash += *i;
102 }
103
104 _synthesizeRequest.speed = (hash % 16) / 16.0 * fgGetDouble("/sim/atis/speed", 1);
105 _synthesizeRequest.pitch = (hash % 16) / 16.0 * fgGetDouble("/sim/atis/pitch", 1);
106
107 if( starts_with( _stationId, "K" ) || starts_with( _stationId, "C" ) ||
108 starts_with( _stationId, "P" ) ) {
109 voice = FLITEVoiceSynthesizer::getVoicePath("cmu_us_arctic_slt");
110 } else if ( starts_with( _stationId, "EG" ) ) {
111 voice = FLITEVoiceSynthesizer::getVoicePath("cstr_uk_female");
112 } else {
113 // Pick a random voice from the available voices
116 }
117 }
118
119 auto smgr = globals->get_subsystem<FGSoundManager>();
120 if (!smgr) {
121 return;
122 }
123
124 SG_LOG(SG_INSTR, SG_DEBUG,"node->getPath()=" << node->getPath() << " AtisSpeaker voice is " << voice );
125 FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
126
127 synthesizer->synthesize(_synthesizeRequest);
128}
129
130void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
131{
132 // we are now in the synthesizers worker thread!
133 _spokenAtis.push(sample);
134}
135
139
141public:
143 : _altitudeAgl_ft(fgGetNode("/position/altitude-agl-ft", true))
144 {
145 }
146
150
151 double computeSignalQuality(double distance_nm) const
152 {
153 // Very simple line of sight propagation model. It's cheap but it does the trick for now.
154 // assume transmitter and receiver antennas are at some elevation above ground
155 // so we have at least a range of 5NM. Add the approx. distance to the horizon.
156 double range_nm = 5.0 + 1.23 * ::sqrt(SGMiscd::max(.0, _altitudeAgl_ft));
157 return distance_nm < range_nm ? 1.0 : (range_nm * range_nm / distance_nm / distance_nm);
158 }
159
160private:
161 PropertyObject<double> _altitudeAgl_ft;
162};
163
165public:
166 virtual void onExit() = 0;
168 {
169 }
170};
171
172class OnExit {
173public:
174 OnExit(OnExitHandler * onExitHandler)
175 : _onExitHandler(onExitHandler)
176 {
177 }
179 {
180 _onExitHandler->onExit();
181 }
182private:
183 OnExitHandler * _onExitHandler;
184};
185
187public:
188
189 void bind(SGPropertyNode* rn)
190 {
191 _rootNode = rn;
192
193 _PO_stationType = PropertyObject<string>(_rootNode->getNode("station-type", true));
194 _PO_stationName = PropertyObject<string>(_rootNode->getNode("station-name", true));
195 _PO_airportId = PropertyObject<string>(_rootNode->getNode("airport-id", true));
196 _PO_signalQuality_norm = PropertyObject<double>(_rootNode->getNode("signal-quality-norm", true));
197 _PO_slantDistance_m = PropertyObject<double>(_rootNode->getNode("slant-distance-m", true));
198 _PO_trueBearingTo_deg = PropertyObject<double>(_rootNode->getNode("true-bearing-to-deg", true));
199 _PO_trueBearingFrom_deg = PropertyObject<double>(_rootNode->getNode("true-bearing-from-deg", true));
200 _PO_trackDistance_m = PropertyObject<double>(_rootNode->getNode("track-distance-m", true));
201 _PO_heightAboveStation_ft = PropertyObject<double>(_rootNode->getNode("height-above-station-ft", true));
202 }
203
205 {
206 }
207
208protected:
209 SGPropertyNode_ptr _rootNode;
210
211 std::string _stationType;
212 std::string _stationName;
213 std::string _airportId;
215 double _slantDistance_m = 0.0;
216 double _trueBearingTo_deg = 0.0;
218 double _trackDistance_m = 0.0;
220
221private:
222 PropertyObject<string> _PO_stationType;
223 PropertyObject<string> _PO_stationName;
224 PropertyObject<string> _PO_airportId;
225 PropertyObject<double> _PO_signalQuality_norm;
226 PropertyObject<double> _PO_slantDistance_m;
227 PropertyObject<double> _PO_trueBearingTo_deg;
228 PropertyObject<double> _PO_trueBearingFrom_deg;
229 PropertyObject<double> _PO_trackDistance_m;
230 PropertyObject<double> _PO_heightAboveStation_ft;
231
232 virtual void onExit()
233 {
234 _PO_stationType = _stationType;
235 _PO_stationName = _stationName;
236 _PO_airportId = _airportId;
237 _PO_signalQuality_norm = _signalQuality_norm;
238 _PO_slantDistance_m = _slantDistance_m;
239 _PO_trueBearingTo_deg = _trueBearingTo_deg;
240 _PO_trueBearingFrom_deg = _trueBearingFrom_deg;
241 _PO_trackDistance_m = _trackDistance_m;
242 _PO_heightAboveStation_ft = _heightAboveStation_ft;
243 }
244};
245
246/* ------------- The CommRadio implementation ---------------------- */
247
248class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
249public:
250
251 void bind();
252 void unbind();
253 void requestMetarForId(std::string & id);
254 void clearMetar();
255 void setMetarPropertiesRoot(SGPropertyNode_ptr n)
256 {
257 _metarPropertiesNode = n;
258 }
259 void setAtisNode(SGPropertyNode * n)
260 {
261 _atisNode = n;
262 }
263
264protected:
265 virtual void valueChanged(SGPropertyNode *);
266
267private:
268 std::string _requestedId;
269 SGPropertyNode_ptr _realWxEnabledNode;
270 SGPropertyNode_ptr _metarPropertiesNode;
271 SGPropertyNode * _atisNode = nullptr;
272 ATISEncoder _atisEncoder;
273};
274
275typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
276
278{
279 _realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
280 _metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
281}
282
284{
285 _metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
286}
287
288void MetarBridge::requestMetarForId(std::string & id)
289{
290 std::string uppercaseId = simgear::strutils::uppercase(id);
291 if (_requestedId == uppercaseId) return;
292 _requestedId = uppercaseId;
293
294 if (_realWxEnabledNode->getBoolValue()) {
295 // trigger a METAR request for the associated metarproperties
296 _metarPropertiesNode->getNode("station-id", true)->setStringValue(uppercaseId);
297 _metarPropertiesNode->getNode("valid", true)->setBoolValue(false);
298 _metarPropertiesNode->getNode("time-to-live", true)->setDoubleValue(0.0);
299 } else {
300 // use the present weather to generate the ATIS.
301 if ( NULL != _atisNode && !_requestedId.empty()) {
302 CurrentWeatherATISInformationProvider provider(_requestedId);
303 _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
304 }
305 }
306}
307
309{
310 string empty;
311 requestMetarForId(empty);
312}
313
314void MetarBridge::valueChanged(SGPropertyNode * node)
315{
316 // check for raising edge of valid flag
317 if ( NULL == node || !node->getBoolValue() || !_realWxEnabledNode->getBoolValue()) return;
318
319 std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
320
321 // unrequested metar!?
322 if (responseId != _requestedId) return;
323
324 if ( NULL != _atisNode) {
325 MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
326 _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
327 }
328}
329
330/* ------------- 8.3kHz Channel implementation ---------------------- */
331
334 public SGPropertyChangeListener {
335public:
336 EightPointThreeFrequencyFormatter( SGPropertyNode_ptr root,
337 const char * channel,
338 const char * fmt,
339 const char * width,
340 const char * frq,
341 const char * cnum ) :
342 _channel( root, channel ),
343 _frequency( root, frq ),
344 _channelSpacing( root, width ),
345 _formattedChannel( root, fmt ),
346 _channelNum( root, cnum )
347 {
348 // ensure properties exist.
349 _channel.node(true);
350 _frequency.node(true);
351 _channelSpacing.node(true);
352 _channelNum.node(true);
353 _formattedChannel.node(true);
354
355 _channel.node()->addChangeListener( this, true );
356 _channelNum.node()->addChangeListener( this, true );
357 }
358
360 {
361 _channel.node()->removeChangeListener( this );
362 _channelNum.node()->removeChangeListener( this );
363 }
364
365private:
368
369 void valueChanged (SGPropertyNode * prop) {
370 if( prop == _channel.node() )
371 setFrequency(prop->getDoubleValue());
372 else if( prop == _channelNum.node() )
373 setChannel(prop->getIntValue());
374 }
375
376 void setChannel( int channel ) {
377 channel %= 3040;
378 if( channel < 0 ) channel += 3040;
379 double f = 118.000 + 0.025*(channel/4) + 0.005*(channel%4);
380 if( f != _channel ) _channel = f;
381 }
382
383 void setFrequency( double channel ) {
384 // format as fixed decimal "nnn.nnn"
385 std::ostringstream buf;
386 buf << std::fixed
387 << std::setw(6)
388 << std::setfill('0')
389 << std::setprecision(3)
390 << _channel;
391 _formattedChannel = buf.str();
392
393 // sanitize range and round to nearest kHz.
394 unsigned c = static_cast<int>(SGMiscd::round(SGMiscd::clip( channel, 118.0, 136.99 ) * 1000));
395
396 if ( (c % 25) == 0 ) {
397 // legacy 25kHz channels continue to be just that.
398 _channelSpacing = 25.0;
399 _frequency = c / 1000.0;
400
401 int channelNum = (c-118000)/25*4;
402 if( channelNum != _channelNum ) _channelNum = channelNum;
403
404 if( _frequency != channel ) {
405 const double channelValue = c / 1000.0;
406 _channel = channelValue;
407 }
408 } else {
409 _channelSpacing = 8.33;
410
411 // 25kHz base frequency: xxx.000, xxx.025, xxx.050, xxx.075
412 unsigned base25 = (c/25) * 25;
413
414 // add n*8.33 to the 25kHz frequency
415 unsigned subChannel = SGMisc<unsigned>::clip((c - base25)/5-1, 0, 2 );
416
417 _frequency = (base25 + 8.33 * subChannel)/1000.0;
418
419 int channelNum = (base25-118000)/25*4 + subChannel+1;
420 if( channelNum != _channelNum ) _channelNum = channelNum;
421
422 // set to correct channel on bogous input
423 double sanitizedChannel = (base25 + 5*(subChannel+1))/1000.0;
424 if( sanitizedChannel != channel ) {
425 _channel = sanitizedChannel; // triggers recursion
426 }
427 }
428 }
429
430 double getFrequency() const {
431 return _channel;
432 }
433
434
435 PropertyObject<double> _channel;
436 PropertyObject<double> _frequency;
437 PropertyObject<double> _channelSpacing;
438 PropertyObject<string> _formattedChannel;
439 PropertyObject<int> _channelNum;
440
441};
442
443
444/* ------------- The CommRadio implementation ---------------------- */
445
448{
449public:
450 CommRadioImpl(SGPropertyNode_ptr node);
451 virtual ~CommRadioImpl();
452
453 // Subsystem API.
454 void bind() override;
455 void init() override;
456 void unbind() override;
457 void update(double dt) override;
458
459private:
460 bool _useEightPointThree = false;
461 MetarBridgeRef _metarBridge;
462 AtisSpeaker _atisSpeaker;
463 SGSharedPtr<FrequencyFormatterBase> _useFrequencyFormatter;
464 SGSharedPtr<FrequencyFormatterBase> _stbyFrequencyFormatter;
465 const SignalQualityComputerRef _signalQualityComputer;
466
467 double _stationTTL = 0.0;
468 double _frequency = -1.0;
469 flightgear::CommStationRef _commStationForFrequency;
470
471 PropertyObject<double> _volume_norm;
472 PropertyObject<bool> _pushToTalk;
473 bool _fullDuplexConfig = false;
474 PropertyObject<bool> _fullDuplex;
475 PropertyObject<bool> _receivingFlag;
476 PropertyObject<string> _atis;
477 PropertyObject<bool> _addNoise;
478 PropertyObject<double> _cutoffSignalQuality;
479
480 SGPropertyNode_ptr _atis_enabled_node;
481 bool _atis_enabled_prev;
482 SGSharedPtr<SGSoundSample> _atis_sample;
483
484 std::string _soundPrefix;
485 void stopAudio();
486 void updateAudio();
487
488 SGSampleGroup* _sampleGroup = nullptr;
489};
490
491CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node) :
492 _metarBridge(new MetarBridge),
493 _signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer),
494 _atis_enabled_prev(false)
495{
496 // set a special value to indicate we don't require a power supply node
497 // by default
498 setDefaultPowerSupplyPath("NO_DEFAULT");
499 readConfig(node, "comm");
500 _soundPrefix = name() + "_" + std::to_string(number()) + "_";
501 _useEightPointThree = node->getBoolValue("eight-point-three", false );
502 _fullDuplexConfig = node->getBoolValue("full-duplex", _fullDuplexConfig);
503}
504
508
510{
511 SGPropertyNode_ptr n = fgGetNode(nodePath(), true);
513
514 _pushToTalk = PropertyObject<bool>(_rootNode->getNode("ptt", true));
515 _fullDuplex = PropertyObject<bool>::create(_rootNode->getNode("full-duplex", true), _fullDuplexConfig);
516 _receivingFlag = PropertyObject<bool>::create(_rootNode->getNode("receiving-flag", true), false);
517 _volume_norm = PropertyObject<double>(_rootNode->getNode("volume", true));
518 _atis = PropertyObject<string>(_rootNode->getNode("atis", true));
519 if (!fgHasNode("/sim/atis/enabled")) fgSetBool("/sim/atis/enabled", true);
520 _atis_enabled_node = fgGetNode("/sim/atis/enabled");
521 _addNoise = PropertyObject<bool>(_rootNode->getNode("add-noise", true));
522 _cutoffSignalQuality = PropertyObject<double>(_rootNode->getNode("cutoff-signal-quality", true));
523
524 _metarBridge->setAtisNode(_atis.node());
525 _atis.node()->addChangeListener(&_atisSpeaker);
526
527 // link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
528 // see FGDATA/Environment/environment.xml
529 _metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", number() + 3, true));
530 _metarBridge->bind();
531
532 if (_useEightPointThree) {
533 _useFrequencyFormatter = new EightPointThreeFrequencyFormatter(
534 _rootNode->getNode("frequencies", true),
535 "selected-mhz",
536 "selected-mhz-fmt",
537 "selected-channel-width-khz",
538 "selected-real-frequency-mhz",
539 "selected-channel"
540 );
541 _stbyFrequencyFormatter = new EightPointThreeFrequencyFormatter(
542 _rootNode->getNode("frequencies", true),
543 "standby-mhz",
544 "standby-mhz-fmt",
545 "standby-channel-width-khz",
546 "standby-real-frequency-mhz",
547 "standby-channel"
548 );
549 } else {
550 _useFrequencyFormatter = new FrequencyFormatter(
551 _rootNode->getNode("frequencies/selected-mhz", true),
552 _rootNode->getNode("frequencies/selected-mhz-fmt", true),
553 0.025, 118.0, 137.0);
554
555 _stbyFrequencyFormatter = new FrequencyFormatter(
556 _rootNode->getNode("frequencies/standby-mhz", true),
557 _rootNode->getNode("frequencies/standby-mhz-fmt", true),
558 0.025, 118.0, 137.0);
559
560
561 }
562}
563
565{
566 _atis.node()->removeChangeListener(&_atisSpeaker);
567
568 stopAudio();
569
570 _metarBridge->unbind();
572}
573
575{
577
578 string s;
579 // initialize squelch to a sane value if unset
580 s = _cutoffSignalQuality.node()->getStringValue();
581 if (s.empty()) _cutoffSignalQuality = 0.4;
582
583 // initialize add-noize to true if unset
584 s = _addNoise.node()->getStringValue();
585 if (s.empty()) _addNoise = true;
586
587 auto soundManager = globals->get_subsystem<SGSoundMgr>();
588 if (soundManager) {
589 _sampleGroup = soundManager->find("atc", true);
590 }
591}
592
594{
595 if (dt < SGLimitsd::min()) return;
596 _stationTTL -= dt;
597
598 // Ensure all output properties get written on exit of this method
599 OnExit onExit(this);
600
601 SGGeod position;
602 try {
603 position = globals->get_aircraft_position();
604 }
605 catch (std::exception &) {
606 return;
607 }
608
610 _metarBridge->clearMetar();
611 _atis = "";
612 _stationTTL = 0.0;
613 stopAudio();
614 _receivingFlag = false;
615 return;
616 }
617
618 if (_frequency != _useFrequencyFormatter->getFrequency()) {
619 _frequency = _useFrequencyFormatter->getFrequency();
620 _stationTTL = 0.0;
621 }
622
623 if (_stationTTL <= 0.0) {
624 _stationTTL = 30.0;
625
626 int freqKhz = static_cast<int>(_frequency * 1000 + 0.5);
627 // make sure the frequency is integral multiple of 25, if not using 8.333 kHz spacing
628 if (!_useEightPointThree && freqKhz % 25) {
629 freqKhz += 5;
630 }
631
632 _commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
633 }
634
635 if (!_commStationForFrequency.valid()) {
636 stopAudio();
637 _receivingFlag = false;
638 return;
639 }
640
641 _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
642
643 SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
644
645 _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
646
647 _signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m * SG_METER_TO_NM);
648 _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
649 _stationName = _commStationForFrequency->ident();
650 _airportId = _commStationForFrequency->airport()->getId();
651
652 _atisSpeaker.setStationId(_airportId);
653
654 switch (_commStationForFrequency->type()) {
657 if (_signalQuality_norm > 0.01) {
658 _metarBridge->requestMetarForId(_airportId);
659 _receivingFlag = _fullDuplex || !_pushToTalk;
660 } else {
661 _metarBridge->clearMetar();
662 _atis = "";
663 _receivingFlag = false;
664 }
665 }
666 break;
667
668 default:
669 _metarBridge->clearMetar();
670 _atis = "";
671 _receivingFlag = false;
672 break;
673 }
674
675 updateAudio();
676}
677
678void CommRadioImpl::updateAudio()
679{
680 if (!_sampleGroup)
681 return;
682
683 const string noiseRef = _soundPrefix + "_noise";
684 const string atisRef = _soundPrefix + "_atis";
685
686 SGSoundSample* noiseSample = _sampleGroup->find(noiseRef);
687
688 // create noise sample if necessary, and play forever
689 if (_addNoise && !noiseSample) {
690 SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
691 _sampleGroup->add(noise, noiseRef);
692 _sampleGroup->play_looped(noiseRef);
693 noiseSample = noise;
694 }
695
696 bool atis_enabled = _atis_enabled_node->getBoolValue();
697 int atis_delta = 0;
698 if (atis_enabled && !_atis_enabled_prev) atis_delta = 1;
699 if (!atis_enabled && _atis_enabled_prev) atis_delta = -1;
700
701 if (_atisSpeaker.hasSpokenAtis()) {
702 // the speaker has created a new atis sample
703 // remove previous atis sample
704 _sampleGroup->remove(atisRef);
705 if (!atis_delta && atis_enabled) atis_delta = 1;
706 }
707 if (atis_delta == 1) {
708 // Start play of atis text. We store the most recent sample in _atis_sample
709 // so that we can resume if /sim/atis/enabled is changed from false to
710 // true.
711 SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
712 if (sample) _atis_sample = sample;
713 else sample = _atis_sample;
714 if (sample) {
715 SG_LOG(SG_INSTR, SG_DEBUG, "starting looped play of atis sample.");
716 _sampleGroup->add(sample, atisRef);
717 _sampleGroup->play_looped(atisRef);
718 }
719 else {
720 SG_LOG(SG_INSTR, SG_DEBUG, "no atis sample available");
721 }
722 }
723 if (atis_delta == -1) {
724 // Stop play of atis text.
725 _sampleGroup->remove(atisRef);
726 }
727 _atis_enabled_prev = atis_enabled;
728
729 // adjust volumes
730 const auto targetVolume = (_pushToTalk && !_fullDuplex) ? 0.0 : _volume_norm;
731 const bool doSquelch = (_signalQuality_norm < _cutoffSignalQuality);
732 double atisVolume = doSquelch ? 0.0 : targetVolume;
733 if (_addNoise) {
734 double noiseVol = (1.0 - _signalQuality_norm) * targetVolume;
735 if (_cutoffSignalQuality < 0.01) {
736 // ensure noise is still heard disabling squelch
737 // see https://sourceforge.net/p/flightgear/codetickets/2846/
738 noiseVol = targetVolume;
739 }
740
741 atisVolume = _signalQuality_norm * targetVolume;
742 noiseSample->set_volume(doSquelch ? 0.0: noiseVol);
743 }
744
745 SGSoundSample* s = _sampleGroup->find(atisRef);
746 if (s) {
747 s->set_volume(atisVolume);
748 }
749}
750
751void CommRadioImpl::stopAudio()
752{
753 if (_sampleGroup) {
754 _sampleGroup->remove(_soundPrefix + "_noise");
755 _sampleGroup->remove(_soundPrefix + "_atis");
756 }
757}
758
759SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
760{
761 return new CommRadioImpl(rootNode);
762}
763
764
765// Register the subsystem.
766#if 0
767SGSubsystemMgr::InstancedRegistrant<CommRadio> registrantCommRadio(
768 SGSubsystemMgr::FDM,
769 {{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
770#endif
771
772} // namespace Instrumentation
773
#define i(x)
void initServicePowerProperties(SGPropertyNode *node)
std::string name() const
void readConfig(SGPropertyNode *config, std::string defaultName)
std::string nodePath() const
void setDefaultPowerSupplyPath(const std::string &p)
specify the default path to use to power the instrument, if it's non- standard.
bool isServiceableAndPowered() const
A Voice Synthesizer using FLITE+HTS.
virtual SGSoundSample * synthesize(const std::string &text, double volume, double speed, double pitch)
static std::string getVoicePath(voice_t voice)
virtual void SoundSampleReady(SGSharedPtr< SGSoundSample >)
virtual void valueChanged(SGPropertyNode *node)
Definition commradio.cxx:76
void setStationId(const string &stationId)
Definition commradio.cxx:52
SGSharedPtr< SGSoundSample > getSpokenAtis()
Definition commradio.cxx:47
CommRadioImpl(SGPropertyNode_ptr node)
void update(double dt) override
static SGSubsystem * createInstance(SGPropertyNode_ptr rootNode)
EightPointThreeFrequencyFormatter(SGPropertyNode_ptr root, const char *channel, const char *fmt, const char *width, const char *frq, const char *cnum)
void setAtisNode(SGPropertyNode *n)
void requestMetarForId(std::string &id)
virtual void valueChanged(SGPropertyNode *)
void setMetarPropertiesRoot(SGPropertyNode_ptr n)
OnExit(OnExitHandler *onExitHandler)
void bind(SGPropertyNode *rn)
static CommStationRef findByFreq(int freqKhz, const SGGeod &pos, FGPositioned::Filter *filt=NULL)
bool fgHasNode(const char *path)
Test whether a given node exists.
Definition fg_props.cxx:507
FGGlobals * globals
Definition globals.cxx:142
SGSharedPtr< SignalQualityComputer > SignalQualityComputerRef
Definition commradio.hxx:21
SGSharedPtr< MetarBridge > MetarBridgeRef
SGSharedPtr< CommStation > CommStationRef
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
Definition proptest.cpp:31
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
Definition proptest.cpp:24
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
Definition proptest.cpp:30
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
SoundSampleReadyListener * listener