]> git.mxchange.org Git - flightgear.git/blob - src/Sound/voice.cxx
VoiceSynthesizer: add some test/debug properties
[flightgear.git] / src / Sound / voice.cxx
1 // speech synthesis interface subsystem
2 //
3 // Written by Melchior FRANZ, started February 2006.
4 //
5 // Copyright (C) 2006  Melchior FRANZ - mfranz@aon.at
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26
27 #include <Main/globals.hxx>
28 #include <sstream>
29 #include <simgear/compiler.h>
30 #include <Main/fg_props.hxx>
31 #include "voice.hxx"
32
33 #if defined(ENABLE_FLITE)
34 #include "flitevoice.hxx"
35 #endif
36
37 #define VOICE "/sim/sound/voices"
38
39 using std::string;
40
41 class FGFestivalVoice : public FGVoiceMgr::FGVoice {
42 public:
43   FGFestivalVoice(FGVoiceMgr *, const SGPropertyNode_ptr);
44   virtual ~FGFestivalVoice();
45   virtual void speak( const string & msg );
46   virtual void update();
47   void setVolume(double);
48   void setPitch(double);
49   void setSpeed(double);
50
51 private:
52   SGSocket *_sock;
53   double _volume;
54   double _pitch;
55   double _speed;
56   SGPropertyNode_ptr _volumeNode;
57   SGPropertyNode_ptr _pitchNode;
58   SGPropertyNode_ptr _speedNode;
59 };
60
61 /// MANAGER ///
62
63 FGVoiceMgr::FGVoiceMgr() :
64 #if defined(ENABLE_THREADS)
65   _thread(NULL),
66 #endif
67         _host(fgGetString(VOICE "/host", "localhost")),
68         _port(fgGetString(VOICE "/port", "1314")),
69         _enabled(fgGetBool(VOICE "/enabled", false)),
70         _pausedNode(fgGetNode("/sim/sound/working", true)),
71         _paused(false)
72 {
73 }
74
75
76 FGVoiceMgr::~FGVoiceMgr()
77 {
78 }
79
80
81 void FGVoiceMgr::init()
82 {
83         if (!_enabled)
84                 return;
85
86 #if defined(ENABLE_THREADS)
87   _thread = new FGVoiceThread(this);
88 #endif
89
90         SGPropertyNode *base = fgGetNode(VOICE, true);
91         vector<SGPropertyNode_ptr> voices = base->getChildren("voice");
92   for (unsigned int i = 0; i < voices.size(); i++) {
93     SGPropertyNode_ptr voice = voices[i];
94     if( voice->getBoolValue("festival", false ) ) {
95       try {
96         SG_LOG(SG_ALL,SG_ALERT,"creating festival voice" );
97         _voices.push_back(new FGFestivalVoice(this, voice));
98       } catch (const std::string& s) {
99         SG_LOG(SG_SOUND, SG_ALERT, "VOICE: " << s);
100       }
101     } else {
102 #if defined(ENABLE_FLITE)
103       SG_LOG(SG_ALL,SG_ALERT,"creating flite voice" );
104       _voices.push_back(new FGFLITEVoice(this, voice));
105 #else
106       SG_LOG(SG_ALL,SG_ALERT,"non festival voice not supported." );
107 #endif
108     }
109   }
110
111 #if defined(ENABLE_THREADS)
112         _thread->setProcessorAffinity(1);
113         _thread->start();
114 #endif
115 }
116
117 void FGVoiceMgr::shutdown()
118 {
119 #if defined(ENABLE_THREADS)
120   SG_LOG(SG_ALL,SG_ALERT,"FGVoiceMgr::shutdown");
121   if( _thread ) {
122     _thread->cancel();
123     _thread->join();
124     delete _thread;
125     _thread = NULL;
126   }
127 #endif
128
129   for( std::vector<FGVoice*>::iterator it = _voices.begin(); it != _voices.end(); ++it )
130     delete *it;
131 }
132
133
134 void FGVoiceMgr::update(double)
135 {
136         if (!_enabled)
137                 return;
138
139         _paused = !_pausedNode->getBoolValue();
140         for (unsigned int i = 0; i < _voices.size(); i++) {
141                 _voices[i]->update();
142 #if !defined(ENABLE_THREADS)
143                 _voices[i]->speak();
144 #endif
145         }
146 }
147
148
149
150
151 /// VOICE ///
152
153 FGFestivalVoice::FGFestivalVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
154   FGVoice(mgr),
155         _volumeNode(node->getNode("volume", true)),
156         _pitchNode(node->getNode("pitch", true)),
157         _speedNode(node->getNode("speed", true))
158 {
159         SG_LOG(SG_SOUND, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
160                         << "' voice");
161         const string &host = _mgr->_host;
162         const string &port = _mgr->_port;
163
164         _sock = new SGSocket(host, port, "tcp");
165         _sock->set_timeout(6000);
166         if (!_sock->open(SG_IO_OUT))
167                 throw string("no connection to `") + host + ':' + port + '\'';
168
169         {
170                 _sock->writestring("(SayText \"\")\015\012");
171                 char buf[4];
172                 int len = _sock->read(buf, 3);
173                 if (len != 3 || buf[0] != 'L' || buf[1] != 'P')
174                         throw string("unexpected or no response from `") + host + ':' + port
175                                         + "'. Either it's not\n       Festival listening,"
176                                         " or Festival couldn't open a sound device.";
177
178                 SG_LOG(SG_SOUND, SG_INFO, "VOICE: connection to Festival server on `"
179                                 << host << ':' << port << "' established");
180
181                 setVolume(_volume = _volumeNode->getDoubleValue());
182                 setPitch(_pitch = _pitchNode->getDoubleValue());
183                 setSpeed(_speed = _speedNode->getDoubleValue());
184         }
185
186         string preamble = node->getStringValue("preamble", "");
187         if (!preamble.empty())
188                 pushMessage(preamble);
189   node->getNode("text", true)->addChangeListener(this);
190 }
191
192
193 FGFestivalVoice::~FGFestivalVoice()
194 {
195         _sock->close();
196         delete _sock;
197 }
198
199
200 void FGVoiceMgr::FGVoice::pushMessage( const string & m)
201 {
202         _msg.push(m);
203 #if defined(ENABLE_THREADS)
204         _mgr->_thread->wake_up();
205 #endif
206 }
207
208 bool FGVoiceMgr::FGVoice::speak(void)
209 {
210   if (_msg.empty())
211     return false;
212
213   const string s = _msg.front();
214   _msg.pop();
215
216   speak(s);
217
218   return !_msg.empty();
219 }
220
221 void FGFestivalVoice::speak( const string & msg )
222 {
223   if( msg.empty() )
224     return;
225
226   string s;
227
228   if( msg[0] == '(' ) {
229     s = msg;
230   } else {
231     s.append("(SayText \"");
232     s.append(msg).append("\")");
233   }
234
235   s.append("\015\012");
236         _sock->writestring(s.c_str());
237 }
238
239
240 void FGFestivalVoice::update(void)
241 {
242                 double d;
243                 d = _volumeNode->getDoubleValue();
244                 if (d != _volume)
245                         setVolume(_volume = d);
246                 d = _pitchNode->getDoubleValue();
247                 if (d != _pitch)
248                         setPitch(_pitch = d);
249                 d = _speedNode->getDoubleValue();
250                 if (d != _speed)
251                         setSpeed(_speed = d);
252 }
253
254
255 void FGFestivalVoice::setVolume(double d)
256 {
257         std::ostringstream s;
258         s << "(set! default_after_synth_hooks (list (lambda (utt)"
259                         "(utt.wave.rescale utt " << d << " t))))";
260         pushMessage(s.str());
261 }
262
263
264 void FGFestivalVoice::setPitch(double d)
265 {
266         std::ostringstream s;
267         s << "(set! int_lr_params '((target_f0_mean " << d <<
268                         ")(target_f0_std 14)(model_f0_mean 170)"
269                         "(model_f0_std 34)))";
270         pushMessage(s.str());
271 }
272
273
274 void FGFestivalVoice::setSpeed(double d)
275 {
276         std::ostringstream s;
277         s << "(Parameter.set 'Duration_Stretch " << d << ')';
278         pushMessage(s.str());
279 }
280
281 /// THREAD ///
282
283 #if defined(ENABLE_THREADS)
284 void FGVoiceMgr::FGVoiceThread::run(void)
285 {
286         while (1) {
287                 bool busy = false;
288                 for (unsigned int i = 0; i < _mgr->_voices.size(); i++)
289                         busy |= _mgr->_voices[i]->speak();
290
291                 if (!busy)
292                         wait_for_jobs();
293         }
294 }
295 #endif
296
297
298
299
300 /// LISTENER ///
301
302 void FGVoiceMgr::FGVoice::valueChanged(SGPropertyNode *node)
303 {
304         if (_mgr->_paused)
305                 return;
306
307         const string s = node->getStringValue();
308         //cerr << "\033[31;1mBEFORE [" << s << "]\033[m" << endl;
309
310         string m;
311         for (unsigned int i = 0; i < s.size(); i++) {
312                 char c = s[i];
313                 if (c == '\n' || c == '\r' || c == '\t')
314                         m += ' ';
315                 else if (!isprint(c))
316                         continue;
317                 else if (c == '\\' || c == '"')
318                         m += '\\', m += c;
319                 else if (c == '|' || c == '_')
320                         m += ' ';       // don't say "vertical bar" or "underscore"
321                 else if (c == '&')
322                         m += " and ";
323         else if (c == '>' || c == '<')
324             m += ' ';      // don't say "greater than" or "less than" either
325                 else if (c == '{') {
326                         while (i < s.size())
327                                 if (s[++i] == '|')
328                                         break;
329                 } else if (c == '}')
330                         ;
331                 else
332                         m += c;
333         }
334         //cerr << "\033[31;1mAFTER [" << m << "]\033[m" << endl;
335
336         pushMessage(m);
337 }
338
339