1
0
Fork 0

use flite+hts for metar

This commit is contained in:
Torsten Dreyer 2014-04-24 20:59:08 +02:00
parent f6b6cfc645
commit cadb77b18d
8 changed files with 607 additions and 191 deletions

View file

@ -39,6 +39,12 @@
#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 {
@ -46,129 +52,185 @@ namespace Instrumentation {
using simgear::PropertyObject;
using std::string;
#if defined(ENABLE_FLITE)
class MetarSpeaker: public SGPropertyChangeListener, SoundSampleReadyListener {
public:
MetarSpeaker();
virtual ~MetarSpeaker();
virtual void valueChanged(SGPropertyNode * node);
virtual void SoundSampleReady(SGSharedPtr<SGSoundSample>);
bool hasSpokenMetar() { return _spokenMetar.empty() == false; }
SGSharedPtr<SGSoundSample> getSpokenMetar() { return _spokenMetar.pop(); }
private:
SynthesizeRequest _synthesizeRequest;
SGLockedQueue<SGSharedPtr<SGSoundSample> > _spokenMetar;
};
MetarSpeaker::MetarSpeaker()
{
_synthesizeRequest.listener = this;
}
MetarSpeaker::~MetarSpeaker()
{
}
void MetarSpeaker::valueChanged(SGPropertyNode * node)
{
string newText = node->getStringValue();
if (_synthesizeRequest.text == newText) return;
_synthesizeRequest.text = newText;
FGSoundManager * smgr = dynamic_cast<FGSoundManager*>(globals->get_soundmgr());
assert(smgr != NULL);
string voice = globals->get_fg_root() + "/ATC/cmu_us_arctic_slt.htsvoice";
FLITEVoiceSynthesizer * synthesizer = dynamic_cast<FLITEVoiceSynthesizer*>(smgr->getSynthesizer(voice));
synthesizer->synthesize(_synthesizeRequest);
}
void MetarSpeaker::SoundSampleReady(SGSharedPtr<SGSoundSample> sample)
{
// we are now in the synthesizers worker thread!
_spokenMetar.push(sample);
}
#endif
SignalQualityComputer::~SignalQualityComputer()
{
}
class SimpleDistanceSquareSignalQualityComputer : public SignalQualityComputer
{
class SimpleDistanceSquareSignalQualityComputer: public SignalQualityComputer {
public:
SimpleDistanceSquareSignalQualityComputer( double range ) : _rangeM(range), _rangeM2(range*range) {}
virtual double computeSignalQuality( const SGGeod & sender, const SGGeod & receiver ) const;
virtual double computeSignalQuality( const SGVec3d & sender, const SGVec3d & receiver ) const;
virtual double computeSignalQuality( double slantDistanceM ) const;
SimpleDistanceSquareSignalQualityComputer(double range)
: _rangeM(range), _rangeM2(range * range)
{
}
virtual double computeSignalQuality(const SGGeod & sender, const SGGeod & receiver) const;
virtual double computeSignalQuality(const SGVec3d & sender, const SGVec3d & receiver) const;
virtual double computeSignalQuality(double slantDistanceM) const;
private:
double _rangeM;
double _rangeM2;
};
double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality( const SGVec3d & sender, const SGVec3d & receiver ) const
double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(const SGVec3d & sender, const SGVec3d & receiver) const
{
return computeSignalQuality( dist( sender, receiver ) );
return computeSignalQuality(dist(sender, receiver));
}
double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality( const SGGeod & sender, const SGGeod & receiver ) const
double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(const SGGeod & sender, const SGGeod & receiver) const
{
return computeSignalQuality( SGGeodesy::distanceM( sender, receiver ) );
return computeSignalQuality(SGGeodesy::distanceM(sender, receiver));
}
double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality( double distanceM ) const
double SimpleDistanceSquareSignalQualityComputer::computeSignalQuality(double distanceM) const
{
return distanceM < _rangeM ? 1.0 : ( _rangeM2 / (distanceM*distanceM) );
return distanceM < _rangeM ? 1.0 : (_rangeM2 / (distanceM * distanceM));
}
class OnExitHandler {
public:
virtual void onExit() = 0;
virtual ~OnExitHandler() {}
public:
virtual void onExit() = 0;
virtual ~OnExitHandler()
{
}
};
class OnExit {
public:
OnExit( OnExitHandler * onExitHandler ) : _onExitHandler( onExitHandler ) {}
~OnExit() { _onExitHandler->onExit(); }
private:
OnExitHandler * _onExitHandler;
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),
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() {}
_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;
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;
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;
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;
}
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 {
class MetarBridge: public SGReferenced, public SGPropertyChangeListener {
public:
MetarBridge();
~MetarBridge();
void bind();
void unbind();
void requestMetarForId( std::string & id );
void requestMetarForId(std::string & id);
void clearMetar();
void setMetarPropertiesRoot( SGPropertyNode_ptr n ) { _metarPropertiesNode = n; }
void setAtisNode( SGPropertyNode * n ) { _atisNode = n; }
void setMetarPropertiesRoot(SGPropertyNode_ptr n)
{
_metarPropertiesNode = n;
}
void setAtisNode(SGPropertyNode * n)
{
_atisNode = n;
}
protected:
virtual void valueChanged(SGPropertyNode * );
virtual void valueChanged(SGPropertyNode *);
private:
std::string _requestedId;
@ -179,8 +241,8 @@ private:
};
typedef SGSharedPtr<MetarBridge> MetarBridgeRef;
MetarBridge::MetarBridge() :
_atisNode(0)
MetarBridge::MetarBridge()
: _atisNode(0)
{
}
@ -190,31 +252,31 @@ MetarBridge::~MetarBridge()
void MetarBridge::bind()
{
_realWxEnabledNode = fgGetNode( "/environment/realwx/enabled", true );
_metarPropertiesNode->getNode( "valid", true )->addChangeListener( this );
_realWxEnabledNode = fgGetNode("/environment/realwx/enabled", true);
_metarPropertiesNode->getNode("valid", true)->addChangeListener(this);
}
void MetarBridge::unbind()
{
_metarPropertiesNode->getNode( "valid", true )->removeChangeListener( this );
_metarPropertiesNode->getNode("valid", true)->removeChangeListener(this);
}
void MetarBridge::requestMetarForId( std::string & id )
void MetarBridge::requestMetarForId(std::string & id)
{
std::string uppercaseId = simgear::strutils::uppercase( id );
if( _requestedId == uppercaseId ) return;
std::string uppercaseId = simgear::strutils::uppercase(id);
if (_requestedId == uppercaseId) return;
_requestedId = uppercaseId;
if( _realWxEnabledNode->getBoolValue() ) {
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 {
_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 ) );
if ( NULL != _atisNode && false == _requestedId.empty()) {
CurrentWeatherATISInformationProvider provider(_requestedId);
_atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
}
}
}
@ -222,82 +284,74 @@ void MetarBridge::requestMetarForId( std::string & id )
void MetarBridge::clearMetar()
{
string empty;
requestMetarForId( empty );
requestMetarForId(empty);
}
void MetarBridge::valueChanged(SGPropertyNode * node )
void MetarBridge::valueChanged(SGPropertyNode * node)
{
// check for raising edge of valid flag
if( NULL == node || false == node->getBoolValue() || false == _realWxEnabledNode->getBoolValue() )
return;
if ( NULL == node || false == node->getBoolValue() || false == _realWxEnabledNode->getBoolValue()) return;
std::string responseId = simgear::strutils::uppercase(
_metarPropertiesNode->getNode( "station-id", true )->getStringValue() );
std::string responseId = simgear::strutils::uppercase(_metarPropertiesNode->getNode("station-id", true)->getStringValue());
// unrequested metar!?
if( responseId != _requestedId )
return;
if (responseId != _requestedId) return;
if( NULL != _atisNode ) {
MetarPropertiesATISInformationProvider provider( _metarPropertiesNode );
_atisNode->setStringValue( _atisEncoder.encodeATIS( &provider ) );
if ( NULL != _atisNode) {
MetarPropertiesATISInformationProvider provider(_metarPropertiesNode);
_atisNode->setStringValue(_atisEncoder.encodeATIS(&provider));
}
}
/* ------------- The CommRadio implementation ---------------------- */
class CommRadioImpl : public CommRadio, OutputProperties {
class CommRadioImpl: public CommRadio, OutputProperties {
public:
CommRadioImpl( SGPropertyNode_ptr node );
CommRadioImpl(SGPropertyNode_ptr node);
virtual ~CommRadioImpl();
virtual void update( double dt );
virtual void update(double dt);
virtual void init();
void bind();
void unbind();
private:
int _num;
MetarBridgeRef _metarBridge;
int _num;
MetarBridgeRef _metarBridge;
#if defined(ENABLE_FLITE)
MetarSpeaker _metarSpeaker;
#endif
FrequencyFormatter _useFrequencyFormatter;
FrequencyFormatter _stbyFrequencyFormatter;
const SignalQualityComputerRef _signalQualityComputer;
const SignalQualityComputerRef _signalQualityComputer;
double _stationTTL;
double _frequency;
flightgear::CommStationRef _commStationForFrequency;
PropertyObject<bool> _serviceable;
PropertyObject<bool> _power_btn;
PropertyObject<bool> _power_good;
PropertyObject<bool> _serviceable;
PropertyObject<bool> _power_btn;
PropertyObject<bool> _power_good;
PropertyObject<double> _volume_norm;
PropertyObject<string> _atis;
};
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() ),
_useFrequencyFormatter( _rootNode->getNode("frequencies/selected-mhz",true),
_rootNode->getNode("frequencies/selected-mhz-fmt",true), 0.025, 118.0, 136.0 ),
_stbyFrequencyFormatter( _rootNode->getNode("frequencies/standby-mhz",true),
_rootNode->getNode("frequencies/standby-mhz-fmt",true), 0.025, 118.0, 136.0 ),
_signalQualityComputer( new SimpleDistanceSquareSignalQualityComputer(10*SG_NM_TO_METER) ),
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()), _useFrequencyFormatter(
_rootNode->getNode("frequencies/selected-mhz", true), _rootNode->getNode("frequencies/selected-mhz-fmt", true), 0.025,
118.0, 136.0), _stbyFrequencyFormatter(_rootNode->getNode("frequencies/standby-mhz", true),
_rootNode->getNode("frequencies/standby-mhz-fmt", true), 0.025, 118.0, 136.0), _signalQualityComputer(
new SimpleDistanceSquareSignalQualityComputer(10 * SG_NM_TO_METER)),
_stationTTL(0.0),
_frequency(-1.0),
_commStationForFrequency(NULL),
_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) )
_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))
{
}
@ -307,15 +361,21 @@ CommRadioImpl::~CommRadioImpl()
void CommRadioImpl::bind()
{
_metarBridge->setAtisNode( _atis.node() );
// 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->setAtisNode(_atis.node());
#if defined(ENABLE_FLITE)
_atis.node()->addChangeListener( &_metarSpeaker );
#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( &_metarSpeaker );
#endif
_metarBridge->unbind();
}
@ -323,80 +383,88 @@ void CommRadioImpl::init()
{
}
void CommRadioImpl::update( double dt )
void CommRadioImpl::update(double dt)
{
if( dt < SGLimitsd::min() ) return;
_stationTTL -= dt;
if (dt < SGLimitsd::min()) return;
_stationTTL -= dt;
// Ensure all output properties get written on exit of this method
OnExit onExit(this);
// 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; }
SGGeod position;
try {
position = globals->get_aircraft_position();
}
catch (std::exception &) {
return;
}
if( false == (_power_btn )) {
_stationTTL = 0.0;
return;
}
if (false == (_power_btn)) {
_stationTTL = 0.0;
return;
}
if (_frequency != _useFrequencyFormatter.getFrequency()) {
_frequency = _useFrequencyFormatter.getFrequency();
_stationTTL = 0.0;
}
if( _frequency != _useFrequencyFormatter.getFrequency() ) {
_frequency = _useFrequencyFormatter.getFrequency();
_stationTTL = 0.0;
}
if (_stationTTL <= 0.0) {
_stationTTL = 30.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);
// 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;
if( false == _commStationForFrequency.valid() ) return;
_slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
_slantDistance_m = dist(_commStationForFrequency->cart(), SGVec3d::fromGeod(position));
SGGeodesy::inverse(position, _commStationForFrequency->geod(), _trueBearingTo_deg, _trueBearingFrom_deg, _trackDistance_m);
SGGeodesy::inverse(position, _commStationForFrequency->geod(),
_trueBearingTo_deg,
_trueBearingFrom_deg,
_trackDistance_m );
_heightAboveStation_ft = SGMiscd::max(0.0, position.getElevationFt() - _commStationForFrequency->airport()->elevation());
_heightAboveStation_ft =
SGMiscd::max(0.0, position.getElevationFt()
- _commStationForFrequency->airport()->elevation());
_signalQuality_norm = _signalQualityComputer->computeSignalQuality( _slantDistance_m );
_stationType = _commStationForFrequency->nameForType( _commStationForFrequency->type() );
_stationName = _commStationForFrequency->ident();
_airportId = _commStationForFrequency->airport()->getId();
_signalQuality_norm = _signalQualityComputer->computeSignalQuality(_slantDistance_m);
_stationType = _commStationForFrequency->nameForType(_commStationForFrequency->type());
_stationName = _commStationForFrequency->ident();
_airportId = _commStationForFrequency->airport()->getId();
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:
switch (_commStationForFrequency->type()) {
case FGPositioned::FREQ_ATIS:
case FGPositioned::FREQ_AWOS: {
if (_signalQuality_norm > 0.01) {
_metarBridge->requestMetarForId(_airportId);
} else {
_metarBridge->clearMetar();
_atis = "";
break;
}
}
break;
default:
_metarBridge->clearMetar();
_atis = "";
break;
}
#if defined(ENABLE_FLITE)
if( _metarSpeaker.hasSpokenMetar() ) {
SGSharedPtr<SGSoundSample> sample = _metarSpeaker.getSpokenMetar();
SGSoundMgr * _smgr = globals->get_soundmgr();
SGSampleGroup * _sgr = _smgr->find("comm", true );
_sgr->tie_to_listener();
_sgr->remove("metar");
_sgr->add(sample,"metar");
_sgr->play_looped( "metar" );
}
#endif
}
SGSubsystem * CommRadio::createInstance( SGPropertyNode_ptr rootNode )
SGSubsystem * CommRadio::createInstance(SGPropertyNode_ptr rootNode)
{
return new CommRadioImpl( rootNode );
return new CommRadioImpl(rootNode);
}
} // namespace Instrumentation

