- Add section tag to support inclusion of ATIS fragments. - Add visibility, QNH and cloud tokens to support new ATIS formats. - Add support for starts-with, ends-with and contains comparisons in conditionals, including negated versions. - Strip and convert case in comparisons. - Speak VRB wind direction as "variable". - Speak zeroes in fractional part of QNH inHg. - Force US voice in US, Canada and Pacific; UK voice in UK.
727 lines
21 KiB
C++
727 lines
21 KiB
C++
// commradio.cxx -- class to manage a nav radio instance
|
|
//
|
|
// Written by Torsten Dreyer, February 2014
|
|
//
|
|
// Copyright (C) 2000 - 2011 Curtis L. Olson - http://www.flightgear.org/~curt
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License as
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
//
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include "commradio.hxx"
|
|
|
|
#include <assert.h>
|
|
#include <boost/foreach.hpp>
|
|
|
|
#include <simgear/sg_inlines.h>
|
|
#include <simgear/props/propertyObject.hxx>
|
|
#include <simgear/misc/strutils.hxx>
|
|
|
|
#include <ATC/CommStation.hxx>
|
|
#include <ATC/MetarPropertiesATISInformationProvider.hxx>
|
|
#include <ATC/CurrentWeatherATISInformationProvider.hxx>
|
|
#include <Airports/airport.hxx>
|
|
#include <Main/fg_props.hxx>
|
|
#include <Navaids/navlist.hxx>
|
|
|
|
#if defined(ENABLE_FLITE)
|
|
#include <Sound/soundmanager.hxx>
|
|
#include <simgear/sound/sample_group.hxx>
|
|
#include <Sound/VoiceSynthesizer.hxx>
|
|
#endif
|
|
|
|
#include "frequencyformatter.hxx"
|
|
|
|
namespace Instrumentation {
|
|
|
|
using simgear::PropertyObject;
|
|
using std::string;
|
|
|
|
#if defined(ENABLE_FLITE)
|
|
class AtisSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
|
|
public:
|
|
AtisSpeaker();
|
|
virtual ~AtisSpeaker();
|
|
virtual void valueChanged(SGPropertyNode * node);
|
|
virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
|
|
|
|
bool hasSpokenAtis()
|
|
{
|
|
return _spokenAtis.empty() == false;
|
|
}
|
|
|
|
SGSharedPtr<SGSoundSample> getSpokenAtis()
|
|
{
|
|
return _spokenAtis.pop();
|
|
}
|
|
|
|
void setStationId(const string & stationId)
|
|
{
|
|
_stationId = stationId;
|
|
}
|
|
|
|
|
|
private:
|
|
SynthesizeRequest _synthesizeRequest;
|
|
SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenAtis;
|
|
string _stationId;
|
|
};
|
|
|
|
AtisSpeaker::AtisSpeaker()
|
|
{
|
|
_synthesizeRequest.listener = this;
|
|
}
|
|
|
|
AtisSpeaker::~AtisSpeaker()
|
|
{
|
|
|
|
}
|
|
void AtisSpeaker::valueChanged(SGPropertyNode * node)
|
|
{
|
|
using namespace simgear::strutils;
|
|
|
|
if (!fgGetBool("/sim/sound/working", false))
|
|
return;
|
|
|
|
string newText = node->getStringValue();
|
|
if (_synthesizeRequest.text == newText) return;
|
|
|
|
_synthesizeRequest.text = newText;
|
|
|
|
string voice = "cmu_us_arctic_slt";
|
|
|
|
if (!_stationId.empty()) {
|
|
// lets play a bit with the voice so not every airports atis sounds alike
|
|
// but every atis of an airport has the same voice
|
|
|
|
// create a simple hash from the last two letters of the airport's id
|
|
unsigned char hash = 0;
|
|
string::iterator i = _stationId.end() - 1;
|
|
hash += *i;
|
|
|
|
if( i != _stationId.begin() ) {
|
|
--i;
|
|
hash += *i;
|
|
}
|
|
|
|
_synthesizeRequest.speed = (hash % 16) / 16.0;
|
|
_synthesizeRequest.pitch = (hash % 16) / 16.0;
|
|
|
|
if( starts_with( _stationId, "K" ) || starts_with( _stationId, "C" ) ||
|
|
starts_with( _stationId, "P" ) ) {
|
|
voice = FLITEVoiceSynthesizer::getVoicePath("cmu_us_arctic_slt");
|
|
} else if ( starts_with( _stationId, "EG" ) ) {
|
|
voice = FLITEVoiceSynthesizer::getVoicePath("cstr_uk_female");
|
|
} else {
|
|
// Pick a random voice from the available voices
|
|
voice = FLITEVoiceSynthesizer::getVoicePath(
|
|
static_cast<FLITEVoiceSynthesizer::voice_t>(hash % FLITEVoiceSynthesizer::VOICE_UNKNOWN) );
|
|
}
|
|
}
|
|
|
|
FGSoundManager * smgr = globals->get_subsystem<FGSoundManager>();
|
|
assert(smgr != NULL);
|
|
|
|
SG_LOG(SG_INSTR,SG_INFO,"AtisSpeaker voice is " << voice );
|
|
FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
|
|
|
|
synthesizer->synthesize(_synthesizeRequest);
|
|
}
|
|
|
|
void AtisSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
|
|
{
|
|
// we are now in the synthesizers worker thread!
|
|
_spokenAtis.push(sample);
|
|
}
|
|
#endif
|
|
|
|
SignalQualityComputer::~SignalQualityComputer()
|
|
{
|
|
}
|
|
|
|
class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
|
|
public:
|
|
SimpleDistanceSquareSignalQualityComputer()
|
|
: _altitudeAgl_ft(fgGetNode("/position/altitude-agl-ft", true))
|
|
{
|
|
}
|
|
|
|
~SimpleDistanceSquareSignalQualityComputer()
|
|
{
|
|
}
|
|
|
|
double computeSignalQuality(double distance_nm) const
|
|
{
|
|
// Very simple line of sight propagation model. It's cheap but it does the trick for now.
|
|
// assume transmitter and receiver antennas are at some elevation above ground
|
|
// so we have at least a range of 5NM. Add the approx. distance to the horizon.
|
|
double range_nm = 5.0 + 1.23 * ::sqrt(SGMiscd::max(.0, _altitudeAgl_ft));
|
|
return distance_nm < range_nm ? 1.0 : (range_nm * range_nm / distance_nm / distance_nm);
|
|
}
|
|
|
|
private:
|
|
PropertyObject<double> _altitudeAgl_ft;
|
|
};
|
|
|
|
class OnExitHandler {
|
|
public:
|
|
virtual void onExit() = 0;
|
|
virtual ~OnExitHandler()
|
|
{
|
|
}
|
|
};
|
|
|
|
class OnExit {
|
|
public:
|
|
OnExit(OnExitHandler * onExitHandler)
|
|
: _onExitHandler(onExitHandler)
|
|
{
|
|
}
|
|
~OnExit()
|
|
{
|
|
_onExitHandler->onExit();
|
|
}
|
|
private:
|
|
OnExitHandler * _onExitHandler;
|
|
};
|
|
|
|
class OutputProperties: public OnExitHandler {
|
|
public:
|
|
OutputProperties(SGPropertyNode_ptr rootNode)
|
|
: _rootNode(rootNode), _signalQuality_norm(0.0), _slantDistance_m(0.0), _trueBearingTo_deg(0.0), _trueBearingFrom_deg(0.0), _trackDistance_m(
|
|
0.0), _heightAboveStation_ft(0.0),
|
|
|
|
_PO_stationType(rootNode->getNode("station-type", true)), _PO_stationName(rootNode->getNode("station-name", true)), _PO_airportId(
|
|
rootNode->getNode("airport-id", true)), _PO_signalQuality_norm(rootNode->getNode("signal-quality-norm", true)), _PO_slantDistance_m(
|
|
rootNode->getNode("slant-distance-m", true)), _PO_trueBearingTo_deg(rootNode->getNode("true-bearing-to-deg", true)), _PO_trueBearingFrom_deg(
|
|
rootNode->getNode("true-bearing-from-deg", true)), _PO_trackDistance_m(rootNode->getNode("track-distance-m", true)), _PO_heightAboveStation_ft(
|
|
rootNode->getNode("height-above-station-ft", true))
|
|
{
|
|
}
|
|
virtual ~OutputProperties()
|
|
{
|
|
}
|
|
|
|
protected:
|
|
SGPropertyNode_ptr _rootNode;
|
|
|
|
std::string _stationType;
|
|
std::string _stationName;
|
|
std::string _airportId;
|
|
double _signalQuality_norm;
|
|
double _slantDistance_m;
|
|
double _trueBearingTo_deg;
|
|
double _trueBearingFrom_deg;
|
|
double _trackDistance_m;
|
|
double _heightAboveStation_ft;
|
|
|
|
private:
|
|
PropertyObject<string> _PO_stationType;
|
|
PropertyObject<string> _PO_stationName;
|
|
PropertyObject<string> _PO_airportId;
|
|
PropertyObject<double> _PO_signalQuality_norm;
|
|
PropertyObject<double> _PO_slantDistance_m;
|
|
PropertyObject<double> _PO_trueBearingTo_deg;
|
|
PropertyObject<double> _PO_trueBearingFrom_deg;
|
|
PropertyObject<double> _PO_trackDistance_m;
|
|
PropertyObject<double> _PO_heightAboveStation_ft;
|
|
|
|
virtual void onExit()
|
|
{
|
|
_PO_stationType = _stationType;
|
|
_PO_stationName = _stationName;
|
|
_PO_airportId = _airportId;
|
|
_PO_signalQuality_norm = _signalQuality_norm;
|
|
_PO_slantDistance_m = _slantDistance_m;
|
|
_PO_trueBearingTo_deg = _trueBearingTo_deg;
|
|
_PO_trueBearingFrom_deg = _trueBearingFrom_deg;
|
|
_PO_trackDistance_m = _trackDistance_m;
|
|
_PO_heightAboveStation_ft = _heightAboveStation_ft;
|
|
}
|
|
};
|
|
|
|
/* ------------- The CommRadio implementation ---------------------- */
|
|
|
|
class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
|
|
public:
|
|
MetarBridge();
|
|
~MetarBridge();
|
|
|
|
void bind();
|
|
void unbind();
|
|
void requestMetarForId(std::string & id);
|
|
void clearMetar();
|
|
void setMetarPropertiesRoot(SGPropertyNode_ptr n)
|
|
{
|
|
_metarPropertiesNode = n;
|
|
}
|
|
void setAtisNode(SGPropertyNode * n)
|
|
{
|
|
_atisNode = n;
|
|
}
|
|
|
|
protected:
|
|
virtual void valueChanged(SGPropertyNode *);
|
|
|
|
private:
|
|
std::string _requestedId;
|
|
SGPropertyNode_ptr _realWxEnabledNode;
|
|
SGPropertyNode_ptr _metarPropertiesNode;
|
|
SGPropertyNode * _atisNode;
|
|
ATISEncoder _atisEncoder;
|
|
};
|
|
typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
|
|
|
|
MetarBridge::MetarBridge()
|
|
: _atisNode(0)
|
|
{
|
|
}
|
|
|
|
MetarBridge::~MetarBridge()
|
|
{
|
|
}
|
|
|
|
void MetarBridge::bind()
|
|
{
|
|
_realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
|
|
_metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
|
|
}
|
|
|
|
void MetarBridge::unbind()
|
|
{
|
|
_metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
|
|
}
|
|
|
|
void MetarBridge::requestMetarForId(std::string & id)
|
|
{
|
|
std::string uppercaseId = simgear::strutils::uppercase(id);
|
|
if (_requestedId == uppercaseId) return;
|
|
_requestedId = uppercaseId;
|
|
|
|
if (_realWxEnabledNode->getBoolValue()) {
|
|
// trigger a METAR request for the associated metarproperties
|
|
_metarPropertiesNode->getNode("station-id", true)->setStringValue(uppercaseId);
|
|
_metarPropertiesNode->getNode("valid", true)->setBoolValue(false);
|
|
_metarPropertiesNode->getNode("time-to-live", true)->setDoubleValue(0.0);
|
|
} else {
|
|
// use the present weather to generate the ATIS.
|
|
if ( NULL != _atisNode && false == _requestedId.empty()) {
|
|
CurrentWeatherATISInformationProvider provider(_requestedId);
|
|
_atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MetarBridge::clearMetar()
|
|
{
|
|
string empty;
|
|
requestMetarForId(empty);
|
|
}
|
|
|
|
void MetarBridge::valueChanged(SGPropertyNode * node)
|
|
{
|
|
// check for raising edge of valid flag
|
|
if ( NULL == node || false == node->getBoolValue() || false == _realWxEnabledNode->getBoolValue()) return;
|
|
|
|
std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
|
|
|
|
// unrequested metar!?
|
|
if (responseId != _requestedId) return;
|
|
|
|
if ( NULL != _atisNode) {
|
|
MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
|
|
_atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
|
|
}
|
|
}
|
|
|
|
/* ------------- 8.3kHz Channel implementation ---------------------- */
|
|
|
|
class EightPointThreeFrequencyFormatter :
|
|
public FrequencyFormatterBase,
|
|
public SGPropertyChangeListener {
|
|
public:
|
|
EightPointThreeFrequencyFormatter( SGPropertyNode_ptr root,
|
|
const char * channel,
|
|
const char * fmt,
|
|
const char * width,
|
|
const char * frq,
|
|
const char * cnum ) :
|
|
_channel( root, channel ),
|
|
_frequency( root, frq ),
|
|
_channelSpacing( root, width ),
|
|
_formattedChannel( root, fmt ),
|
|
_channelNum( root, cnum )
|
|
{
|
|
// ensure properties exist.
|
|
_channel.node(true);
|
|
_frequency.node(true);
|
|
_channelSpacing.node(true);
|
|
_channelNum.node(true);
|
|
_formattedChannel.node(true);
|
|
|
|
_channel.node()->addChangeListener( this, true );
|
|
_channelNum.node()->addChangeListener( this, true );
|
|
}
|
|
|
|
virtual ~EightPointThreeFrequencyFormatter()
|
|
{
|
|
_channel.node()->removeChangeListener( this );
|
|
_channelNum.node()->removeChangeListener( this );
|
|
}
|
|
|
|
private:
|
|
EightPointThreeFrequencyFormatter( const EightPointThreeFrequencyFormatter & );
|
|
EightPointThreeFrequencyFormatter & operator = ( const EightPointThreeFrequencyFormatter & );
|
|
|
|
void valueChanged (SGPropertyNode * prop) {
|
|
if( prop == _channel.node() )
|
|
setFrequency(prop->getDoubleValue());
|
|
else if( prop == _channelNum.node() )
|
|
setChannel(prop->getIntValue());
|
|
}
|
|
|
|
void setChannel( int channel ) {
|
|
channel %= 3040;
|
|
if( channel < 0 ) channel += 3040;
|
|
double f = 118.000 + 0.025*(channel/4) + 0.005*(channel%4);
|
|
if( f != _channel ) _channel = f;
|
|
}
|
|
|
|
void setFrequency( double channel ) {
|
|
// format as fixed decimal "nnn.nnn"
|
|
std::ostringstream buf;
|
|
buf << std::fixed
|
|
<< std::setw(6)
|
|
<< std::setfill('0')
|
|
<< std::setprecision(3)
|
|
<< _channel;
|
|
_formattedChannel = buf.str();
|
|
|
|
// sanitize range and round to nearest kHz.
|
|
unsigned c = static_cast<int>(SGMiscd::round(SGMiscd::clip( channel, 118.0, 136.99 ) * 1000));
|
|
|
|
if ( (c % 25) == 0 ) {
|
|
// legacy 25kHz channels continue to be just that.
|
|
_channelSpacing = 25.0;
|
|
_frequency = c / 1000.0;
|
|
|
|
int channelNum = (c-118000)/25*4;
|
|
if( channelNum != _channelNum ) _channelNum = channelNum;
|
|
|
|
if( _frequency != channel ) {
|
|
_channel = _frequency; //triggers recursion
|
|
}
|
|
} else {
|
|
_channelSpacing = 8.33;
|
|
|
|
// 25kHz base frequency: xxx.000, xxx.025, xxx.050, xxx.075
|
|
unsigned base25 = (c/25) * 25;
|
|
|
|
// add n*8.33 to the 25kHz frequency
|
|
unsigned subChannel = SGMisc<unsigned>::clip((c - base25)/5-1, 0, 2 );
|
|
|
|
_frequency = (base25 + 8.33 * subChannel)/1000.0;
|
|
|
|
int channelNum = (base25-118000)/25*4 + subChannel+1;
|
|
if( channelNum != _channelNum ) _channelNum = channelNum;
|
|
|
|
// set to correct channel on bogous input
|
|
double sanitizedChannel = (base25 + 5*(subChannel+1))/1000.0;
|
|
if( sanitizedChannel != channel ) {
|
|
_channel = sanitizedChannel; // triggers recursion
|
|
}
|
|
}
|
|
}
|
|
|
|
double getFrequency() const {
|
|
return _channel;
|
|
}
|
|
|
|
|
|
PropertyObject<double> _channel;
|
|
PropertyObject<double> _frequency;
|
|
PropertyObject<double> _channelSpacing;
|
|
PropertyObject<string> _formattedChannel;
|
|
PropertyObject<int> _channelNum;
|
|
|
|
};
|
|
|
|
|
|
/* ------------- The CommRadio implementation ---------------------- */
|
|
|
|
class CommRadioImpl: public CommRadio, OutputProperties {
|
|
|
|
public:
|
|
CommRadioImpl(SGPropertyNode_ptr node);
|
|
virtual ~CommRadioImpl();
|
|
|
|
virtual void update(double dt);
|
|
virtual void init();
|
|
void bind();
|
|
void unbind();
|
|
|
|
private:
|
|
string getSampleGroupRefname() const
|
|
{
|
|
return _rootNode->getPath();
|
|
}
|
|
|
|
int _num;
|
|
MetarBridgeRef _metarBridge;
|
|
#if defined(ENABLE_FLITE)
|
|
AtisSpeaker _atisSpeaker;
|
|
#endif
|
|
SGSharedPtr<FrequencyFormatterBase> _useFrequencyFormatter;
|
|
SGSharedPtr<FrequencyFormatterBase> _stbyFrequencyFormatter;
|
|
const SignalQualityComputerRef _signalQualityComputer;
|
|
|
|
double _stationTTL;
|
|
double _frequency;
|
|
flightgear::CommStationRef _commStationForFrequency;
|
|
#if defined(ENABLE_FLITE)
|
|
SGSharedPtr<SGSampleGroup> _sampleGroup;
|
|
#endif
|
|
|
|
PropertyObject<bool> _serviceable;
|
|
PropertyObject<bool> _power_btn;
|
|
PropertyObject<bool> _power_good;
|
|
PropertyObject<double> _volume_norm;
|
|
PropertyObject<string> _atis;
|
|
PropertyObject<bool> _addNoise;
|
|
PropertyObject<double> _cutoffSignalQuality;
|
|
|
|
};
|
|
|
|
CommRadioImpl::CommRadioImpl(SGPropertyNode_ptr node)
|
|
: OutputProperties(
|
|
fgGetNode("/instrumentation", true)->getNode(node->getStringValue("name", "comm"), node->getIntValue("number", 0), true)),
|
|
_num(node->getIntValue("number", 0)),
|
|
_metarBridge(new MetarBridge()),
|
|
_signalQualityComputer(new SimpleDistanceSquareSignalQualityComputer()),
|
|
|
|
_stationTTL(0.0),
|
|
_frequency(-1.0),
|
|
_commStationForFrequency(NULL),
|
|
|
|
_serviceable(_rootNode->getNode("serviceable", true)),
|
|
_power_btn(_rootNode->getNode("power-btn", true)),
|
|
_power_good(_rootNode->getNode("power-good", true)),
|
|
_volume_norm(_rootNode->getNode("volume", true)),
|
|
_atis(_rootNode->getNode("atis", true)),
|
|
_addNoise(_rootNode->getNode("add-noise", true)),
|
|
_cutoffSignalQuality(_rootNode->getNode("cutoff-signal-quality", true))
|
|
{
|
|
if( node->getBoolValue("eight-point-three", false ) ) {
|
|
_useFrequencyFormatter = new EightPointThreeFrequencyFormatter(
|
|
_rootNode->getNode("frequencies", true),
|
|
"selected-mhz",
|
|
"selected-mhz-fmt",
|
|
"selected-channel-width-khz",
|
|
"selected-real-frequency-mhz",
|
|
"selected-channel"
|
|
);
|
|
_stbyFrequencyFormatter = new EightPointThreeFrequencyFormatter(
|
|
_rootNode->getNode("frequencies", true),
|
|
"standby-mhz",
|
|
"standby-mhz-fmt",
|
|
"standby-channel-width-khz",
|
|
"standby-real-frequency-mhz",
|
|
"standby-channel"
|
|
);
|
|
} else {
|
|
_useFrequencyFormatter = new FrequencyFormatter(
|
|
_rootNode->getNode("frequencies/selected-mhz", true),
|
|
_rootNode->getNode("frequencies/selected-mhz-fmt", true),
|
|
0.025, 118.0, 137.0);
|
|
|
|
_stbyFrequencyFormatter = new FrequencyFormatter(
|
|
_rootNode->getNode("frequencies/standby-mhz", true),
|
|
_rootNode->getNode("frequencies/standby-mhz-fmt", true),
|
|
0.025, 118.0, 137.0);
|
|
|
|
|
|
}
|
|
}
|
|
|
|
CommRadioImpl::~CommRadioImpl()
|
|
{
|
|
}
|
|
|
|
void CommRadioImpl::bind()
|
|
{
|
|
_metarBridge->setAtisNode(_atis.node());
|
|
#if defined(ENABLE_FLITE)
|
|
_atis.node()->addChangeListener(&_atisSpeaker);
|
|
#endif
|
|
// link the metar node. /environment/metar[3] is comm1 and /environment[4] is comm2.
|
|
// see FGDATA/Environment/environment.xml
|
|
_metarBridge->setMetarPropertiesRoot(fgGetNode("/environment", true)->getNode("metar", _num + 3, true));
|
|
_metarBridge->bind();
|
|
}
|
|
|
|
void CommRadioImpl::unbind()
|
|
{
|
|
#if defined(ENABLE_FLITE)
|
|
_atis.node()->removeChangeListener(&_atisSpeaker);
|
|
if (_sampleGroup.valid()) {
|
|
globals->get_subsystem<SGSoundMgr>()->remove(getSampleGroupRefname());
|
|
}
|
|
#endif
|
|
_metarBridge->unbind();
|
|
}
|
|
|
|
void CommRadioImpl::init()
|
|
{
|
|
// initialize power_btn to true if unset
|
|
string s = _power_btn.node()->getStringValue();
|
|
if (s.empty()) _power_btn = true;
|
|
|
|
// initialize squelch to a sane value if unset
|
|
s = _cutoffSignalQuality.node()->getStringValue();
|
|
if (s.empty()) _cutoffSignalQuality = 0.4;
|
|
|
|
// initialize add-noize to true if unset
|
|
s = _addNoise.node()->getStringValue();
|
|
if (s.empty()) _addNoise = true;
|
|
}
|
|
|
|
void CommRadioImpl::update(double dt)
|
|
{
|
|
if (dt < SGLimitsd::min()) return;
|
|
_stationTTL -= dt;
|
|
|
|
// Ensure all output properties get written on exit of this method
|
|
OnExit onExit(this);
|
|
|
|
SGGeod position;
|
|
try {
|
|
position = globals->get_aircraft_position();
|
|
}
|
|
catch (std::exception &) {
|
|
return;
|
|
}
|
|
|
|
#if defined(ENABLE_FLITE)
|
|
{
|
|
static const char * atisSampleRefName = "atis";
|
|
static const char * noiseSampleRefName = "noise";
|
|
|
|
if (_atisSpeaker.hasSpokenAtis()) {
|
|
// the speaker has created a new atis sample
|
|
if (!_sampleGroup.valid()) {
|
|
// create a sample group for our instrument on the fly
|
|
SGSoundMgr * smgr = globals->get_subsystem<SGSoundMgr>();
|
|
|
|
_sampleGroup = smgr->find(getSampleGroupRefname(), true);
|
|
_sampleGroup->tie_to_listener();
|
|
if (_addNoise) {
|
|
SGSharedPtr<SGSoundSample> noise = new SGSoundSample("Sounds/radionoise.wav", globals->get_fg_root());
|
|
_sampleGroup->add(noise, noiseSampleRefName);
|
|
_sampleGroup->play_looped(noiseSampleRefName);
|
|
}
|
|
|
|
}
|
|
// remove previous atis sample
|
|
_sampleGroup->remove(atisSampleRefName);
|
|
// add and play the new atis sample
|
|
SGSharedPtr<SGSoundSample> sample = _atisSpeaker.getSpokenAtis();
|
|
_sampleGroup->add(sample, atisSampleRefName);
|
|
_sampleGroup->play_looped(atisSampleRefName);
|
|
}
|
|
|
|
if (_sampleGroup.valid()) {
|
|
if (_addNoise) {
|
|
// if noise is configured and there is a noise sample
|
|
// scale noise and signal volume by signalQuality.
|
|
SGSoundSample * s = _sampleGroup->find(noiseSampleRefName);
|
|
if ( NULL != s) {
|
|
s->set_volume(1.0 - _signalQuality_norm);
|
|
s = _sampleGroup->find(atisSampleRefName);
|
|
s->set_volume(_signalQuality_norm);
|
|
}
|
|
}
|
|
// master volume for radio, mute on bad signal quality
|
|
_sampleGroup->set_volume(_signalQuality_norm >= _cutoffSignalQuality ? _volume_norm : 0.0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (false == (_power_btn) || false == (_serviceable)) {
|
|
_metarBridge->clearMetar();
|
|
_atis = "";
|
|
_stationTTL = 0.0;
|
|
return;
|
|
}
|
|
|
|
if (_frequency != _useFrequencyFormatter->getFrequency()) {
|
|
_frequency = _useFrequencyFormatter->getFrequency();
|
|
_stationTTL = 0.0;
|
|
}
|
|
|
|
if (_stationTTL <= 0.0) {
|
|
_stationTTL = 30.0;
|
|
|
|
// Note: 122.375 must be rounded DOWN to 122370
|
|
// in order to be consistent with apt.dat et cetera.
|
|
int freqKhz = 10 * static_cast<int>(_frequency * 100 + 0.25);
|
|
_commStationForFrequency = flightgear::CommStation::findByFreq(freqKhz, position, NULL);
|
|
|
|
}
|
|
|
|
if (false == _commStationForFrequency.valid()) return;
|
|
|
|
_slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
|
|
|
|
SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
|
|
|
|
_heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
|
|
|
|
_signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m * SG_METER_TO_NM);
|
|
_stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
|
|
_stationName = _commStationForFrequency->ident();
|
|
_airportId = _commStationForFrequency->airport()->getId();
|
|
|
|
#if defined(ENABLE_FLITE)
|
|
_atisSpeaker.setStationId(_airportId);
|
|
#endif
|
|
switch (_commStationForFrequency->type()) {
|
|
case FGPositioned::FREQ_ATIS:
|
|
case FGPositioned::FREQ_AWOS: {
|
|
if (_signalQuality_norm > 0.01) {
|
|
_metarBridge->requestMetarForId(_airportId);
|
|
} else {
|
|
_metarBridge->clearMetar();
|
|
_atis = "";
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
_metarBridge->clearMetar();
|
|
_atis = "";
|
|
break;
|
|
}
|
|
}
|
|
|
|
SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
|
|
{
|
|
return new CommRadioImpl(rootNode);
|
|
}
|
|
|
|
} // namespace Instrumentation
|
|
|