]> git.mxchange.org Git - flightgear.git/blob - src/Instrumentation/commradio.cxx
New PathsDialog, for managing locations.
[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
73   void setStationId(const string & stationId)
74   {
75     _stationId = stationId;
76   }
77
78
79 private:
80   SynthesizeRequest _synthesizeRequest;
81   SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
82   string _stationId;
83 };
84
85 AtisSpeaker::AtisSpeaker()
86 {
87   _synthesizeRequest.listener = this;
88 }
89
90 AtisSpeaker::~AtisSpeaker()
91 {
92
93 }
94 void AtisSpeaker::valueChanged(SGPropertyNode * node)
95 {
96   if (!fgGetBool("/sim/sound/working", false))
97     return;
98
99   string newText = node->getStringValue();
100   if (_synthesizeRequest.text == newText) return;
101
102   _synthesizeRequest.text = newText;
103
104   string voice = "cmu_us_arctic_slt";
105
106   if (!_stationId.empty()) {
107     // lets play a bit with the voice so not every airports atis sounds alike
108     // but every atis of an airport has the same voice
109
110     // create a simple hash from the last two letters of the airport's id
111     unsigned char hash = 0;
112     string::iterator i = _stationId.end() - 1;
113     hash += *i;
114
115     if( i != _stationId.begin() ) {
116       --i;
117       hash += *i;
118     }
119
120     _synthesizeRequest.speed = (hash % 16) / 16.0;
121     _synthesizeRequest.pitch = (hash % 16) / 16.0;
122
123     // pick a voice
124     voice = FLITEVoiceSynthesizer::getVoicePath(
125         static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
126   }
127
128
129   FGSoundManager * smgr = dynamic_cast<FGSoundManager*>(globals->get_soundmgr());
130   assert(smgr != NULL);
131
132   SG_LOG(SG_INSTR,SG_INFO,"AtisSpeaker voice is " << voice );
133   FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
134
135   synthesizer->synthesize(_synthesizeRequest);
136 }
137
138 void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
139 {
140   // we are now in the synthesizers worker thread!
141   _spokenAtis.push(sample);
142 }
143 #endif
144
145 SignalQualityComputer::~SignalQualityComputer()
146 {
147 }
148
149 class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
150 public:
151   SimpleDistanceSquareSignalQualityComputer()
152       : _altitudeAgl_ft(fgGetNode("/position/altitude-agl-ft", true))
153   {
154   }
155
156   ~SimpleDistanceSquareSignalQualityComputer()
157   {
158   }
159
160   double computeSignalQuality(double distance_nm) const
161   {
162     // Very simple line of sight propagation model. It's cheap but it does the trick for now.
163     // assume transmitter and receiver antennas are at some elevation above ground
164     // so we have at least a range of 5NM. Add the approx. distance to the horizon.
165     double range_nm = 5.0 + 1.23 * ::sqrt(SGMiscd::max(.0, _altitudeAgl_ft));
166     return distance_nm < range_nm ? 1.0 : (range_nm * range_nm / distance_nm / distance_nm);
167   }
168
169 private:
170   PropertyObject<double> _altitudeAgl_ft;
171 };
172
173 class OnExitHandler {
174 public:
175   virtual void onExit() = 0;
176   virtual ~OnExitHandler()
177   {
178   }
179 };
180
181 class OnExit {
182 public:
183   OnExit(OnExitHandler * onExitHandler)
184       : _onExitHandler(onExitHandler)
185   {
186   }
187   ~OnExit()
188   {
189     _onExitHandler->onExit();
190   }
191 private:
192   OnExitHandler * _onExitHandler;
193 };
194
195 class OutputProperties: public OnExitHandler {
196 public:
197   OutputProperties(SGPropertyNode_ptr rootNode)
198       : _rootNode(rootNode), _signalQuality_norm(0.0), _slantDistance_m(0.0), _trueBearingTo_deg(0.0), _trueBearingFrom_deg(0.0), _trackDistance_m(
199           0.0), _heightAboveStation_ft(0.0),
200
201       _PO_stationType(rootNode->getNode("station-type", true)), _PO_stationName(rootNode->getNode("station-name", true)), _PO_airportId(
202           rootNode->getNode("airport-id", true)), _PO_signalQuality_norm(rootNode->getNode("signal-quality-norm", true)), _PO_slantDistance_m(
203           rootNode->getNode("slant-distance-m", true)), _PO_trueBearingTo_deg(rootNode->getNode("true-bearing-to-deg", true)), _PO_trueBearingFrom_deg(
204           rootNode->getNode("true-bearing-from-deg", true)), _PO_trackDistance_m(rootNode->getNode("track-distance-m", true)), _PO_heightAboveStation_ft(
205           rootNode->getNode("height-above-station-ft", true))
206   {
207   }
208   virtual ~OutputProperties()
209   {
210   }
211
212 protected:
213   SGPropertyNode_ptr _rootNode;
214
215   std::string _stationType;
216   std::string _stationName;
217   std::string _airportId;
218   double _signalQuality_norm;
219   double _slantDistance_m;
220   double _trueBearingTo_deg;
221   double _trueBearingFrom_deg;
222   double _trackDistance_m;
223   double _heightAboveStation_ft;
224
225 private:
226   PropertyObject<string> _PO_stationType;
227   PropertyObject<string> _PO_stationName;
228   PropertyObject<string> _PO_airportId;
229   PropertyObject<double> _PO_signalQuality_norm;
230   PropertyObject<double> _PO_slantDistance_m;
231   PropertyObject<double> _PO_trueBearingTo_deg;
232   PropertyObject<double> _PO_trueBearingFrom_deg;
233   PropertyObject<double> _PO_trackDistance_m;
234   PropertyObject<double> _PO_heightAboveStation_ft;
235
236   virtual void onExit()
237   {
238     _PO_stationType = _stationType;
239     _PO_stationName = _stationName;
240     _PO_airportId = _airportId;
241     _PO_signalQuality_norm = _signalQuality_norm;
242     _PO_slantDistance_m = _slantDistance_m;
243     _PO_trueBearingTo_deg = _trueBearingTo_deg;
244     _PO_trueBearingFrom_deg = _trueBearingFrom_deg;
245     _PO_trackDistance_m = _trackDistance_m;
246     _PO_heightAboveStation_ft = _heightAboveStation_ft;
247   }
248 };
249
250 /* ------------- The CommRadio implementation ---------------------- */
251
252 class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
253 public:
254   MetarBridge();
255   ~MetarBridge();
256
257   void bind();
258   void unbind();
259   void requestMetarForId(std::string & id);
260   void clearMetar();
261   void setMetarPropertiesRoot(SGPropertyNode_ptr n)
262   {
263     _metarPropertiesNode = n;
264   }
265   void setAtisNode(SGPropertyNode * n)
266   {
267     _atisNode = n;
268   }
269
270 protected:
271   virtual void valueChanged(SGPropertyNode *);
272
273 private:
274   std::string _requestedId;
275   SGPropertyNode_ptr _realWxEnabledNode;
276   SGPropertyNode_ptr _metarPropertiesNode;
277   SGPropertyNode * _atisNode;
278   ATISEncoder _atisEncoder;
279 };
280 typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
281
282 MetarBridge::MetarBridge()
283     : _atisNode(0)
284 {
285 }
286
287 MetarBridge::~MetarBridge()
288 {
289 }
290
291 void MetarBridge::bind()
292 {
293   _realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
294   _metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
295 }
296
297 void MetarBridge::unbind()
298 {
299   _metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
300 }
301
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
322 void MetarBridge::clearMetar()
323 {
324   string empty;
325   requestMetarForId(empty);
326 }
327
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 /* ------------- The CommRadio implementation ---------------------- */
345
346 class CommRadioImpl: public CommRadio, OutputProperties {
347
348 public:
349   CommRadioImpl(SGPropertyNode_ptr node);
350   virtual ~CommRadioImpl();
351
352   virtual void update(double dt);
353   virtual void init();
354   void bind();
355   void unbind();
356
357 private:
358   string getSampleGroupRefname() const
359   {
360     return _rootNode->getPath();
361   }
362
363   int _num;
364   MetarBridgeRef _metarBridge;
365   #if defined(ENABLE_FLITE)
366   AtisSpeaker _atisSpeaker;
367   #endif
368   FrequencyFormatter _useFrequencyFormatter;
369   FrequencyFormatter _stbyFrequencyFormatter;
370   const SignalQualityComputerRef _signalQualityComputer;
371
372   double _stationTTL;
373   double _frequency;
374   flightgear::CommStationRef _commStationForFrequency;
375   #if defined(ENABLE_FLITE)
376   SGSharedPtr<SGSampleGroup> _sampleGroup;
377   #endif
378
379   PropertyObject<bool> _serviceable;
380   PropertyObject<bool> _power_btn;
381   PropertyObject<bool> _power_good;
382   PropertyObject<double> _volume_norm;
383   PropertyObject<string> _atis;
384   PropertyObject<bool> _addNoise;
385   PropertyObject<double> _cutoffSignalQuality;
386 };
387
388 CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node)
389     : OutputProperties(
390         fgGetNode("/instrumentation", true)->getNode(node->getStringValue("name", "comm"), node->getIntValue("number", 0), true)),
391         _num(node->getIntValue("number", 0)),
392         _metarBridge(new MetarBridge()),
393         _useFrequencyFormatter(_rootNode->getNode("frequencies/selected-mhz", true),
394             _rootNode->getNode("frequencies/selected-mhz-fmt", true), 0.025, 118.0, 137.0),
395
396         _stbyFrequencyFormatter(_rootNode->getNode("frequencies/standby-mhz", true),
397             _rootNode->getNode("frequencies/standby-mhz-fmt", true), 0.025, 118.0, 137.0),
398
399         _signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer()),
400
401         _stationTTL(0.0),
402         _frequency(-1.0),
403         _commStationForFrequency(NULL),
404
405         _serviceable(_rootNode->getNode("serviceable", true)),
406         _power_btn(_rootNode->getNode("power-btn", true)),
407         _power_good(_rootNode->getNode("power-good", true)),
408         _volume_norm(_rootNode->getNode("volume", true)),
409         _atis(_rootNode->getNode("atis", true)),
410         _addNoise(_rootNode->getNode("add-noise", true)),
411         _cutoffSignalQuality(_rootNode->getNode("cutoff-signal-quality", true))
412 {
413 }
414
415 CommRadioImpl::~CommRadioImpl()
416 {
417 }
418
419 void CommRadioImpl::bind()
420 {
421   _metarBridge->setAtisNode(_atis.node());
422 #if defined(ENABLE_FLITE)
423   _atis.node()->addChangeListener(&_atisSpeaker);
424 #endif
425   // link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
426   // see FGDATA/Environment/environment.xml
427   _metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", _num + 3, true));
428   _metarBridge->bind();
429 }
430
431 void CommRadioImpl::unbind()
432 {
433 #if defined(ENABLE_FLITE)
434   _atis.node()->removeChangeListener(&_atisSpeaker);
435   if (_sampleGroup.valid()) {
436     globals->get_soundmgr()->remove(getSampleGroupRefname());
437   }
438 #endif
439   _metarBridge->unbind();
440 }
441
442 void CommRadioImpl::init()
443 {
444   // initialize power_btn to true if unset
445   string s = _power_btn.node()->getStringValue();
446   if (s.empty()) _power_btn = true;
447
448   // initialize squelch to a sane value if unset
449   s = _cutoffSignalQuality.node()->getStringValue();
450   if (s.empty()) _cutoffSignalQuality = 0.4;
451
452   // initialize add-noize to true if unset
453   s = _addNoise.node()->getStringValue();
454   if (s.empty()) _addNoise = true;
455 }
456
457 void CommRadioImpl::update(double dt)
458 {
459   if (dt < SGLimitsd::min()) return;
460   _stationTTL -= dt;
461
462   // Ensure all output properties get written on exit of this method
463   OnExit onExit(this);
464
465   SGGeod position;
466   try {
467     position = globals->get_aircraft_position();
468   }
469   catch (std::exception &) {
470     return;
471   }
472
473 #if defined(ENABLE_FLITE)
474   {
475     static const char * atisSampleRefName = "atis";
476     static const char * noiseSampleRefName = "noise";
477
478     if (_atisSpeaker.hasSpokenAtis()) {
479       // the speaker has created a new atis sample
480       if (!_sampleGroup.valid()) {
481         // create a sample group for our instrument on the fly
482         _sampleGroup = globals->get_soundmgr()->find(getSampleGroupRefname(), true);
483         _sampleGroup->tie_to_listener();
484         if (_addNoise) {
485           SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
486           _sampleGroup->add(noise, noiseSampleRefName);
487           _sampleGroup->play_looped(noiseSampleRefName);
488         }
489
490       }
491       // remove previous atis sample
492       _sampleGroup->remove(atisSampleRefName);
493       // add and play the new atis sample
494       SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
495       _sampleGroup->add(sample, atisSampleRefName);
496       _sampleGroup->play_looped(atisSampleRefName);
497     }
498
499     if (_sampleGroup.valid()) {
500       if (_addNoise) {
501         // if noise is configured and there is a noise sample
502         // scale noise and signal volume by signalQuality.
503         SGSoundSample * s = _sampleGroup->find(noiseSampleRefName);
504         if ( NULL != s) {
505           s->set_volume(1.0 - _signalQuality_norm);
506           s = _sampleGroup->find(atisSampleRefName);
507           s->set_volume(_signalQuality_norm);
508         }
509       }
510       // master volume for radio, mute on bad signal quality
511       _sampleGroup->set_volume(_signalQuality_norm >= _cutoffSignalQuality ? _volume_norm : 0.0);
512     }
513   }
514 #endif
515
516   if (false == (_power_btn)) {
517     _metarBridge->clearMetar();
518     _atis = "";
519     _stationTTL = 0.0;
520     return;
521   }
522
523   if (_frequency != _useFrequencyFormatter.getFrequency()) {
524     _frequency = _useFrequencyFormatter.getFrequency();
525     _stationTTL = 0.0;
526   }
527
528   if (_stationTTL <= 0.0) {
529     _stationTTL = 30.0;
530
531     // Note:  122.375 must be rounded DOWN to 122370
532     // in order to be consistent with apt.dat et cetera.
533     int freqKhz = 10 * static_cast<int>(_frequency * 100 + 0.25);
534     _commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
535
536   }
537
538   if (false == _commStationForFrequency.valid()) return;
539
540   _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
541
542   SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
543
544   _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
545
546   _signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m * SG_METER_TO_NM);
547   _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
548   _stationName = _commStationForFrequency->ident();
549   _airportId = _commStationForFrequency->airport()->getId();
550
551 #if defined(ENABLE_FLITE)
552   _atisSpeaker.setStationId(_airportId);
553 #endif
554   switch (_commStationForFrequency->type()) {
555     case FGPositioned::FREQ_ATIS:
556       case FGPositioned::FREQ_AWOS: {
557       if (_signalQuality_norm > 0.01) {
558         _metarBridge->requestMetarForId(_airportId);
559       } else {
560         _metarBridge->clearMetar();
561         _atis = "";
562       }
563     }
564       break;
565
566     default:
567       _metarBridge->clearMetar();
568       _atis = "";
569       break;
570   }
571 }
572
573 SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
574 {
575   return new CommRadioImpl(rootNode);
576 }
577
578 } // namespace Instrumentation
579