View file

@ -24,4 +24,19 @@ set(HEADERS
soundmanager.hxx
)
if (ENABLE_FLITE)
set(SOURCES
"${SOURCES}"
VoiceSynthesizer.cxx
flitevoice.cxx
)
set(HEADERS
"${HEADERS}"
VoiceSynthesizer.hxx
flitevoice.hxx
)
endif()
flightgear_component(Sound "${SOURCES}" "${HEADERS}")

View file

@ -0,0 +1,107 @@
/*
* VoiceSynthesizer.cxx
*
* Created on: Apr 24, 2014
* Author: flightgear
*/
#include "VoiceSynthesizer.hxx"
#include <Main/globals.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/sound/readwav.hxx>
#include <simgear/misc/sg_path.hxx>
#include <OpenThreads/Thread>
#include <flite_hts_engine.h>
class ScopedTempfile {
public:
ScopedTempfile()
{
_name = ::tempnam(globals->get_fg_home().c_str(), "fgvox");
}
~ScopedTempfile()
{
if (_name) ::unlink(_name);
::free(_name);
}
const char * getName() const
{
return _name;
}
SGPath getPath()
{
return SGPath(_name);
}
private:
char * _name;
};
class FLITEVoiceSynthesizer::WorkerThread: public OpenThreads::Thread {
public:
WorkerThread(FLITEVoiceSynthesizer * synthesizer)
: _synthesizer(synthesizer)
{
}
virtual void run();
private:
FLITEVoiceSynthesizer * _synthesizer;
};
void FLITEVoiceSynthesizer::WorkerThread::run()
{
for (;;) {
SynthesizeRequest request = _synthesizer->_requests.pop();
if ( NULL != request.listener) {
SGSharedPtr<SGSoundSample> sample = _synthesizer->synthesize(request.text);
request.listener->SoundSampleReady( sample );
}
}
}
void FLITEVoiceSynthesizer::synthesize( SynthesizeRequest & request)
{
_requests.push(request);
}
FLITEVoiceSynthesizer::FLITEVoiceSynthesizer(const std::string & voice)
: _engine(new Flite_HTS_Engine), _worker(new FLITEVoiceSynthesizer::WorkerThread(this))
{
Flite_HTS_Engine_initialize(_engine);
Flite_HTS_Engine_load(_engine, voice.c_str());
_worker->start();
}
FLITEVoiceSynthesizer::~FLITEVoiceSynthesizer()
{
_worker->cancel();
_worker->join();
Flite_HTS_Engine_clear(_engine);
}
SGSoundSample * FLITEVoiceSynthesizer::synthesize(const std::string & text)
{
ScopedTempfile scratch;
if ( FALSE == Flite_HTS_Engine_synthesize(_engine, text.c_str(), scratch.getName())) return NULL;
SG_LOG(SG_SOUND, SG_ALERT, "created wav at " << scratch.getPath());
ALenum format;
ALsizei size;
ALfloat freqf;
ALvoid * data = simgear::loadWAVFromFile(scratch.getPath(), format, size, freqf);
SG_LOG(SG_ALL, SG_ALERT, "loaded wav at " << freqf << "Hz size=" << size << " format=" << format);
if (data == NULL) {
SG_LOG(SG_SOUND, SG_ALERT, "Failed to load wav file " << scratch.getPath());
}
if (format == AL_FORMAT_STEREO8 || format == AL_FORMAT_STEREO16) {
free(data);
SG_LOG(SG_SOUND, SG_ALERT, "Warning: STEREO files are not supported for 3D audio effects: " << scratch.getPath());
}
return new SGSoundSample(&data, size, (ALsizei) freqf, format);
}

