//
// $Id$
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <Main/globals.hxx>
#include <sstream>
#include <simgear/compiler.h>
#include <Main/fg_props.hxx>
-#include <Main/globals.hxx>
#include "voice.hxx"
+#if defined(ENABLE_FLITE)
+#include "flitevoice.hxx"
+#endif
+
#define VOICE "/sim/sound/voices"
+using std::string;
+
+class FGFestivalVoice : public FGVoiceMgr::FGVoice {
+public:
+ FGFestivalVoice(FGVoiceMgr *, const SGPropertyNode_ptr);
+ virtual ~FGFestivalVoice();
+ virtual void speak( const string & msg );
+ virtual void update();
+ void setVolume(double);
+ void setPitch(double);
+ void setSpeed(double);
+
+private:
+ SGSocket *_sock;
+ double _volume;
+ double _pitch;
+ double _speed;
+ SGPropertyNode_ptr _volumeNode;
+ SGPropertyNode_ptr _pitchNode;
+ SGPropertyNode_ptr _speedNode;
+};
/// MANAGER ///
FGVoiceMgr::FGVoiceMgr() :
+#if defined(ENABLE_THREADS)
+ _thread(NULL),
+#endif
_host(fgGetString(VOICE "/host", "localhost")),
_port(fgGetString(VOICE "/port", "1314")),
_enabled(fgGetBool(VOICE "/enabled", false)),
- _pausedNode(fgGetNode("/sim/sound/pause", true))
+ _pausedNode(fgGetNode("/sim/sound/working", true)),
+ _paused(false)
{
-#if defined(ENABLE_THREADS)
- if (!_enabled)
- return;
- _thread = new FGVoiceThread(this);
-#endif
}
FGVoiceMgr::~FGVoiceMgr()
{
-#if defined(ENABLE_THREADS)
- if (!_enabled)
- return;
- _thread->cancel();
- _thread->join();
-#endif
}
if (!_enabled)
return;
+#if defined(ENABLE_THREADS)
+ _thread = new FGVoiceThread(this);
+#endif
+
SGPropertyNode *base = fgGetNode(VOICE, true);
vector<SGPropertyNode_ptr> voices = base->getChildren("voice");
- for (unsigned int i = 0; i < voices.size(); i++)
- _voices.push_back(new FGVoice(this, voices[i]));
+ for (unsigned int i = 0; i < voices.size(); i++) {
+ SGPropertyNode_ptr voice = voices[i];
+ if( voice->getBoolValue("festival", false ) ) {
+ try {
+ _voices.push_back(new FGFestivalVoice(this, voice));
+ continue;
+ } catch (const std::string& ) {
+ SG_LOG(SG_SOUND, SG_WARN, "failed to create festival voice, falling back to flite voice" );
+ }
+ }
+#if defined(ENABLE_FLITE)
+ SG_LOG(SG_SOUND,SG_INFO,"creating flite voice" );
+ _voices.push_back(new FGFLITEVoice(this, voice));
+#else
+ SG_LOG(SG_SOUND,SG_ALERT,"non festival voice not supported." );
+#endif
+ }
#if defined(ENABLE_THREADS)
- _thread->start(1);
+ _thread->setProcessorAffinity(1);
+ _thread->start();
#endif
}
+void FGVoiceMgr::shutdown()
+{
+#if defined(ENABLE_THREADS)
+ if( _thread ) {
+ _thread->cancel();
+ _thread->join();
+ delete _thread;
+ _thread = NULL;
+ }
+#endif
+
+ for( std::vector<FGVoice*>::iterator it = _voices.begin(); it != _voices.end(); ++it )
+ delete *it;
+}
+
void FGVoiceMgr::update(double)
{
if (!_enabled)
return;
- _paused = _pausedNode->getBoolValue();
+ _paused = !_pausedNode->getBoolValue();
for (unsigned int i = 0; i < _voices.size(); i++) {
_voices[i]->update();
#if !defined(ENABLE_THREADS)
_voices[i]->speak();
#endif
}
-
}
/// VOICE ///
-FGVoiceMgr::FGVoice::FGVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
+FGFestivalVoice::FGFestivalVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
+ FGVoice(mgr),
_volumeNode(node->getNode("volume", true)),
_pitchNode(node->getNode("pitch", true)),
- _speedNode(node->getNode("speed", true)),
- _festival(node->getBoolValue("festival", true)),
- _mgr(mgr)
+ _speedNode(node->getNode("speed", true))
{
- SG_LOG(SG_IO, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
+ SG_LOG(SG_SOUND, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
<< "' voice");
const string &host = _mgr->_host;
const string &port = _mgr->_port;
_sock = new SGSocket(host, port, "tcp");
- _sock->set_timeout(10000);
- _connected = _sock->open(SG_IO_OUT);
- if (!_connected) {
- SG_LOG(SG_IO, SG_ALERT, "VOICE: no connection to `"
- << host << ':' << port << '\'');
- return;
- }
+ _sock->set_timeout(6000);
+ if (!_sock->open(SG_IO_OUT))
+ throw string("no connection to `") + host + ':' + port + '\'';
- if (_festival) {
+ {
_sock->writestring("(SayText \"\")\015\012");
char buf[4];
int len = _sock->read(buf, 3);
- if (len != 3 || buf[0] != 'L' || buf[1] != 'P') {
- SG_LOG(SG_IO, SG_ALERT, "VOICE: something is listening to "
- << host << ':' << port << "', but it doesn't seem "
- "to be Festival");
- _connected = false;
- return;
- }
-
- SG_LOG(SG_IO, SG_BULK, "VOICE: connection to Festival server on `"
+ if (len != 3 || buf[0] != 'L' || buf[1] != 'P')
+ throw string("unexpected or no response from `") + host + ':' + port
+ + "'. Either it's not\n Festival listening,"
+ " or Festival couldn't open a sound device.";
+
+ SG_LOG(SG_SOUND, SG_INFO, "VOICE: connection to Festival server on `"
<< host << ':' << port << "' established");
setVolume(_volume = _volumeNode->getDoubleValue());
string preamble = node->getStringValue("preamble", "");
if (!preamble.empty())
pushMessage(preamble);
-
- node->getNode("text", true)->addChangeListener(new FGVoiceListener(this));
+ node->getNode("text", true)->addChangeListener(this);
}
-FGVoiceMgr::FGVoice::~FGVoice()
+FGFestivalVoice::~FGFestivalVoice()
{
_sock->close();
delete _sock;
}
-void FGVoiceMgr::FGVoice::pushMessage(string m)
+void FGVoiceMgr::FGVoice::pushMessage( const string & m)
{
- _msg.push(m + "\015\012");
+ _msg.push(m);
#if defined(ENABLE_THREADS)
_mgr->_thread->wake_up();
#endif
}
-
bool FGVoiceMgr::FGVoice::speak(void)
{
- if (_msg.empty()) {
-// cerr << "<nothing to say>" << endl;
- return false;
- }
+ if (_msg.empty())
+ return false;
+
+ const string s = _msg.front();
+ _msg.pop();
- const string s = _msg.front();
- _msg.pop();
-// cerr << "POP " << s;
+ speak(s);
+
+ return !_msg.empty();
+}
+
+void FGFestivalVoice::speak( const string & msg )
+{
+ if( msg.empty() )
+ return;
+
+ string s;
+
+ if( msg[0] == '(' ) {
+ s = msg;
+ } else {
+ s.append("(SayText \"");
+ s.append(msg).append("\")");
+ }
+
+ s.append("\015\012");
_sock->writestring(s.c_str());
- return !_msg.empty();
}
-void FGVoiceMgr::FGVoice::update(void)
+void FGFestivalVoice::update(void)
{
- if (_connected && _festival) {
double d;
d = _volumeNode->getDoubleValue();
if (d != _volume)
d = _speedNode->getDoubleValue();
if (d != _speed)
setSpeed(_speed = d);
- }
}
-void FGVoiceMgr::FGVoice::setVolume(double d)
+void FGFestivalVoice::setVolume(double d)
{
std::ostringstream s;
s << "(set! default_after_synth_hooks (list (lambda (utt)"
}
-void FGVoiceMgr::FGVoice::setPitch(double d)
+void FGFestivalVoice::setPitch(double d)
{
std::ostringstream s;
s << "(set! int_lr_params '((target_f0_mean " << d <<
}
-void FGVoiceMgr::FGVoice::setSpeed(double d)
+void FGFestivalVoice::setSpeed(double d)
{
std::ostringstream s;
s << "(Parameter.set 'Duration_Stretch " << d << ')';
pushMessage(s.str());
}
-
-
-
/// THREAD ///
#if defined(ENABLE_THREADS)
/// LISTENER ///
-void FGVoiceMgr::FGVoice::FGVoiceListener::valueChanged(SGPropertyNode *node)
+void FGVoiceMgr::FGVoice::valueChanged(SGPropertyNode *node)
{
- if (_voice->_mgr->_paused)
+ if (_mgr->_paused)
return;
const string s = node->getStringValue();
-// cerr << "PUSH " << s << endl;
+ //cerr << "\033[31;1mBEFORE [" << s << "]\033[m" << endl;
string m;
for (unsigned int i = 0; i < s.size(); i++) {
char c = s[i];
- if (!isprint(c))
+ if (c == '\n' || c == '\r' || c == '\t')
+ m += ' ';
+ else if (!isprint(c))
continue;
- else if (c == '"' || c == '\\')
- m += '\\' + c;
+ else if (c == '\\' || c == '"')
+ m += '\\', m += c;
else if (c == '|' || c == '_')
m += ' '; // don't say "vertical bar" or "underscore"
else if (c == '&')
m += " and ";
+ else if (c == '>' || c == '<')
+ m += ' '; // don't say "greater than" or "less than" either
+ else if (c == '{') {
+ while (i < s.size())
+ if (s[++i] == '|')
+ break;
+ } else if (c == '}')
+ ;
else
m += c;
}
- if (_voice->_festival)
- m = string("(SayText \"") + m + "\")";
+ //cerr << "\033[31;1mAFTER [" << m << "]\033[m" << endl;
- _voice->pushMessage(m);
+ pushMessage(m);
}