]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/commradio.cxx
commradio: typo: cuttoff --> cutoff
[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 private:
73   SynthesizeRequest _synthesizeRequest;
74   SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
75 };
76
77 AtisSpeaker::AtisSpeaker()
78 {
79   _synthesizeRequest.listener = this;
80 }
81
82 AtisSpeaker::~AtisSpeaker()
83 {
84
85 }
86 void AtisSpeaker::valueChanged(SGPropertyNode * node)
87 {
88   if (!fgGetBool("/sim/sound/working", false))
89     return;
90
91   string newText = node->getStringValue();
92   if (_synthesizeRequest.text == newText) return;
93
94   _synthesizeRequest.text = newText;
95
96   FGSoundManager * smgr = dynamic_cast<FGSoundManager*>(globals->get_soundmgr());
97   assert(smgr != NULL);
98
99   string voice = globals->get_fg_root() + "/ATC/cmu_us_arctic_slt.htsvoice";
100   FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
101
102   synthesizer->synthesize(_synthesizeRequest);
103 }
104
105 void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
106 {
107   // we are now in the synthesizers worker thread!
108   _spokenAtis.push(sample);
109 }
110 #endif
111
112 SignalQualityComputer::~SignalQualityComputer()
113 {
114 }
115
116 class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
117 public:
118   SimpleDistanceSquareSignalQualityComputer(double range)
119       : _rangeM(range), _rangeM2(range * range)
120   {
121   }
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;
125   private:
126   double _rangeM;
127   double _rangeM2;
128 };
129
130 double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(const SGVec3d & sender, const SGVec3d & receiver) const
131     {
132   return computeSignalQuality(dist(sender, receiver));
133 }
134
135 double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(const SGGeod & sender, const SGGeod & receiver) const
136     {
137   return computeSignalQuality(SGGeodesy::distanceM(sender, receiver));
138 }
139
140 double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(double distanceM) const
141     {
142   return distanceM < _rangeM ? 1.0 : (_rangeM2 / (distanceM * distanceM));
143 }
144
145 class OnExitHandler {
146 public:
147   virtual void onExit() = 0;
148   virtual ~OnExitHandler()
149   {
150   }
151 };
152
153 class OnExit {
154 public:
155   OnExit(OnExitHandler * onExitHandler)
156       : _onExitHandler(onExitHandler)
157   {
158   }
159   ~OnExit()
160   {
161     _onExitHandler->onExit();
162   }
163 private:
164   OnExitHandler * _onExitHandler;
165 };
166
167 class OutputProperties: public OnExitHandler {
168 public:
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),
172
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))
178   {
179   }
180   virtual ~OutputProperties()
181   {
182   }
183
184 protected:
185   SGPropertyNode_ptr _rootNode;
186
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;
196
197 private:
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;
207
208   virtual void onExit()
209   {
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;
219   }
220 };
221
222 /* ------------- The CommRadio implementation ---------------------- */
223
224 class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
225 public:
226   MetarBridge();
227   ~MetarBridge();
228
229   void bind();
230   void unbind();
231   void requestMetarForId(std::string & id);
232   void clearMetar();
233   void setMetarPropertiesRoot(SGPropertyNode_ptr n)
234   {
235     _metarPropertiesNode = n;
236   }
237   void setAtisNode(SGPropertyNode * n)
238   {
239     _atisNode = n;
240   }
241
242 protected:
243   virtual void valueChanged(SGPropertyNode *);
244
245 private:
246   std::string _requestedId;
247   SGPropertyNode_ptr _realWxEnabledNode;
248   SGPropertyNode_ptr _metarPropertiesNode;
249   SGPropertyNode * _atisNode;
250   ATISEncoder _atisEncoder;
251 };
252 typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
253
254 MetarBridge::MetarBridge()
255     : _atisNode(0)
256 {
257 }
258
259 MetarBridge::~MetarBridge()
260 {
261 }
262
263 void MetarBridge::bind()
264 {
265   _realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
266   _metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
267 }
268
269 void MetarBridge::unbind()
270 {
271   _metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
272 }
273
274 void MetarBridge::requestMetarForId(std::string & id)
275 {
276   std::string uppercaseId = simgear::strutils::uppercase(id);
277   if (_requestedId == uppercaseId) return;
278   _requestedId = uppercaseId;
279
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);
285   } else {
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));
290     }
291   }
292 }
293
294 void MetarBridge::clearMetar()
295 {
296   string empty;
297   requestMetarForId(empty);
298 }
299
300 void MetarBridge::valueChanged(SGPropertyNode * node)
301 {
302   // check for raising edge of valid flag
303   if ( NULL == node || false == node->getBoolValue() || false == _realWxEnabledNode->getBoolValue()) return;
304
305   std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
306
307   // unrequested metar!?
308   if (responseId != _requestedId) return;
309
310   if ( NULL != _atisNode) {
311     MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
312     _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
313   }
314 }
315
316 /* ------------- The CommRadio implementation ---------------------- */
317
318 class CommRadioImpl: public CommRadio, OutputProperties {
319
320 public:
321   CommRadioImpl(SGPropertyNode_ptr node);
322   virtual ~CommRadioImpl();
323
324   virtual void update(double dt);
325   virtual void init();
326   void bind();
327   void unbind();
328
329 private:
330   string getSampleGroupRefname() const
331   {
332     return _rootNode->getPath();
333   }
334
335   int _num;
336   MetarBridgeRef _metarBridge;
337   #if defined(ENABLE_FLITE)
338   AtisSpeaker _atisSpeaker;
339   #endif
340   FrequencyFormatter _useFrequencyFormatter;
341   FrequencyFormatter _stbyFrequencyFormatter;
342   const SignalQualityComputerRef _signalQualityComputer;
343
344   double _stationTTL;
345   double _frequency;
346   flightgear::CommStationRef _commStationForFrequency;
347   #if defined(ENABLE_FLITE)
348   SGSharedPtr<SGSampleGroup> _sampleGroup;
349   #endif
350
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;
358 };
359
360 CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node)
361     : OutputProperties(
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),
367
368         _stbyFrequencyFormatter(_rootNode->getNode("frequencies/standby-mhz", true),
369             _rootNode->getNode("frequencies/standby-mhz-fmt", true), 0.025, 118.0, 136.0),
370
371         _signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer(50 * SG_NM_TO_METER)),
372
373         _stationTTL(0.0),
374         _frequency(-1.0),
375         _commStationForFrequency(NULL),
376
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))
384 {
385 }
386
387 CommRadioImpl::~CommRadioImpl()
388 {
389 }
390
391 void CommRadioImpl::bind()
392 {
393   _metarBridge->setAtisNode(_atis.node());
394 #if defined(ENABLE_FLITE)
395   _atis.node()->addChangeListener(&_atisSpeaker);
396 #endif
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();
401 }
402
403 void CommRadioImpl::unbind()
404 {
405 #if defined(ENABLE_FLITE)
406   _atis.node()->removeChangeListener(&_atisSpeaker);
407   if (_sampleGroup.valid()) {
408     globals->get_soundmgr()->remove(getSampleGroupRefname());
409   }
410 #endif
411   _metarBridge->unbind();
412 }
413
414 void CommRadioImpl::init()
415 {
416   // initialize power_btn to true if unset
417   string s = _power_btn.node()->getStringValue();
418   if (s.empty()) _power_btn = true;
419
420   // initialize squelch to a sane value if unset
421   s = _cutoffSignalQuality.node()->getStringValue();
422   if (s.empty()) _cutoffSignalQuality = 0.4;
423 }
424
425 void CommRadioImpl::update(double dt)
426 {
427   if (dt < SGLimitsd::min()) return;
428   _stationTTL -= dt;
429
430   // Ensure all output properties get written on exit of this method
431   OnExit onExit(this);
432
433   SGGeod position;
434   try {
435     position = globals->get_aircraft_position();
436   }
437   catch (std::exception &) {
438     return;
439   }
440
441 #if defined(ENABLE_FLITE)
442   {
443     static const char * atisSampleRefName = "atis";
444     static const char * noiseSampleRefName = "noise";
445
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();
452         if (_addNoise) {
453           SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
454           _sampleGroup->add(noise, noiseSampleRefName);
455           _sampleGroup->play_looped(noiseSampleRefName);
456         }
457
458       }
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);
465     }
466
467     if (_sampleGroup.valid()) {
468       if (_addNoise) {
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);
472         if ( NULL != s) {
473           s->set_volume(1.0 - _signalQuality_norm);
474           s = _sampleGroup->find(atisSampleRefName);
475           s->set_volume(_signalQuality_norm);
476         }
477       }
478       // master volume for radio, mute on bad signal quality
479       _sampleGroup->set_volume(_signalQuality_norm >= _cutoffSignalQuality ? _volume_norm : 0.0);
480     }
481   }
482 #endif
483
484   if (false == (_power_btn)) {
485     _stationTTL = 0.0;
486     return;
487   }
488
489   if (_frequency != _useFrequencyFormatter.getFrequency()) {
490     _frequency = _useFrequencyFormatter.getFrequency();
491     _stationTTL = 0.0;
492   }
493
494   if (_stationTTL <= 0.0) {
495     _stationTTL = 30.0;
496
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);
501
502   }
503
504   if (false == _commStationForFrequency.valid()) return;
505
506   _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
507
508   SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
509
510   _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
511
512   _signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m);
513   _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
514   _stationName = _commStationForFrequency->ident();
515   _airportId = _commStationForFrequency->airport()->getId();
516
517   switch (_commStationForFrequency->type()) {
518     case FGPositioned::FREQ_ATIS:
519       case FGPositioned::FREQ_AWOS: {
520       if (_signalQuality_norm > 0.01) {
521         _metarBridge->requestMetarForId(_airportId);
522       } else {
523         _metarBridge->clearMetar();
524         _atis = "";
525       }
526     }
527       break;
528
529     default:
530       _metarBridge->clearMetar();
531       _atis = "";
532       break;
533   }
534 }
535
536 SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
537 {
538   return new CommRadioImpl(rootNode);
539 }
540
541 } // namespace Instrumentation
542