add voice subsystem. This is a generic socket interface with a special
mode for communication with the "Festival" speech synthesis system.
This commit is contained in:
parent
4010611300
commit
ea49db6c4f
3 changed files with 409 additions and 1 deletions
|
@ -3,6 +3,7 @@ noinst_LIBRARIES = libSound.a
|
|||
libSound_a_SOURCES = \
|
||||
beacon.cxx beacon.hxx \
|
||||
fg_fx.cxx fg_fx.hxx \
|
||||
morse.cxx morse.hxx
|
||||
morse.cxx morse.hxx \
|
||||
voice.cxx voice.hxx
|
||||
|
||||
INCLUDES = -I$(top_srcdir) -I$(top_srcdir)/src
|
||||
|
|
270
src/Sound/voice.cxx
Normal file
270
src/Sound/voice.cxx
Normal file
|
@ -0,0 +1,270 @@
|
|||
// speech synthesis interface subsystem
|
||||
//
|
||||
// Written by Melchior FRANZ, started February 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Melchior FRANZ - mfranz@aon.at
|
||||
//
|
||||
// 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$
|
||||
|
||||
#include <sstream>
|
||||
#include <simgear/compiler.h>
|
||||
#include <Main/fg_props.hxx>
|
||||
#include <Main/globals.hxx>
|
||||
#include "voice.hxx"
|
||||
|
||||
#define VOICE "/sim/sound/voices"
|
||||
|
||||
|
||||
/// MANAGER ///
|
||||
|
||||
FGVoiceMgr::FGVoiceMgr() :
|
||||
_host(fgGetString(VOICE "/host", "localhost")),
|
||||
_port(fgGetString(VOICE "/port", "1314")),
|
||||
_enabled(fgGetBool(VOICE "/enabled", false)),
|
||||
_pausedNode(fgGetNode("/sim/sound/pause", true))
|
||||
{
|
||||
#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
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::init()
|
||||
{
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
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]));
|
||||
|
||||
#if defined(ENABLE_THREADS)
|
||||
_thread->start(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::update(double)
|
||||
{
|
||||
if (!_enabled)
|
||||
return;
|
||||
|
||||
_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) :
|
||||
_volumeNode(node->getNode("volume", true)),
|
||||
_pitchNode(node->getNode("pitch", true)),
|
||||
_speedNode(node->getNode("speed", true)),
|
||||
_festival(node->getBoolValue("festival", true)),
|
||||
_mgr(mgr)
|
||||
{
|
||||
SG_LOG(SG_IO, 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;
|
||||
}
|
||||
|
||||
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 `"
|
||||
<< host << ':' << port << "' established");
|
||||
|
||||
setVolume(_volume = _volumeNode->getDoubleValue());
|
||||
setPitch(_pitch = _pitchNode->getDoubleValue());
|
||||
setSpeed(_speed = _speedNode->getDoubleValue());
|
||||
}
|
||||
|
||||
string preamble = node->getStringValue("preamble", "");
|
||||
if (!preamble.empty())
|
||||
pushMessage(preamble);
|
||||
|
||||
node->getNode("text", true)->addChangeListener(new FGVoiceListener(this));
|
||||
}
|
||||
|
||||
|
||||
FGVoiceMgr::FGVoice::~FGVoice()
|
||||
{
|
||||
_sock->close();
|
||||
delete _sock;
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::FGVoice::pushMessage(string m)
|
||||
{
|
||||
_msg.push(m + "\015\012");
|
||||
#if defined(ENABLE_THREADS)
|
||||
_mgr->_thread->wake_up();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool FGVoiceMgr::FGVoice::speak(void)
|
||||
{
|
||||
if (_msg.empty()) {
|
||||
// cerr << "<nothing to say>" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const string s = _msg.front();
|
||||
_msg.pop();
|
||||
// cerr << "POP " << s;
|
||||
_sock->writestring(s.c_str());
|
||||
return !_msg.empty();
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::FGVoice::update(void)
|
||||
{
|
||||
if (_connected && _festival) {
|
||||
double d;
|
||||
d = _volumeNode->getDoubleValue();
|
||||
if (d != _volume)
|
||||
setVolume(_volume = d);
|
||||
d = _pitchNode->getDoubleValue();
|
||||
if (d != _pitch)
|
||||
setPitch(_pitch = d);
|
||||
d = _speedNode->getDoubleValue();
|
||||
if (d != _speed)
|
||||
setSpeed(_speed = d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::FGVoice::setVolume(double d)
|
||||
{
|
||||
std::ostringstream s;
|
||||
s << "(set! default_after_synth_hooks (list (lambda (utt)"
|
||||
"(utt.wave.rescale utt " << d << " t))))";
|
||||
pushMessage(s.str());
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::FGVoice::setPitch(double d)
|
||||
{
|
||||
std::ostringstream s;
|
||||
s << "(set! int_lr_params '((target_f0_mean " << d <<
|
||||
")(target_f0_std 14)(model_f0_mean 170)"
|
||||
"(model_f0_std 34)))";
|
||||
pushMessage(s.str());
|
||||
}
|
||||
|
||||
|
||||
void FGVoiceMgr::FGVoice::setSpeed(double d)
|
||||
{
|
||||
std::ostringstream s;
|
||||
s << "(Parameter.set 'Duration_Stretch " << d << ')';
|
||||
pushMessage(s.str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// THREAD ///
|
||||
|
||||
#if defined(ENABLE_THREADS)
|
||||
void FGVoiceMgr::FGVoiceThread::run(void)
|
||||
{
|
||||
while (1) {
|
||||
bool busy = false;
|
||||
for (unsigned int i = 0; i < _mgr->_voices.size(); i++)
|
||||
busy |= _mgr->_voices[i]->speak();
|
||||
|
||||
if (!busy)
|
||||
wait_for_jobs();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
/// LISTENER ///
|
||||
|
||||
void FGVoiceMgr::FGVoice::FGVoiceListener::valueChanged(SGPropertyNode *node)
|
||||
{
|
||||
if (_voice->_mgr->_paused)
|
||||
return;
|
||||
|
||||
const string s = node->getStringValue();
|
||||
// cerr << "PUSH " << s << endl;
|
||||
|
||||
string m;
|
||||
for (unsigned int i = 0; i < s.size(); i++) {
|
||||
char c = s[i];
|
||||
if (!isprint(c))
|
||||
continue;
|
||||
else if (c == '"' || c == '\\')
|
||||
m += '\\' + c;
|
||||
else if (c == '|' || c == '_')
|
||||
m += ' '; // don't say "vertical bar" or "underscore"
|
||||
else if (c == '&')
|
||||
m += " and ";
|
||||
else
|
||||
m += c;
|
||||
}
|
||||
if (_voice->_festival)
|
||||
m = string("(SayText \"") + m + "\")";
|
||||
|
||||
_voice->pushMessage(m);
|
||||
}
|
||||
|
||||
|
137
src/Sound/voice.hxx
Normal file
137
src/Sound/voice.hxx
Normal file
|
@ -0,0 +1,137 @@
|
|||
// speech synthesis interface subsystem
|
||||
//
|
||||
// Written by Melchior FRANZ, started February 2006.
|
||||
//
|
||||
// Copyright (C) 2006 Melchior FRANZ - mfranz@aon.at
|
||||
//
|
||||
// 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 _VOICE_HXX
|
||||
#define _VOICE_HXX
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/props/props.hxx>
|
||||
#include <simgear/io/sg_socket.hxx>
|
||||
#include <simgear/structure/subsystem_mgr.hxx>
|
||||
|
||||
#include <Main/fg_props.hxx>
|
||||
|
||||
#if defined(ENABLE_THREADS)
|
||||
# include <simgear/threads/SGThread.hxx>
|
||||
# include <simgear/threads/SGQueue.hxx>
|
||||
#else
|
||||
# include <queue>
|
||||
SG_USING_STD(queue);
|
||||
#endif // ENABLE_THREADS
|
||||
|
||||
SG_USING_STD(vector);
|
||||
|
||||
|
||||
|
||||
class FGVoiceMgr : public SGSubsystem {
|
||||
public:
|
||||
FGVoiceMgr();
|
||||
~FGVoiceMgr();
|
||||
void init(void);
|
||||
void update(double dt);
|
||||
|
||||
private:
|
||||
class FGVoice;
|
||||
|
||||
#if defined(ENABLE_THREADS)
|
||||
class FGVoiceThread;
|
||||
FGVoiceThread *_thread;
|
||||
#endif
|
||||
|
||||
string _host;
|
||||
string _port;
|
||||
bool _enabled;
|
||||
SGPropertyNode_ptr _pausedNode;
|
||||
bool _paused;
|
||||
vector<FGVoice *> _voices;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#if defined(ENABLE_THREADS)
|
||||
class FGVoiceMgr::FGVoiceThread : public SGThread {
|
||||
public:
|
||||
FGVoiceThread(FGVoiceMgr *mgr) : _mgr(mgr) {}
|
||||
void run();
|
||||
void wake_up() { _jobs.signal(); }
|
||||
|
||||
private:
|
||||
void wait_for_jobs() { _mutex.lock(); _jobs.wait(_mutex); _mutex.unlock(); }
|
||||
SGPthreadCond _jobs;
|
||||
SGMutex _mutex;
|
||||
FGVoiceMgr *_mgr;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
class FGVoiceMgr::FGVoice {
|
||||
public:
|
||||
FGVoice(FGVoiceMgr *, const SGPropertyNode_ptr);
|
||||
~FGVoice();
|
||||
bool speak();
|
||||
void update();
|
||||
void setVolume(double);
|
||||
void setPitch(double);
|
||||
void setSpeed(double);
|
||||
void pushMessage(string);
|
||||
|
||||
private:
|
||||
class FGVoiceListener;
|
||||
SGSocket *_sock;
|
||||
bool _connected;
|
||||
double _volume;
|
||||
double _pitch;
|
||||
double _speed;
|
||||
SGPropertyNode_ptr _volumeNode;
|
||||
SGPropertyNode_ptr _pitchNode;
|
||||
SGPropertyNode_ptr _speedNode;
|
||||
bool _festival;
|
||||
FGVoiceMgr *_mgr;
|
||||
|
||||
#if defined(ENABLE_THREADS)
|
||||
SGLockedQueue<string> _msg;
|
||||
#else
|
||||
queue<string> _msg;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class FGVoiceMgr::FGVoice::FGVoiceListener : public SGPropertyChangeListener {
|
||||
public:
|
||||
FGVoiceListener(FGVoice *voice) : _voice(voice) {}
|
||||
void valueChanged(SGPropertyNode *node);
|
||||
|
||||
private:
|
||||
FGVoice *_voice;
|
||||
};
|
||||
|
||||
|
||||
#endif // _VOICE_HXX
|
Loading…
Add table
Reference in a new issue