1 // commradio.cxx -- class to manage a nav radio instance
3 // Written by Torsten Dreyer, February 2014
5 // Copyright (C) 2000 - 2011 Curtis L. Olson - http://www.flightgear.org/~curt
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.
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.
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.
26 #include "commradio.hxx"
29 #include <boost/foreach.hpp>
31 #include <simgear/sg_inlines.h>
32 #include <simgear/props/propertyObject.hxx>
33 #include <simgear/misc/strutils.hxx>
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>
42 #if defined(ENABLE_FLITE)
43 #include <Sound/soundmanager.hxx>
44 #include <simgear/sound/sample_group.hxx>
45 #include <Sound/VoiceSynthesizer.hxx>
48 #include "frequencyformatter.hxx"
50 namespace Instrumentation {
52 using simgear::PropertyObject;
55 #if defined(ENABLE_FLITE)
56 class AtisSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
59 virtual ~AtisSpeaker();
60 virtual void valueChanged(SGPropertyNode * node);
61 virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
65 return _spokenAtis.empty() == false;
68 SGSharedPtr<SGSoundSample> getSpokenAtis()
70 return _spokenAtis.pop();
73 SynthesizeRequest _synthesizeRequest;
74 SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
77 AtisSpeaker::AtisSpeaker()
79 _synthesizeRequest.listener = this;
82 AtisSpeaker::~AtisSpeaker()
86 void AtisSpeaker::valueChanged(SGPropertyNode * node)
88 if (!fgGetBool("/sim/sound/working", false))
91 string newText = node->getStringValue();
92 if (_synthesizeRequest.text == newText) return;
94 _synthesizeRequest.text = newText;
96 FGSoundManager * smgr = dynamic_cast<FGSoundManager*>(globals->get_soundmgr());
99 string voice = globals->get_fg_root() + "/ATC/cmu_us_arctic_slt.htsvoice";
100 FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
102 synthesizer->synthesize(_synthesizeRequest);
105 void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
107 // we are now in the synthesizers worker thread!
108 _spokenAtis.push(sample);
112 SignalQualityComputer::~SignalQualityComputer()
116 class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
118 SimpleDistanceSquareSignalQualityComputer(double range)
119 : _rangeM(range), _rangeM2(range * range)
122 virtual double computeSignalQuality(const SGGeod & sender, const SGGeod & receiver) const;
123 virtual double computeSignalQuality(const SGVec3d & sender, const SGVec3d & receiver) const;
124 virtual double computeSignalQuality(double slantDistanceM) const;
130 double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(const SGVec3d & sender, const SGVec3d & receiver) const
132 return computeSignalQuality(dist(sender, receiver));
135 double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(const SGGeod & sender, const SGGeod & receiver) const
137 return computeSignalQuality(SGGeodesy::distanceM(sender, receiver));
140 double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(double distanceM) const
142 return distanceM < _rangeM ? 1.0 : (_rangeM2 / (distanceM * distanceM));
145 class OnExitHandler {
147 virtual void onExit() = 0;
148 virtual ~OnExitHandler()
155 OnExit(OnExitHandler * onExitHandler)
156 : _onExitHandler(onExitHandler)
161 _onExitHandler->onExit();
164 OnExitHandler * _onExitHandler;
167 class OutputProperties: public OnExitHandler {
169 OutputProperties(SGPropertyNode_ptr rootNode)
170 : _rootNode(rootNode), _signalQuality_norm(0.0), _slantDistance_m(0.0), _trueBearingTo_deg(0.0), _trueBearingFrom_deg(0.0), _trackDistance_m(
171 0.0), _heightAboveStation_ft(0.0),
173 _PO_stationType(rootNode->getNode("station-type", true)), _PO_stationName(rootNode->getNode("station-name", true)), _PO_airportId(
174 rootNode->getNode("airport-id", true)), _PO_signalQuality_norm(rootNode->getNode("signal-quality-norm", true)), _PO_slantDistance_m(
175 rootNode->getNode("slant-distance-m", true)), _PO_trueBearingTo_deg(rootNode->getNode("true-bearing-to-deg", true)), _PO_trueBearingFrom_deg(
176 rootNode->getNode("true-bearing-from-deg", true)), _PO_trackDistance_m(rootNode->getNode("track-distance-m", true)), _PO_heightAboveStation_ft(
177 rootNode->getNode("height-above-station-ft", true))
180 virtual ~OutputProperties()
185 SGPropertyNode_ptr _rootNode;
187 std::string _stationType;
188 std::string _stationName;
189 std::string _airportId;
190 double _signalQuality_norm;
191 double _slantDistance_m;
192 double _trueBearingTo_deg;
193 double _trueBearingFrom_deg;
194 double _trackDistance_m;
195 double _heightAboveStation_ft;
198 PropertyObject<string> _PO_stationType;
199 PropertyObject<string> _PO_stationName;
200 PropertyObject<string> _PO_airportId;
201 PropertyObject<double> _PO_signalQuality_norm;
202 PropertyObject<double> _PO_slantDistance_m;
203 PropertyObject<double> _PO_trueBearingTo_deg;
204 PropertyObject<double> _PO_trueBearingFrom_deg;
205 PropertyObject<double> _PO_trackDistance_m;
206 PropertyObject<double> _PO_heightAboveStation_ft;
208 virtual void onExit()
210 _PO_stationType = _stationType;
211 _PO_stationName = _stationName;
212 _PO_airportId = _airportId;
213 _PO_signalQuality_norm = _signalQuality_norm;
214 _PO_slantDistance_m = _slantDistance_m;
215 _PO_trueBearingTo_deg = _trueBearingTo_deg;
216 _PO_trueBearingFrom_deg = _trueBearingFrom_deg;
217 _PO_trackDistance_m = _trackDistance_m;
218 _PO_heightAboveStation_ft = _heightAboveStation_ft;
222 /* ------------- The CommRadio implementation ---------------------- */
224 class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
231 void requestMetarForId(std::string & id);
233 void setMetarPropertiesRoot(SGPropertyNode_ptr n)
235 _metarPropertiesNode = n;
237 void setAtisNode(SGPropertyNode * n)
243 virtual void valueChanged(SGPropertyNode *);
246 std::string _requestedId;
247 SGPropertyNode_ptr _realWxEnabledNode;
248 SGPropertyNode_ptr _metarPropertiesNode;
249 SGPropertyNode * _atisNode;
250 ATISEncoder _atisEncoder;
252 typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
254 MetarBridge::MetarBridge()
259 MetarBridge::~MetarBridge()
263 void MetarBridge::bind()
265 _realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
266 _metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
269 void MetarBridge::unbind()
271 _metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
274 void MetarBridge::requestMetarForId(std::string & id)
276 std::string uppercaseId = simgear::strutils::uppercase(id);
277 if (_requestedId == uppercaseId) return;
278 _requestedId = uppercaseId;
280 if (_realWxEnabledNode->getBoolValue()) {
281 // trigger a METAR request for the associated metarproperties
282 _metarPropertiesNode->getNode("station-id", true)->setStringValue(uppercaseId);
283 _metarPropertiesNode->getNode("valid", true)->setBoolValue(false);
284 _metarPropertiesNode->getNode("time-to-live", true)->setDoubleValue(0.0);
286 // use the present weather to generate the ATIS.
287 if ( NULL != _atisNode && false == _requestedId.empty()) {
288 CurrentWeatherATISInformationProvider provider(_requestedId);
289 _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
294 void MetarBridge::clearMetar()
297 requestMetarForId(empty);
300 void MetarBridge::valueChanged(SGPropertyNode * node)
302 // check for raising edge of valid flag
303 if ( NULL == node || false == node->getBoolValue() || false == _realWxEnabledNode->getBoolValue()) return;
305 std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
307 // unrequested metar!?
308 if (responseId != _requestedId) return;
310 if ( NULL != _atisNode) {
311 MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
312 _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
316 /* ------------- The CommRadio implementation ---------------------- */
318 class CommRadioImpl: public CommRadio, OutputProperties {
321 CommRadioImpl(SGPropertyNode_ptr node);
322 virtual ~CommRadioImpl();
324 virtual void update(double dt);
330 string getSampleGroupRefname() const
332 return _rootNode->getPath();
336 MetarBridgeRef _metarBridge;
337 #if defined(ENABLE_FLITE)
338 AtisSpeaker _atisSpeaker;
340 FrequencyFormatter _useFrequencyFormatter;
341 FrequencyFormatter _stbyFrequencyFormatter;
342 const SignalQualityComputerRef _signalQualityComputer;
346 flightgear::CommStationRef _commStationForFrequency;
347 #if defined(ENABLE_FLITE)
348 SGSharedPtr<SGSampleGroup> _sampleGroup;
351 PropertyObject<bool> _serviceable;
352 PropertyObject<bool> _power_btn;
353 PropertyObject<bool> _power_good;
354 PropertyObject<double> _volume_norm;
355 PropertyObject<string> _atis;
356 PropertyObject<bool> _addNoise;
357 PropertyObject<double> _cutoffSignalQuality;
360 CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node)
362 fgGetNode("/instrumentation", true)->getNode(node->getStringValue("name", "comm"), node->getIntValue("number", 0), true)),
363 _num(node->getIntValue("number", 0)),
364 _metarBridge(new MetarBridge()),
365 _useFrequencyFormatter(_rootNode->getNode("frequencies/selected-mhz", true),
366 _rootNode->getNode("frequencies/selected-mhz-fmt", true), 0.025, 118.0, 136.0),
368 _stbyFrequencyFormatter(_rootNode->getNode("frequencies/standby-mhz", true),
369 _rootNode->getNode("frequencies/standby-mhz-fmt", true), 0.025, 118.0, 136.0),
371 _signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer(50 * SG_NM_TO_METER)),
375 _commStationForFrequency(NULL),
377 _serviceable(_rootNode->getNode("serviceable", true)),
378 _power_btn(_rootNode->getNode("power-btn", true)),
379 _power_good(_rootNode->getNode("power-good", true)),
380 _volume_norm(_rootNode->getNode("volume", true)),
381 _atis(_rootNode->getNode("atis", true)),
382 _addNoise(_rootNode->getNode("add-noise", true)),
383 _cutoffSignalQuality(_rootNode->getNode("cutoff-signal-quality", true))
387 CommRadioImpl::~CommRadioImpl()
391 void CommRadioImpl::bind()
393 _metarBridge->setAtisNode(_atis.node());
394 #if defined(ENABLE_FLITE)
395 _atis.node()->addChangeListener(&_atisSpeaker);
397 // link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
398 // see FGDATA/Environment/environment.xml
399 _metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", _num + 3, true));
400 _metarBridge->bind();
403 void CommRadioImpl::unbind()
405 #if defined(ENABLE_FLITE)
406 _atis.node()->removeChangeListener(&_atisSpeaker);
407 if (_sampleGroup.valid()) {
408 globals->get_soundmgr()->remove(getSampleGroupRefname());
411 _metarBridge->unbind();
414 void CommRadioImpl::init()
416 // initialize power_btn to true if unset
417 string s = _power_btn.node()->getStringValue();
418 if (s.empty()) _power_btn = true;
420 // initialize squelch to a sane value if unset
421 s = _cutoffSignalQuality.node()->getStringValue();
422 if (s.empty()) _cutoffSignalQuality = 0.4;
425 void CommRadioImpl::update(double dt)
427 if (dt < SGLimitsd::min()) return;
430 // Ensure all output properties get written on exit of this method
435 position = globals->get_aircraft_position();
437 catch (std::exception &) {
441 #if defined(ENABLE_FLITE)
443 static const char * atisSampleRefName = "atis";
444 static const char * noiseSampleRefName = "noise";
446 if (_atisSpeaker.hasSpokenAtis()) {
447 // the speaker has created a new atis sample
448 if (!_sampleGroup.valid()) {
449 // create a sample group for our instrument on the fly
450 _sampleGroup = globals->get_soundmgr()->find(getSampleGroupRefname(), true );
451 _sampleGroup->tie_to_listener();
453 SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
454 _sampleGroup->add(noise, noiseSampleRefName);
455 _sampleGroup->play_looped(noiseSampleRefName);
459 // remove previous atis sample
460 _sampleGroup->remove(atisSampleRefName);
461 // add and play the new atis sample
462 SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
463 _sampleGroup->add(sample, atisSampleRefName);
464 _sampleGroup->play_looped(atisSampleRefName);
467 if (_sampleGroup.valid()) {
469 // if noise is configured and there is a noise sample
470 // scale noise and signal volume by signalQuality.
471 SGSoundSample * s = _sampleGroup->find(noiseSampleRefName);
473 s->set_volume(1.0 - _signalQuality_norm);
474 s = _sampleGroup->find(atisSampleRefName);
475 s->set_volume(_signalQuality_norm);
478 // master volume for radio, mute on bad signal quality
479 _sampleGroup->set_volume(_signalQuality_norm >= _cutoffSignalQuality ? _volume_norm : 0.0);
484 if (false == (_power_btn)) {
489 if (_frequency != _useFrequencyFormatter.getFrequency()) {
490 _frequency = _useFrequencyFormatter.getFrequency();
494 if (_stationTTL <= 0.0) {
497 // Note: 122.375 must be rounded DOWN to 122370
498 // in order to be consistent with apt.dat et cetera.
499 int freqKhz = 10 * static_cast<int>(_frequency * 100 + 0.25);
500 _commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
504 if (false == _commStationForFrequency.valid()) return;
506 _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
508 SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
510 _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
512 _signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m);
513 _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
514 _stationName = _commStationForFrequency->ident();
515 _airportId = _commStationForFrequency->airport()->getId();
517 switch (_commStationForFrequency->type()) {
518 case FGPositioned::FREQ_ATIS:
519 case FGPositioned::FREQ_AWOS: {
520 if (_signalQuality_norm > 0.01) {
521 _metarBridge->requestMetarForId(_airportId);
523 _metarBridge->clearMetar();
530 _metarBridge->clearMetar();
536 SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
538 return new CommRadioImpl(rootNode);
541 } // namespace Instrumentation