View file

@ -0,0 +1,81 @@
/*
* VoiceSynthesizer.hxx
*
* Created on: Apr 24, 2014
* Author: flightgear
*/
#ifndef VOICESYNTHESIZER_HXX_
#define VOICESYNTHESIZER_HXX_
#include <simgear/sound/sample_openal.hxx>
#include <simgear/threads/SGQueue.hxx>
#include <string>
struct _Flite_HTS_Engine;
/**
* A Voice Synthesizer Interface
*/
class VoiceSynthesizer {
public:
virtual ~VoiceSynthesizer() {};
virtual SGSoundSample * synthesize( const std::string & text ) = 0;
};
class SoundSampleReadyListener {
public:
virtual ~SoundSampleReadyListener() {}
virtual void SoundSampleReady( SGSharedPtr<SGSoundSample> ) = 0;
};
struct SynthesizeRequest {
SynthesizeRequest() {
speed = volume = pitch = 1.0;
listener = NULL;
}
SynthesizeRequest( const SynthesizeRequest & other ) {
text = other.text;
speed = other.speed;
volume = other.volume;
pitch = other.pitch;
listener = other.listener;
}
SynthesizeRequest & operator = ( const SynthesizeRequest & other ) {
text = other.text;
speed = other.speed;
volume = other.volume;
pitch = other.pitch;
listener = other.listener;
return *this;
}
std::string text;
double speed;
double volume;
double pitch;
SoundSampleReadyListener * listener;
};
/**
* A Voice Synthesizer using FLITE+HTS
*/
class FLITEVoiceSynthesizer : public VoiceSynthesizer {
public:
FLITEVoiceSynthesizer( const std::string & voice );
~FLITEVoiceSynthesizer();
virtual SGSoundSample * synthesize( const std::string & text );
virtual void synthesize( SynthesizeRequest & request );
private:
struct _Flite_HTS_Engine * _engine;
class WorkerThread;
WorkerThread * _worker;
typedef SGBlockingQueue<SynthesizeRequest> SynthesizeRequestList;
SynthesizeRequestList _requests;
};
#endif /* VOICESYNTHESIZER_HXX_ */

