]> git.mxchange.org Git - flightgear.git/blob - src/Sound/voice.cxx
Interim windows build fix
[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         _voices.push_back(new FGFestivalVoice(this, voice));
97         continue;
98       } catch (const std::string& ) {
99         SG_LOG(SG_SOUND, SG_WARN, "failed to create festival voice, falling back to flite voice" );
100       }
101     }
102 #if defined(ENABLE_FLITE)
103     SG_LOG(SG_SOUND,SG_INFO,"creating flite voice" );
104     _voices.push_back(new FGFLITEVoice(this, voice));
105 #else
106     SG_LOG(SG_SOUND,SG_ALERT,"non festival voice not supported." );
107 #endif
108   }
109
110 #if defined(ENABLE_THREADS)
111         _thread->setProcessorAffinity(1);
112         _thread->start();
113 #endif
114 }
115
116 void FGVoiceMgr::shutdown()
117 {
118 #if defined(ENABLE_THREADS)
119   if( _thread ) {
120     _thread->cancel();
121     _thread->join();
122     delete _thread;
123     _thread = NULL;
124   }
125 #endif
126
127   for( std::vector<FGVoice*>::iterator it = _voices.begin(); it != _voices.end(); ++it )
128     delete *it;
129 }
130
131
132 void FGVoiceMgr::update(double)
133 {
134         if (!_enabled)
135                 return;
136
137         _paused = !_pausedNode->getBoolValue();
138         for (unsigned int i = 0; i < _voices.size(); i++) {
139                 _voices[i]->update();
140 #if !defined(ENABLE_THREADS)
141                 _voices[i]->speak();
142 #endif
143         }
144 }
145
146
147
148
149 /// VOICE ///
150
151 FGFestivalVoice::FGFestivalVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
152   FGVoice(mgr),
153         _volumeNode(node->getNode("volume", true)),
154         _pitchNode(node->getNode("pitch", true)),
155         _speedNode(node->getNode("speed", true))
156 {
157         SG_LOG(SG_SOUND, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
158                         << "' voice");
159         const string &host = _mgr->_host;
160         const string &port = _mgr->_port;
161
162         _sock = new SGSocket(host, port, "tcp");
163         _sock->set_timeout(6000);
164         if (!_sock->open(SG_IO_OUT))
165                 throw string("no connection to `") + host + ':' + port + '\'';
166
167         {
168                 _sock->writestring("(SayText \"\")\015\012");
169                 char buf[4];
170                 int len = _sock->read(buf, 3);
171                 if (len != 3 || buf[0] != 'L' || buf[1] != 'P')
172                         throw string("unexpected or no response from `") + host + ':' + port
173                                         + "'. Either it's not\n       Festival listening,"
174                                         " or Festival couldn't open a sound device.";
175
176                 SG_LOG(SG_SOUND, SG_INFO, "VOICE: connection to Festival server on `"
177                                 << host << ':' << port << "' established");
178
179                 setVolume(_volume = _volumeNode->getDoubleValue());
180                 setPitch(_pitch = _pitchNode->getDoubleValue());
181                 setSpeed(_speed = _speedNode->getDoubleValue());
182         }
183
184         string preamble = node->getStringValue("preamble", "");
185         if (!preamble.empty())
186                 pushMessage(preamble);
187   node->getNode("text", true)->addChangeListener(this);
188 }
189
190
191 FGFestivalVoice::~FGFestivalVoice()
192 {
193         _sock->close();
194         delete _sock;
195 }
196
197
198 void FGVoiceMgr::FGVoice::pushMessage( const string & m)
199 {
200         _msg.push(m);
201 #if defined(ENABLE_THREADS)
202         _mgr->_thread->wake_up();
203 #endif
204 }
205
206 bool FGVoiceMgr::FGVoice::speak(void)
207 {
208   if (_msg.empty())
209     return false;
210
211   const string s = _msg.front();
212   _msg.pop();
213
214   speak(s);
215
216   return !_msg.empty();
217 }
218
219 void FGFestivalVoice::speak( const string & msg )
220 {
221   if( msg.empty() )
222     return;
223
224   string s;
225
226   if( msg[0] == '(' ) {
227     s = msg;
228   } else {
229     s.append("(SayText \"");
230     s.append(msg).append("\")");
231   }
232
233   s.append("\015\012");
234         _sock->writestring(s.c_str());
235 }
236
237
238 void FGFestivalVoice::update(void)
239 {
240                 double d;
241                 d = _volumeNode->getDoubleValue();
242                 if (d != _volume)
243                         setVolume(_volume = d);
244                 d = _pitchNode->getDoubleValue();
245                 if (d != _pitch)
246                         setPitch(_pitch = d);
247                 d = _speedNode->getDoubleValue();
248                 if (d != _speed)
249                         setSpeed(_speed = d);
250 }
251
252
253 void FGFestivalVoice::setVolume(double d)
254 {
255         std::ostringstream s;
256         s << "(set! default_after_synth_hooks (list (lambda (utt)"
257                         "(utt.wave.rescale utt " << d << " t))))";
258         pushMessage(s.str());
259 }
260
261
262 void FGFestivalVoice::setPitch(double d)
263 {
264         std::ostringstream s;
265         s << "(set! int_lr_params '((target_f0_mean " << d <<
266                         ")(target_f0_std 14)(model_f0_mean 170)"
267                         "(model_f0_std 34)))";
268         pushMessage(s.str());
269 }
270
271
272 void FGFestivalVoice::setSpeed(double d)
273 {
274         std::ostringstream s;
275         s << "(Parameter.set 'Duration_Stretch " << d << ')';
276         pushMessage(s.str());
277 }
278
279 /// THREAD ///
280
281 #if defined(ENABLE_THREADS)
282 void FGVoiceMgr::FGVoiceThread::run(void)
283 {
284         while (1) {
285                 bool busy = false;
286                 for (unsigned int i = 0; i < _mgr->_voices.size(); i++)
287                         busy |= _mgr->_voices[i]->speak();
288
289                 if (!busy)
290                         wait_for_jobs();
291         }
292 }
293 #endif
294
295
296
297
298 /// LISTENER ///
299
300 void FGVoiceMgr::FGVoice::valueChanged(SGPropertyNode *node)
301 {
302         if (_mgr->_paused)
303                 return;
304
305         const string s = node->getStringValue();
306         //cerr << "\033[31;1mBEFORE [" << s << "]\033[m" << endl;
307
308         string m;
309         for (unsigned int i = 0; i < s.size(); i++) {
310                 char c = s[i];
311                 if (c == '\n' || c == '\r' || c == '\t')
312                         m += ' ';
313                 else if (!isprint(c))
314                         continue;
315                 else if (c == '\\' || c == '"')
316                         m += '\\', m += c;
317                 else if (c == '|' || c == '_')
318                         m += ' ';       // don't say "vertical bar" or "underscore"
319                 else if (c == '&')
320                         m += " and ";
321         else if (c == '>' || c == '<')
322             m += ' ';      // don't say "greater than" or "less than" either
323                 else if (c == '{') {
324                         while (i < s.size())
325                                 if (s[++i] == '|')
326                                         break;
327                 } else if (c == '}')
328                         ;
329                 else
330                         m += c;
331         }
332         //cerr << "\033[31;1mAFTER [" << m << "]\033[m" << endl;
333
334         pushMessage(m);
335 }
336
337