speech
Loading...
Searching...
No Matches
main.cpp
1/*
2 * Copyright (C) 2015 iCub Facility - Istituto Italiano di Tecnologia
3 * Author: Ugo Pattacini
4 * email: ugo.pattacini@iit.it
5 * Permission is granted to copy, distribute, and/or modify this program
6 * under the terms of the GNU General Public License, version 2 or any
7 * later version published by the Free Software Foundation.
8 *
9 * A copy of the license can be found at
10 * http://www.robotcub.org/icub/license/gpl.txt
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 General
15 * Public License for more details
16*/
17
91#include <cstdlib>
92#include <mutex>
93#include <string>
94#include <deque>
95#include <algorithm>
96
97#include <yarp/os/all.h>
98
99using namespace std;
100using namespace yarp::os;
101
102
103/************************************************************************/
104class MouthHandler : public PeriodicThread
105{
106 string state;
107 RpcClient emotions,r1;
108 mutex mtx;
109 double t0, duration, equalize_time;
110 bool equalize;
111
112 /************************************************************************/
113 void send()
114 {
115 if (emotions.getOutputCount()>0)
116 {
117 Bottle cmd, reply;
118 cmd.addVocab32("set");
119 cmd.addVocab32("mou");
120 cmd.addVocab32(state);
121 emotions.write(cmd,reply);
122 }
123 }
124
125 /************************************************************************/
126 void run()
127 {
128 if (equalize)
129 {
130 if (equalize_time>0.)
131 Time::delay(equalize_time);
132 equalize=false;
133 }
134
135 mtx.lock();
136
137 if (state=="sur")
138 state="hap";
139 else
140 state="sur";
141
142 send();
143
144 mtx.unlock();
145
146 if (duration>=0.0)
147 if (Time::now()-t0>=duration)
148 suspend();
149 }
150
151 /************************************************************************/
152 bool threadInit()
153 {
154 equalize=true;
155 if (r1.getOutputCount()>0)
156 {
157 Bottle cmd,rep;
158 cmd.addVocab32("tstart");
159 r1.write(cmd,rep);
160 }
161
162 t0=Time::now();
163 return true;
164 }
165
166 /************************************************************************/
167 void threadRelease()
168 {
169 emotions.interrupt();
170 r1.interrupt();
171 emotions.close();
172 r1.close();
173 }
174
175public:
176 /************************************************************************/
177 MouthHandler() : PeriodicThread(1.0), duration(-1.0), equalize(false) { }
178
179 /************************************************************************/
180 void configure(ResourceFinder &rf)
181 {
182 string name=rf.find("name").asString();
183 equalize_time=rf.check("equalize_time",Value(0.)).asFloat64();
184 equalize_time=std::max(0.,equalize_time);
185 emotions.open("/"+name+"/emotions:o");
186 r1.open("/"+name+"/r1:rpc");
187
188 state="sur";
189 setPeriod((double)rf.check("period",Value(200)).asInt32()/1000.0);
190 }
191
192 /************************************************************************/
193 void setAutoSuspend(const double duration)
194 {
195 this->duration=duration;
196 }
197
198 /************************************************************************/
199 void resume()
200 {
201 equalize=true;
202 if (r1.getOutputCount()>0)
203 {
204 Bottle cmd,rep;
205 cmd.addVocab32("tstart");
206 r1.write(cmd,rep);
207 }
208
209 t0=Time::now();
210 PeriodicThread::resume();
211 }
212
213 /************************************************************************/
214 void suspend()
215 {
216 if (isSuspended())
217 return;
218
219 PeriodicThread::suspend();
220
221 lock_guard<mutex> lg(mtx);
222 state="hap";
223 send();
224
225 if (r1.getOutputCount()>0)
226 {
227 Bottle cmd,rep;
228 cmd.addVocab32("tstop");
229 r1.write(cmd,rep);
230 }
231 }
232};
233
234
235/************************************************************************/
236class iSpeak : protected BufferedPort<Bottle>,
237 public PeriodicThread,
238 public PortReport
239{
240 string name;
241 string package;
242 string package_options;
243 deque<Bottle> buffer;
244 mutex mtx;
245
246 bool speaking;
247 MouthHandler mouth;
248 RpcClient speechdev;
249 int initSpeechDev;
250
251 /************************************************************************/
252 void report(const PortInfo &info)
253 {
254 if (info.created && !info.incoming)
255 initSpeechDev++;
256 }
257
258 /************************************************************************/
259 void onRead(Bottle &request)
260 {
261 lock_guard<mutex> lg(mtx);
262 buffer.push_back(request);
263 speaking=true;
264 }
265
266 /************************************************************************/
267 bool threadInit()
268 {
269 open("/"+name);
270 useCallback();
271 return true;
272 }
273
274 /************************************************************************/
275 void threadRelease()
276 {
277 mouth.stop();
278 if (speechdev.asPort().isOpen())
279 speechdev.close();
280 interrupt();
281 close();
282 }
283
284 /************************************************************************/
285 void execSpeechDevOptions()
286 {
287 lock_guard<mutex> lg(mtx);
288 if (speechdev.getOutputCount()>0)
289 {
290 Bottle options(package_options);
291 for (int i=0; i<options.size(); i++)
292 {
293 if (Bottle *opt=options.get(i).asList())
294 {
295 Bottle cmd,rep;
296 cmd=*opt;
297 yInfo("Setting option: %s",cmd.toString().c_str());
298 speechdev.write(cmd,rep);
299 yInfo("Received reply: %s",rep.toString().c_str());
300 }
301 }
302 }
303 }
304
305 /************************************************************************/
306 void speak(const string &phrase)
307 {
308 lock_guard<mutex> lg(mtx);
309 if (speechdev.asPort().isOpen())
310 {
311 if (speechdev.getOutputCount()>0)
312 {
313 Bottle cmd,rep;
314 cmd.addString("say");
315 cmd.addString(phrase);
316 speechdev.write(cmd,rep);
317 }
318 }
319 else
320 {
321 string command("echo \"");
322 command+=phrase;
323 command+="\" | ";
324 command+=package;
325 command+=" ";
326
327 if (package=="festival")
328 command+="--tts ";
329
330 command+=package_options;
331 int ret=system(command.c_str());
332 }
333 }
334
335 /************************************************************************/
336 void run()
337 {
338 string phrase;
339 double time;
340 bool onlyMouth=false;
341 int rate=(int)(1000.0*mouth.getPeriod());
342 bool resetRate=false;
343 double duration=-1.0;
344
345 mtx.lock();
346 if (buffer.size()>0) // protect also the access to the size() method
347 {
348 Bottle request=buffer.front();
349 buffer.pop_front();
350
351 if (request.size()>0)
352 {
353 if (request.get(0).isString())
354 phrase=request.get(0).asString();
355 else if (request.get(0).isFloat64() || request.get(0).isInt32())
356 {
357 time=request.get(0).asFloat64();
358 onlyMouth=true;
359 }
360
361 if (request.size()>1)
362 {
363 if (request.get(1).isInt32())
364 {
365 int newRate=request.get(1).asInt32();
366 if (newRate>0)
367 {
368 mouth.setPeriod((double)newRate/1000.0);
369 resetRate=true;
370 }
371 }
372
373 if ((request.size()>2) && request.get(0).isString())
374 if (request.get(2).isFloat64() || request.get(2).isInt32())
375 duration=request.get(2).asFloat64();
376 }
377 }
378 }
379 mtx.unlock();
380
381 if (speaking)
382 {
383 mouth.setAutoSuspend(duration);
384 if (mouth.isSuspended())
385 mouth.resume();
386 else
387 mouth.start();
388
389 if (onlyMouth)
390 Time::delay(time);
391 else
392 speak(phrase);
393
394 mouth.suspend();
395 if (resetRate)
396 mouth.setPeriod((double)rate/1000.0);
397
398 lock_guard<mutex> lg(mtx);
399 if (buffer.size()==0)
400 speaking=false;
401 }
402
403 if (initSpeechDev>0)
404 {
405 yInfo("Setting options at connection time");
406 execSpeechDevOptions();
407 initSpeechDev=0;
408 }
409 }
410
411public:
412 /************************************************************************/
413 iSpeak() : PeriodicThread(0.2)
414 {
415 speaking=false;
416 initSpeechDev=0;
417 }
418
419 /************************************************************************/
420 void configure(ResourceFinder &rf)
421 {
422 name=rf.find("name").asString();
423 package=rf.find("package").asString();
424 package_options=rf.find("package_options").asString();
425
426 mouth.configure(rf);
427
428 if (package=="speech-dev")
429 {
430 speechdev.open("/"+name+"/speech-dev/rpc");
431 speechdev.setReporter(*this);
432 }
433
434 yInfo("iSpeak wraps around \"%s\" speech synthesizer",package.c_str());
435 yInfo("starting command-line options: \"%s\"",package_options.c_str());
436 }
437
438 /************************************************************************/
439 bool isSpeaking()
440 {
441 lock_guard<mutex> lg(mtx);
442 return speaking;
443 }
444
445 /************************************************************************/
446 string get_package_options() const
447 {
448 return package_options;
449 }
450
451 /************************************************************************/
452 void set_package_options(const string &package_options)
453 {
454 this->package_options=package_options;
455 if (speechdev.asPort().isOpen())
456 execSpeechDevOptions();
457 }
458};
459
460
461/************************************************************************/
462class Launcher: public RFModule
463{
464protected:
465 iSpeak speaker;
466 RpcServer rpc;
467
468public:
469 /************************************************************************/
470 bool configure(ResourceFinder &rf)
471 {
472 speaker.configure(rf);
473 if (!speaker.start())
474 return false;
475
476 string name=rf.find("name").asString();
477 rpc.open("/"+name+"/rpc");
478 attach(rpc);
479
480 return true;
481 }
482
483 /************************************************************************/
484 bool close()
485 {
486 rpc.close();
487 speaker.stop();
488 return true;
489 }
490
491 /************************************************************************/
492 bool respond(const Bottle &command, Bottle &reply)
493 {
494 int cmd0=command.get(0).asVocab32();
495 if (cmd0==Vocab32::encode("stat"))
496 {
497 reply.addString(speaker.isSpeaking()?"speaking":"quiet");
498 return true;
499 }
500
501 if (command.size()>1)
502 {
503 int cmd1=command.get(1).asVocab32();
504 if (cmd1==Vocab32::encode("opt"))
505 {
506 if (cmd0==Vocab32::encode("set"))
507 {
508 if (command.size()>2)
509 {
510 string cmd2=command.get(2).asString();
511 speaker.set_package_options(cmd2);
512 reply.addString("ack");
513 return true;
514 }
515 }
516 else if (cmd0==Vocab32::encode("get"))
517 {
518 reply.addString(speaker.get_package_options());
519 return true;
520 }
521 }
522 }
523
524 return RFModule::respond(command,reply);
525 }
526
527 /************************************************************************/
528 double getPeriod()
529 {
530 return 1.0;
531 }
532
533 /************************************************************************/
534 bool updateModule()
535 {
536 return true;
537 }
538};
539
540
541/************************************************************************/
542int main(int argc, char *argv[])
543{
544 Network yarp;
545 if (!yarp.checkNetwork())
546 {
547 yError("YARP server not available!");
548 return 1;
549 }
550
551 ResourceFinder rf;
552 rf.setDefault("name","iSpeak");
553 rf.setDefault("package","speech-dev");
554 rf.setDefault("package_options","");
555 rf.configure(argc,argv);
556
557 Launcher launcher;
558 return launcher.runModule(rf);
559}