76
src/Sound/flitevoice.cxx Normal file
View file

@ -0,0 +1,76 @@
// speech synthesis interface subsystem
//
// Written by Torsten Dreyer, started April 2014
//
// Copyright (C) 2014 Torsten Dreyer - torsten (at) t3r (dot) de
//
// 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 "flitevoice.hxx"
#include <Main/fg_props.hxx>
#include <simgear/sound/sample_group.hxx>
#include <flite_hts_engine.h>
using std::string;
#include "VoiceSynthesizer.hxx"
FGFLITEVoice::FGFLITEVoice(FGVoiceMgr * mgr, const SGPropertyNode_ptr node)
: FGVoice(mgr), _synthesizer( NULL )
{
string voice = globals->get_fg_root() + "/ATC/cmu_us_arctic_slt.htsvoice";
_synthesizer = new FLITEVoiceSynthesizer( voice.c_str() );
SGSoundMgr *smgr = globals->get_soundmgr();
_sgr = smgr->find("atc", true);
_sgr->tie_to_listener();
node->getNode("text", true)->addChangeListener(this);
SG_LOG(SG_ALL, SG_ALERT, "FLITEVoice initialized");
}
FGFLITEVoice::~FGFLITEVoice()
{
delete _synthesizer;
SG_LOG(SG_ALL, SG_ALERT, "FLITEVoice dtor()");
}
void FGFLITEVoice::speak(const string & msg)
{
SG_LOG(SG_ALL, SG_ALERT, "FLITEVoice speak(" << msg << ")");
_sgr->remove("flite");
string s = simgear::strutils::strip( msg );
if( false == s.empty() ) {
SGSoundSample * sample = _synthesizer->synthesize( msg );
_sgr->add(sample, "flite");
_sgr->set_volume(1.0);
_sgr->resume();
_sgr->play("flite", true);
}
}
void FGFLITEVoice::update()
{
}

