FlightGear next
voice.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileName: voice.cxx
3 * SPDX-FileComment: speech synthesis interface subsystem
4 * SPDX-FileCopyrightText: Copyright (C) 2006 Melchior FRANZ - mfranz@aon.at
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#ifdef HAVE_CONFIG_H
9# include "config.h"
10#endif
11
12#include <Main/globals.hxx>
13#include <sstream>
14#include <simgear/compiler.h>
15#include <Main/fg_props.hxx>
16#include "voice.hxx"
17#include "flitevoice.hxx"
18
19#define VOICE "/sim/sound/voices"
20
21using std::string;
22
24public:
25 FGFestivalVoice(FGVoiceMgr *, const SGPropertyNode_ptr);
26 virtual ~FGFestivalVoice();
27 virtual void speak( const string & msg );
28 virtual void update(double);
29 void setVolume(double);
30 void setPitch(double);
31 void setSpeed(double);
32
33private:
34 SGSocket *_sock;
35 double _volume;
36 double _pitch;
37 double _speed;
38 SGPropertyNode_ptr _volumeNode;
39 SGPropertyNode_ptr _pitchNode;
40 SGPropertyNode_ptr _speedNode;
41};
42
44
46#if defined(ENABLE_THREADS)
47 _thread(NULL),
48#endif
49 _host(fgGetString(VOICE "/host", "localhost")),
50 _port(fgGetString(VOICE "/port", "1314")),
51 _enabled(fgGetBool(VOICE "/enabled", false)),
52 _pausedNode(fgGetNode("/sim/sound/working", true)),
53 _paused(false)
54{
55}
56
57
61
62
64{
65 if (!_enabled)
66 return;
67
68#if defined(ENABLE_THREADS)
69 _thread = new FGVoiceThread(this);
70#endif
71
72 SGPropertyNode *base = fgGetNode(VOICE, true);
73 std::vector<SGPropertyNode_ptr> voices = base->getChildren("voice");
74 for (unsigned int i = 0; i < voices.size(); i++) {
75 SGPropertyNode_ptr voice = voices[i];
76 if( voice->getBoolValue("festival", false ) ) {
77 try {
78 _voices.push_back(new FGFestivalVoice(this, voice));
79 continue;
80 } catch (const std::string& ) {
81 SG_LOG(SG_SOUND, SG_WARN, "failed to create festival voice, falling back to flite voice" );
82 }
83 }
84
85 _voices.push_back(new FGFLITEVoice(this, voice));
86 }
87
88#if defined(ENABLE_THREADS)
89 _thread->setProcessorAffinity(1);
90 _thread->start();
91#endif
92}
93
95{
96#if defined(ENABLE_THREADS)
97 if( _thread ) {
98 _thread->cancel();
99 _thread->join();
100 delete _thread;
101 _thread = NULL;
102 }
103#endif
104
105 for( std::vector<FGVoice*>::iterator it = _voices.begin(); it != _voices.end(); ++it )
106 delete *it;
107}
108
109
110void FGVoiceMgr::update(double dt)
111{
112 if (!_enabled)
113 return;
114
115 _paused = !_pausedNode->getBoolValue();
116 for (unsigned int i = 0; i < _voices.size(); i++) {
117 _voices[i]->update(dt);
118#if !defined(ENABLE_THREADS)
119 _voices[i]->speak();
120#endif
121 }
122}
123
124
125// Register the subsystem.
126SGSubsystemMgr::Registrant<FGVoiceMgr> registrantFGVoiceMgr(
127 SGSubsystemMgr::DISPLAY);
128
129
131
132FGFestivalVoice::FGFestivalVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
133 FGVoice(mgr),
134 _volumeNode(node->getNode("volume", true)),
135 _pitchNode(node->getNode("pitch", true)),
136 _speedNode(node->getNode("speed", true))
137{
138 SG_LOG(SG_SOUND, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
139 << "' voice");
140 const string &host = _mgr->_host;
141 const string &port = _mgr->_port;
142
143 _sock = new SGSocket(host, port, "tcp");
144 _sock->set_timeout(6000);
145 if (!_sock->open(SG_IO_OUT))
146 throw string("no connection to `") + host + ':' + port + '\'';
147
148 {
149 _sock->writestring("(+ 1 2)\015\012");
150 char buf[4];
151 int len = _sock->read(buf, 3);
152 if (len != 3 || buf[0] != 'L' || buf[1] != 'P')
153 throw string("unexpected or no response from `") + host + ':' + port
154 + "'. Either it's not\n Festival listening,"
155 " or Festival couldn't open a sound device.";
156
157 SG_LOG(SG_SOUND, SG_INFO, "VOICE: connection to Festival server on `"
158 << host << ':' << port << "' established");
159 }
160
161 const string preamble = node->getStringValue("preamble", "");
162 if (!preamble.empty()) {
163 pushMessage(preamble);
164 }
165
166 // set these after sending any preamble, since it may reset them
167 setVolume(_volume = _volumeNode->getDoubleValue());
168 setPitch(_pitch = _pitchNode->getDoubleValue());
169 setSpeed(_speed = _speedNode->getDoubleValue());
170
171 node->getNode("text", true)->addChangeListener(this);
172}
173
174
176{
177 _sock->close();
178 delete _sock;
179}
180
181
182void FGVoiceMgr::FGVoice::pushMessage( const string & m)
183{
184 _msg.push(m);
185#if defined(ENABLE_THREADS)
186 _mgr->_thread->wake_up();
187#endif
188}
189
191{
192 if (_msg.empty())
193 return false;
194
195 const string s = _msg.front();
196 _msg.pop();
197
198 speak(s);
199
200 return !_msg.empty();
201}
202
203void FGFestivalVoice::speak( const string & msg )
204{
205 if( msg.empty() )
206 return;
207
208 string s;
209
210 if( msg[0] == '(' ) {
211 s = msg;
212 } else {
213 s.append("(SayText \"");
214 s.append(msg).append("\")");
215 }
216
217 s.append("\015\012");
218 _sock->writestring(s.c_str());
219}
220
221
223{
224 double d;
225 d = _volumeNode->getDoubleValue();
226 if (d != _volume)
227 setVolume(_volume = d);
228 d = _pitchNode->getDoubleValue();
229 if (d != _pitch)
230 setPitch(_pitch = d);
231 d = _speedNode->getDoubleValue();
232 if (d != _speed)
233 setSpeed(_speed = d);
234}
235
236
238{
239 std::ostringstream s;
240 s << "(set! default_after_synth_hooks (list (lambda (utt)"
241 "(utt.wave.rescale utt " << d << " t))))";
242 pushMessage(s.str());
243}
244
245
247{
248 std::ostringstream s;
249 s << "(set! int_lr_params '((target_f0_mean " << d <<
250 ")(target_f0_std 14)(model_f0_mean 170)"
251 "(model_f0_std 34)))";
252 pushMessage(s.str());
253}
254
255
257{
258 std::ostringstream s;
259 s << "(Parameter.set 'Duration_Stretch " << d << ')';
260 pushMessage(s.str());
261}
262
264
265#if defined(ENABLE_THREADS)
266void FGVoiceMgr::FGVoiceThread::run(void)
267{
268 while (1) {
269 bool busy = false;
270 for (unsigned int i = 0; i < _mgr->_voices.size(); i++)
271 busy |= _mgr->_voices[i]->speak();
272
273 if (!busy)
274 wait_for_jobs();
275 }
276}
277#endif
278
279
280
281
283
284void FGVoiceMgr::FGVoice::valueChanged(SGPropertyNode *node)
285{
286 if (_mgr->_paused)
287 return;
288
289 const string s = node->getStringValue();
290 //cerr << "\033[31;1mBEFORE [" << s << "]\033[m" << endl;
291
292 string m;
293 for (unsigned int i = 0; i < s.size(); i++) {
294 char c = s[i];
295 if (c == '\n' || c == '\r' || c == '\t')
296 m += ' ';
297 else if (!isprint(c))
298 continue;
299 else if (c == '\\' || c == '"')
300 m += '\\', m += c;
301 else if (c == '|' || c == '_')
302 m += ' '; // don't say "vertical bar" or "underscore"
303 else if (c == '&')
304 m += " and ";
305 else if (c == '>' || c == '<')
306 m += ' '; // don't say "greater than" or "less than" either
307 else if (c == '{') {
308 while (i < s.size())
309 if (s[++i] == '|')
310 break;
311 } else if (c == '}')
312 ;
313 else
314 m += c;
315 }
316 //cerr << "\033[31;1mAFTER [" << m << "]\033[m" << endl;
317
318 pushMessage(m);
319}
#define i(x)
void setPitch(double)
Definition voice.cxx:246
void setSpeed(double)
Definition voice.cxx:256
void setVolume(double)
Definition voice.cxx:237
FGFestivalVoice(FGVoiceMgr *, const SGPropertyNode_ptr)
VOICE ///.
Definition voice.cxx:132
virtual void update(double)
Definition voice.cxx:222
virtual ~FGFestivalVoice()
Definition voice.cxx:175
FGVoice(FGVoiceMgr *mgr)
Definition voice.hxx:101
std::queue< std::string > _msg
Definition voice.hxx:116
FGVoiceMgr * _mgr
Definition voice.hxx:111
void pushMessage(const std::string &m)
Definition voice.cxx:182
virtual void speak(const std::string &msg)=0
void valueChanged(SGPropertyNode *node)
THREAD ///.
Definition voice.cxx:284
bool _enabled
Definition voice.hxx:71
friend class FGFestivalVoice
Definition voice.hxx:62
std::string _port
Definition voice.hxx:70
void update(double dt) override
Definition voice.cxx:110
~FGVoiceMgr()
Definition voice.cxx:58
std::string _host
Definition voice.hxx:69
void init() override
Definition voice.cxx:63
void shutdown() override
Definition voice.cxx:94
FGVoiceMgr()
MANAGER ///.
Definition voice.cxx:45
std::vector< FGVoice * > _voices
Definition voice.hxx:74
SGPropertyNode_ptr _pausedNode
Definition voice.hxx:72
bool _paused
Definition voice.hxx:73
std::string fgGetString(const char *name, const char *defaultValue)
Get a string value for a property.
Definition fg_props.cxx:556
bool fgGetBool(char const *name, bool def)
Get a bool value for a property.
Definition proptest.cpp:25
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
SGSubsystemMgr::Registrant< FGVoiceMgr > registrantFGVoiceMgr(SGSubsystemMgr::DISPLAY)
#define VOICE
Definition voice.cxx:19