]> git.mxchange.org Git - flightgear.git/blob - src/Sound/voice.cxx
0753733059031017e4b69259e89a56268aea90a7
[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 <sstream>
28 #include <simgear/compiler.h>
29 #include <Main/fg_props.hxx>
30 #include <Main/globals.hxx>
31 #include "voice.hxx"
32
33 #define VOICE "/sim/sound/voices"
34
35
36 /// MANAGER ///
37
38 FGVoiceMgr::FGVoiceMgr() :
39         _host(fgGetString(VOICE "/host", "localhost")),
40         _port(fgGetString(VOICE "/port", "1314")),
41         _enabled(fgGetBool(VOICE "/enabled", false)),
42         _pausedNode(fgGetNode("/sim/sound/pause", true))
43 {
44 #if defined(ENABLE_THREADS)
45         if (!_enabled)
46                 return;
47         _thread = new FGVoiceThread(this);
48 #endif
49 }
50
51
52 FGVoiceMgr::~FGVoiceMgr()
53 {
54 #if defined(ENABLE_THREADS)
55         if (!_enabled)
56                 return;
57         _thread->cancel();
58         _thread->join();
59 #endif
60 }
61
62
63 void FGVoiceMgr::init()
64 {
65         if (!_enabled)
66                 return;
67
68         SGPropertyNode *base = fgGetNode(VOICE, true);
69         vector<SGPropertyNode_ptr> voices = base->getChildren("voice");
70         for (unsigned int i = 0; i < voices.size(); i++)
71                 _voices.push_back(new FGVoice(this, voices[i]));
72
73 #if defined(ENABLE_THREADS)
74         _thread->start(1);
75 #endif
76 }
77
78
79 void FGVoiceMgr::update(double)
80 {
81         if (!_enabled)
82                 return;
83
84         _paused = _pausedNode->getBoolValue();
85         for (unsigned int i = 0; i < _voices.size(); i++) {
86                 _voices[i]->update();
87 #if !defined(ENABLE_THREADS)
88                 _voices[i]->speak();
89 #endif
90         }
91
92 }
93
94
95
96
97 /// VOICE ///
98
99 FGVoiceMgr::FGVoice::FGVoice(FGVoiceMgr *mgr, const SGPropertyNode_ptr node) :
100         _volumeNode(node->getNode("volume", true)),
101         _pitchNode(node->getNode("pitch", true)),
102         _speedNode(node->getNode("speed", true)),
103         _festival(node->getBoolValue("festival", true)),
104         _mgr(mgr)
105 {
106         SG_LOG(SG_IO, SG_INFO, "VOICE: adding `" << node->getStringValue("desc", "<unnamed>")
107                         << "' voice");
108         const string &host = _mgr->_host;
109         const string &port = _mgr->_port;
110
111         _sock = new SGSocket(host, port, "tcp");
112         _sock->set_timeout(10000);
113         _connected = _sock->open(SG_IO_OUT);
114         if (!_connected) {
115                 SG_LOG(SG_IO, SG_ALERT, "VOICE: no connection to `"
116                                 << host << ':' << port << '\'');
117                 return;
118         }
119
120         if (_festival) {
121                 _sock->writestring("(SayText \"\")\015\012");
122                 char buf[4];
123                 int len = _sock->read(buf, 3);
124                 if (len != 3 || buf[0] != 'L' || buf[1] != 'P') {
125                         SG_LOG(SG_IO, SG_ALERT, "VOICE: something is listening to "
126                                 << host << ':' << port << "', but it doesn't seem "
127                                 "to be Festival");
128                         _connected = false;
129                         return;
130                 }
131
132                 SG_LOG(SG_IO, SG_BULK, "VOICE: connection to Festival server on `"
133                                 << host << ':' << port << "' established");
134
135                 setVolume(_volume = _volumeNode->getDoubleValue());
136                 setPitch(_pitch = _pitchNode->getDoubleValue());
137                 setSpeed(_speed = _speedNode->getDoubleValue());
138         }
139
140         string preamble = node->getStringValue("preamble", "");
141         if (!preamble.empty())
142                 pushMessage(preamble);
143
144         node->getNode("text", true)->addChangeListener(new FGVoiceListener(this));
145 }
146
147
148 FGVoiceMgr::FGVoice::~FGVoice()
149 {
150         _sock->close();
151         delete _sock;
152 }
153
154
155 void FGVoiceMgr::FGVoice::pushMessage(string m)
156 {
157         _msg.push(m + "\015\012");
158 #if defined(ENABLE_THREADS)
159         _mgr->_thread->wake_up();
160 #endif
161 }
162
163
164 bool FGVoiceMgr::FGVoice::speak(void)
165 {
166         if (_msg.empty()) {
167 //              cerr << "<nothing to say>" << endl;
168                 return false;
169         }
170
171         const string s = _msg.front();
172         _msg.pop();
173 //      cerr << "POP " << s;
174         _sock->writestring(s.c_str());
175         return !_msg.empty();
176 }
177
178
179 void FGVoiceMgr::FGVoice::update(void)
180 {
181         if (_connected && _festival) {
182                 double d;
183                 d = _volumeNode->getDoubleValue();
184                 if (d != _volume)
185                         setVolume(_volume = d);
186                 d = _pitchNode->getDoubleValue();
187                 if (d != _pitch)
188                         setPitch(_pitch = d);
189                 d = _speedNode->getDoubleValue();
190                 if (d != _speed)
191                         setSpeed(_speed = d);
192         }
193 }
194
195
196 void FGVoiceMgr::FGVoice::setVolume(double d)
197 {
198         std::ostringstream s;
199         s << "(set! default_after_synth_hooks (list (lambda (utt)"
200                         "(utt.wave.rescale utt " << d << " t))))";
201         pushMessage(s.str());
202 }
203
204
205 void FGVoiceMgr::FGVoice::setPitch(double d)
206 {
207         std::ostringstream s;
208         s << "(set! int_lr_params '((target_f0_mean " << d <<
209                         ")(target_f0_std 14)(model_f0_mean 170)"
210                         "(model_f0_std 34)))";
211         pushMessage(s.str());
212 }
213
214
215 void FGVoiceMgr::FGVoice::setSpeed(double d)
216 {
217         std::ostringstream s;
218         s << "(Parameter.set 'Duration_Stretch " << d << ')';
219         pushMessage(s.str());
220 }
221
222
223
224
225 /// THREAD ///
226
227 #if defined(ENABLE_THREADS)
228 void FGVoiceMgr::FGVoiceThread::run(void)
229 {
230         while (1) {
231                 bool busy = false;
232                 for (unsigned int i = 0; i < _mgr->_voices.size(); i++)
233                         busy |= _mgr->_voices[i]->speak();
234
235                 if (!busy)
236                         wait_for_jobs();
237         }
238 }
239 #endif
240
241
242
243
244 /// LISTENER ///
245
246 void FGVoiceMgr::FGVoice::FGVoiceListener::valueChanged(SGPropertyNode *node)
247 {
248         if (_voice->_mgr->_paused)
249                 return;
250
251         const string s = node->getStringValue();
252 //      cerr << "PUSH " << s << endl;
253
254         string m;
255         for (unsigned int i = 0; i < s.size(); i++) {
256                 char c = s[i];
257                 if (!isprint(c))
258                         continue;
259                 else if (c == '"' || c == '\\')
260                         m += '\\' + c;
261                 else if (c == '|' || c == '_')
262                         m += ' ';       // don't say "vertical bar" or "underscore"
263                 else if (c == '&')
264                         m += " and ";
265                 else
266                         m += c;
267         }
268         if (_voice->_festival)
269                 m = string("(SayText \"") + m + "\")";
270
271         _voice->pushMessage(m);
272 }
273
274