From ea49db6c4f84f56767e4a2e0791dda8d647b802a Mon Sep 17 00:00:00 2001 From: mfranz <mfranz> Date: Sat, 11 Feb 2006 11:58:17 +0000 Subject: [PATCH] add voice subsystem. This is a generic socket interface with a special mode for communication with the "Festival" speech synthesis system. --- src/Sound/Makefile.am | 3 +- src/Sound/voice.cxx | 270 ++++++++++++++++++++++++++++++++++++++++++ src/Sound/voice.hxx | 137 +++++++++++++++++++++ 3 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 src/Sound/voice.cxx create mode 100644 src/Sound/voice.hxx diff --git a/src/Sound/Makefile.am b/src/Sound/Makefile.am index a43e89927..cbbb364f0 100644 --- a/src/Sound/Makefile.am +++ b/src/Sound/Makefile.am @@ -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 diff --git a/src/Sound/voice.cxx b/src/Sound/voice.cxx new file mode 100644 index 000000000..77b40821c --- /dev/null +++ b/src/Sound/voice.cxx @@ -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); +} + + diff --git a/src/Sound/voice.hxx b/src/Sound/voice.hxx new file mode 100644 index 000000000..eb4201dce --- /dev/null +++ b/src/Sound/voice.hxx @@ -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