13#include <simgear/sg_inlines.h>
14#include <simgear/props/propertyObject.hxx>
15#include <simgear/misc/strutils.hxx>
25#include <simgear/sound/sample_group.hxx>
32using simgear::PropertyObject;
44 return _spokenAtis.empty() ==
false;
49 return _spokenAtis.pop();
54 _stationId = stationId;
60 SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
78 using namespace simgear::strutils;
80 if (!
fgGetBool(
"/sim/sound/working",
false))
83 string newText = node->getStringValue();
84 if (_synthesizeRequest.text == newText)
return;
86 _synthesizeRequest.text = newText;
88 string voice =
"cmu_us_arctic_slt";
90 if (!_stationId.empty()) {
95 unsigned char hash = 0;
96 string::iterator
i = _stationId.end() - 1;
99 if(
i != _stationId.begin() ) {
104 _synthesizeRequest.speed = (hash % 16) / 16.0 *
fgGetDouble(
"/sim/atis/speed", 1);
105 _synthesizeRequest.pitch = (hash % 16) / 16.0 *
fgGetDouble(
"/sim/atis/pitch", 1);
107 if( starts_with( _stationId,
"K" ) || starts_with( _stationId,
"C" ) ||
108 starts_with( _stationId,
"P" ) ) {
110 }
else if ( starts_with( _stationId,
"EG" ) ) {
124 SG_LOG(SG_INSTR, SG_DEBUG,
"node->getPath()=" << node->getPath() <<
" AtisSpeaker voice is " << voice );
133 _spokenAtis.push(sample);
143 : _altitudeAgl_ft(
fgGetNode(
"/position/altitude-agl-ft", true))
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);
161 PropertyObject<double> _altitudeAgl_ft;
175 : _onExitHandler(onExitHandler)
180 _onExitHandler->onExit();
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));
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;
232 virtual void onExit()
248class MetarBridge:
public SGReferenced,
public SGPropertyChangeListener {
257 _metarPropertiesNode = n;
268 std::string _requestedId;
269 SGPropertyNode_ptr _realWxEnabledNode;
270 SGPropertyNode_ptr _metarPropertiesNode;
271 SGPropertyNode * _atisNode =
nullptr;
279 _realWxEnabledNode =
fgGetNode(
"/environment/realwx/enabled",
true);
280 _metarPropertiesNode->getNode(
"valid",
true)->addChangeListener(
this);
285 _metarPropertiesNode->getNode(
"valid",
true)->removeChangeListener(
this);
290 std::string uppercaseId = simgear::strutils::uppercase(
id);
291 if (_requestedId == uppercaseId)
return;
292 _requestedId = uppercaseId;
294 if (_realWxEnabledNode->getBoolValue()) {
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);
301 if ( NULL != _atisNode && !_requestedId.empty()) {
303 _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
317 if ( NULL == node || !node->getBoolValue() || !_realWxEnabledNode->getBoolValue())
return;
319 std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode(
"station-id",
true)->getStringValue());
322 if (responseId != _requestedId)
return;
324 if ( NULL != _atisNode) {
326 _atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
334 public SGPropertyChangeListener {
337 const char * channel,
341 const char * cnum ) :
342 _channel( root, channel ),
343 _frequency( root, frq ),
344 _channelSpacing( root, width ),
345 _formattedChannel( root, fmt ),
346 _channelNum( root, cnum )
350 _frequency.node(
true);
351 _channelSpacing.node(
true);
352 _channelNum.node(
true);
353 _formattedChannel.node(
true);
355 _channel.node()->addChangeListener(
this,
true );
356 _channelNum.node()->addChangeListener(
this,
true );
361 _channel.node()->removeChangeListener(
this );
362 _channelNum.node()->removeChangeListener(
this );
369 void valueChanged (SGPropertyNode * prop) {
370 if( prop == _channel.node() )
371 setFrequency(prop->getDoubleValue());
372 else if( prop == _channelNum.node() )
373 setChannel(prop->getIntValue());
376 void setChannel(
int channel ) {
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;
383 void setFrequency(
double channel ) {
385 std::ostringstream buf;
389 << std::setprecision(3)
391 _formattedChannel = buf.str();
394 unsigned c =
static_cast<int>(SGMiscd::round(SGMiscd::clip( channel, 118.0, 136.99 ) * 1000));
396 if ( (c % 25) == 0 ) {
398 _channelSpacing = 25.0;
399 _frequency = c / 1000.0;
401 int channelNum = (c-118000)/25*4;
402 if( channelNum != _channelNum ) _channelNum = channelNum;
404 if( _frequency != channel ) {
405 const double channelValue = c / 1000.0;
406 _channel = channelValue;
409 _channelSpacing = 8.33;
412 unsigned base25 = (c/25) * 25;
415 unsigned subChannel = SGMisc<unsigned>::clip((c - base25)/5-1, 0, 2 );
417 _frequency = (base25 + 8.33 * subChannel)/1000.0;
419 int channelNum = (base25-118000)/25*4 + subChannel+1;
420 if( channelNum != _channelNum ) _channelNum = channelNum;
423 double sanitizedChannel = (base25 + 5*(subChannel+1))/1000.0;
424 if( sanitizedChannel != channel ) {
425 _channel = sanitizedChannel;
430 double getFrequency()
const {
435 PropertyObject<double> _channel;
436 PropertyObject<double> _frequency;
437 PropertyObject<double> _channelSpacing;
438 PropertyObject<string> _formattedChannel;
439 PropertyObject<int> _channelNum;
454 void bind()
override;
455 void init()
override;
457 void update(
double dt)
override;
460 bool _useEightPointThree =
false;
463 SGSharedPtr<FrequencyFormatterBase> _useFrequencyFormatter;
464 SGSharedPtr<FrequencyFormatterBase> _stbyFrequencyFormatter;
467 double _stationTTL = 0.0;
468 double _frequency = -1.0;
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;
480 SGPropertyNode_ptr _atis_enabled_node;
481 bool _atis_enabled_prev;
482 SGSharedPtr<SGSoundSample> _atis_sample;
484 std::string _soundPrefix;
488 SGSampleGroup* _sampleGroup =
nullptr;
494 _atis_enabled_prev(false)
500 _soundPrefix =
name() +
"_" + std::to_string(
number()) +
"_";
501 _useEightPointThree = node->getBoolValue(
"eight-point-three",
false );
502 _fullDuplexConfig = node->getBoolValue(
"full-duplex", _fullDuplexConfig);
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));
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));
524 _metarBridge->setAtisNode(_atis.node());
525 _atis.node()->addChangeListener(&_atisSpeaker);
529 _metarBridge->setMetarPropertiesRoot(
fgGetNode(
"/environment",
true)->getNode(
"metar",
number() + 3,
true));
530 _metarBridge->bind();
532 if (_useEightPointThree) {
537 "selected-channel-width-khz",
538 "selected-real-frequency-mhz",
545 "standby-channel-width-khz",
546 "standby-real-frequency-mhz",
551 _rootNode->getNode(
"frequencies/selected-mhz",
true),
552 _rootNode->getNode(
"frequencies/selected-mhz-fmt",
true),
553 0.025, 118.0, 137.0);
556 _rootNode->getNode(
"frequencies/standby-mhz",
true),
557 _rootNode->getNode(
"frequencies/standby-mhz-fmt",
true),
558 0.025, 118.0, 137.0);
566 _atis.node()->removeChangeListener(&_atisSpeaker);
570 _metarBridge->unbind();
580 s = _cutoffSignalQuality.node()->getStringValue();
581 if (s.empty()) _cutoffSignalQuality = 0.4;
584 s = _addNoise.node()->getStringValue();
585 if (s.empty()) _addNoise =
true;
587 auto soundManager =
globals->get_subsystem<SGSoundMgr>();
589 _sampleGroup = soundManager->find(
"atc",
true);
595 if (dt < SGLimitsd::min())
return;
603 position =
globals->get_aircraft_position();
605 catch (std::exception &) {
610 _metarBridge->clearMetar();
614 _receivingFlag =
false;
618 if (_frequency != _useFrequencyFormatter->getFrequency()) {
619 _frequency = _useFrequencyFormatter->getFrequency();
623 if (_stationTTL <= 0.0) {
626 int freqKhz =
static_cast<int>(_frequency * 1000 + 0.5);
628 if (!_useEightPointThree && freqKhz % 25) {
635 if (!_commStationForFrequency.valid()) {
637 _receivingFlag =
false;
641 _slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
645 _heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
648 _stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
650 _airportId = _commStationForFrequency->airport()->getId();
654 switch (_commStationForFrequency->type()) {
659 _receivingFlag = _fullDuplex || !_pushToTalk;
661 _metarBridge->clearMetar();
663 _receivingFlag =
false;
669 _metarBridge->clearMetar();
671 _receivingFlag =
false;
678void CommRadioImpl::updateAudio()
683 const string noiseRef = _soundPrefix +
"_noise";
684 const string atisRef = _soundPrefix +
"_atis";
686 SGSoundSample* noiseSample = _sampleGroup->find(noiseRef);
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);
696 bool atis_enabled = _atis_enabled_node->getBoolValue();
698 if (atis_enabled && !_atis_enabled_prev) atis_delta = 1;
699 if (!atis_enabled && _atis_enabled_prev) atis_delta = -1;
704 _sampleGroup->remove(atisRef);
705 if (!atis_delta && atis_enabled) atis_delta = 1;
707 if (atis_delta == 1) {
711 SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
712 if (sample) _atis_sample = sample;
713 else sample = _atis_sample;
715 SG_LOG(SG_INSTR, SG_DEBUG,
"starting looped play of atis sample.");
716 _sampleGroup->add(sample, atisRef);
717 _sampleGroup->play_looped(atisRef);
720 SG_LOG(SG_INSTR, SG_DEBUG,
"no atis sample available");
723 if (atis_delta == -1) {
725 _sampleGroup->remove(atisRef);
727 _atis_enabled_prev = atis_enabled;
730 const auto targetVolume = (_pushToTalk && !_fullDuplex) ? 0.0 : _volume_norm;
732 double atisVolume = doSquelch ? 0.0 : targetVolume;
735 if (_cutoffSignalQuality < 0.01) {
738 noiseVol = targetVolume;
742 noiseSample->set_volume(doSquelch ? 0.0: noiseVol);
745 SGSoundSample* s = _sampleGroup->find(atisRef);
747 s->set_volume(atisVolume);
751void CommRadioImpl::stopAudio()
754 _sampleGroup->remove(_soundPrefix +
"_noise");
755 _sampleGroup->remove(_soundPrefix +
"_atis");
767SGSubsystemMgr::InstancedRegistrant<CommRadio> registrantCommRadio(
769 {{
"instrumentation", SGSubsystemMgr::Dependency::HARD}});
void initServicePowerProperties(SGPropertyNode *node)
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)
void setStationId(const string &stationId)
SGSharedPtr< SGSoundSample > getSpokenAtis()
CommRadioImpl(SGPropertyNode_ptr node)
void update(double dt) override
static SGSubsystem * createInstance(SGPropertyNode_ptr rootNode)
OnExit(OnExitHandler *onExitHandler)
double _heightAboveStation_ft
double _trueBearingTo_deg
void bind(SGPropertyNode *rn)
SGPropertyNode_ptr _rootNode
double _signalQuality_norm
virtual ~OutputProperties()
double _trueBearingFrom_deg
virtual ~SignalQualityComputer()
double computeSignalQuality(double distance_nm) const
~SimpleDistanceSquareSignalQualityComputer()
SimpleDistanceSquareSignalQualityComputer()
static CommStationRef findByFreq(int freqKhz, const SGGeod &pos, FGPositioned::Filter *filt=NULL)
bool fgHasNode(const char *path)
Test whether a given node exists.
SGSharedPtr< SignalQualityComputer > SignalQualityComputerRef
SGSharedPtr< MetarBridge > MetarBridgeRef
SGSharedPtr< CommStation > CommStationRef
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
double fgGetDouble(const char *name, double defaultValue)
Get a double value for a property.
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
SoundSampleReadyListener * listener