45
src/Sound/flitevoice.hxx Normal file
View file

@ -0,0 +1,45 @@
// speech synthesis interface subsystem
//
// Written by Torsten Dreyer, started April 2014
//
// Copyright (C) 2014 Torsten Dreyer - torsten (at) t3r (dot) de
//
// 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.
//
// $Id$
#ifndef _FLITEVOICE_HXX
#define _FLITEVOICE_HXX
#include "voice.hxx"
#include <simgear/sound/soundmgr_openal.hxx>
class VoiceSynthesizer;
class FGFLITEVoice: public FGVoiceMgr::FGVoice {
public:
FGFLITEVoice(FGVoiceMgr *, const SGPropertyNode_ptr);
virtual ~FGFLITEVoice();
virtual void speak(const std::string & msg);
virtual void update();
private:
FGFLITEVoice(const FGFLITEVoice & other);
FGFLITEVoice & operator =(const FGFLITEVoice & other);
SGSharedPtr<SGSampleGroup> _sgr;
VoiceSynthesizer * _synthesizer;
};
#endif // _FLITEVOICE_HXX

View file

@ -23,6 +23,9 @@
#include <simgear/sound/soundmgr_openal.hxx>
#if defined(ENABLE_FLITE)
#include "VoiceSynthesizer.hxx"
#endif
#include "soundmanager.hxx"
#include "Main/globals.hxx"
#include "Main/fg_props.hxx"
@ -198,4 +201,16 @@ void FGSoundManager::update(double dt)
}
}
#if defined(ENABLE_FLITE)
VoiceSynthesizer * FGSoundManager::getSynthesizer( const std::string & voice )
{
std::map<std::string,VoiceSynthesizer*>::iterator it = _synthesizers.find(voice);
if( it == _synthesizers.end() ) {
VoiceSynthesizer * synthesizer = new FLITEVoiceSynthesizer( voice );
_synthesizers[voice] = synthesizer;
return synthesizer;
}
return it->second;
}
#endif
#endif // ENABLE_AUDIO_SUPPORT

View file

@ -21,12 +21,16 @@
#define __FG_SOUNDMGR_HXX 1
#include <memory>
#include <map>
#include <simgear/props/props.hxx>
#include <simgear/structure/subsystem_mgr.hxx>
#include <simgear/sound/soundmgr_openal.hxx>
class SGSoundMgr;
class Listener;
#if defined(ENABLE_FLITE)
class VoiceSynthesizer;
#endif
#ifdef ENABLE_AUDIO_SUPPORT
class FGSoundManager : public SGSoundMgr
@ -42,7 +46,9 @@ public:
void activate(bool State);
void update_device_list();
#if defined(ENABLE_FLITE)
VoiceSynthesizer * getSynthesizer( const std::string & voice );
#endif
private:
bool stationaryView() const;
@ -53,6 +59,9 @@ private:
SGPropertyNode_ptr _velocityNorthFPS, _velocityEastFPS, _velocityDownFPS;
SGPropertyNode_ptr _viewXoffset, _viewYoffset, _viewZoffset;
std::auto_ptr<Listener> _listener;
#if defined(ENABLE_FLITE)
std::map<std::string,VoiceSynthesizer*> _synthesizers;
#endif
};
#else
#include "Main/fg_props.hxx"