speech
All Data Structures Functions Modules Pages
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 
99 using namespace std;
100 using namespace yarp::os;
101 
102 
103 /************************************************************************/
104 class 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 
175 public:
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 /************************************************************************/
236 class 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 
411 public:
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 /************************************************************************/
462 class Launcher: public RFModule
463 {
464 protected:
465  iSpeak speaker;
466  RpcServer rpc;
467 
468 public:
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 /************************************************************************/
542 int 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 }