[ previous ] [ next ] [ threads ]
 To :  yate@v...
 From :  Maciek Kaminski <maciejka@t...>
 Subject :  tts module
 Date :  Mon, 04 Jul 2005 09:57:34 +0200
TTS module. It is an interface to Festival TTS engine(http://www.cstr.ed.ac.uk/projects/festival/). Festival server should be started manualy(festival_server). It accepts two forms of source descriptions: 
tts/Hello, Welcome to yate 
than default voice is used and: 
tts/voice_kal_diphone/Hello, Welcome to yate
than voice kal_diphone is used.

mk





/**
 * tts.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Interface to Festival TTS engine
 *
 * Yet Another Telephony Engine - a fully featured software PBX and IVR
 * Copyright (C) 2005 Maciek Kaminski (maciejka_at_tiger.com.pl)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/** 
 * Interface to Festival TTS engine. 
 * Festival server should be started manualy. It accepts two forms of source descriptions:
 * tts/Hello, Welcome to yate 
 * than default voice is used
 * and: 
 * tts/voice_kal_diphone/Hello, Welcome to yate
 * than voice kal_diphone is used.
 * by Maciek Kaminski (maciejka_at_tiger.com.pl)
 */

/** 
 * 
 */

#include 

#include 
#include 
#include 
#include 
#include 
#include 

using namespace TelEngine;

static Configuration s_cfg;

class TTSChan : public Channel
{
public:
  TTSChan(const String& text, const String& voice, Message *m = NULL);
  ~TTSChan();
};


class TTSSource : public ThreadedSource
{
public:
  TTSSource(const String &text, const String &voice, CallEndpoint* chan, bool autoclose = true, Message *answered = NULL);
  ~TTSSource();
  virtual void run();
  inline void setNotify(const String& notify) { m_notify = notify; }
  inline void setTarget(const String& targetid) { m_targetid = targetid; }
private:
  CallEndpoint *m_chan;
  String m_command;
  unsigned m_brate;
  String m_notify;
  String m_targetid;
  bool m_autoclose;
  Message *m_answered;
  static char* s_wv_pattern;
  static char* s_er_pattern;
  static char* s_festival_pattern;
};

char* TTSSource::s_wv_pattern = "WV\n";
char* TTSSource::s_er_pattern = "ER\n";
char* TTSSource::s_festival_pattern = "ft_StUfF_keyLP\n";

TTSSource::TTSSource(const String &text, const String &voice,  CallEndpoint* chan, bool autoclose, Message *answered)
  : ThreadedSource("mulaw"), m_chan(chan), m_command(""), m_brate(8000), m_autoclose(autoclose), m_answered(answered)
{
  String c1("(Parameter.set 'Wavefiletype 'ulaw)\n(tts_return_to_client)\n(tts_text \"");
  String c2("\" \"\")\n");
  if(voice)
    m_command += "(" + voice + ")\n" + c1 + text + c2;
  else
    m_command += c1 + text + c2;
  start("WaveSource");
  Debug("TTSModule", DebugAll,"TTSSource(\"%s\") [%p]", text.c_str(), this);
  XDebug("TTSModule", DebugAll,"TTS command:\n%s", m_command.c_str());
}

void TTSSource::run()
{
  if(m_answered) {
    Engine::enqueue(m_answered);
  }

  const char *host = c_safe(s_cfg.getValue("general","host","127.0.0.1"));
  int port = s_cfg.getIntValue("general", "port", 1314);

  Debug("TTSModule", DebugAll,"Starting tts source...");

  Socket sock(AF_INET, SOCK_STREAM);

  SocketAddr dest(AF_INET);
  dest.host(host);
  dest.port(port);

  Debug("TTSModule", DebugAll,"Connecting to festival server at: %s/%d", host, port);
  if(sock.connect(dest)) {
    Debug("TTSModule", DebugAll,"Connected to festival server at: %s/%d", host, port);
  } else {
    Debug("TTSModule", DebugWarn, "Failed to connect to festival server at %s/%d: %s", host, port, strerror(errno));
    sock.terminate();
    m_chan->setSource();
    return;
  }

  unsigned int written= 0;
  int sent = 0;
  for(; written < m_command.length(); written += sent) {
    XDebug("TTSModule", DebugAll, "Writing to festival server:\n%s", m_command.c_str() + written);
    sent = sock.writeData(m_command.c_str() + written, m_command.length() - written);
    if(!sent){
      Debug("TTSModule", DebugWarn, "Failed to write command(%s) to festival server.", m_command.c_str());  //, strerror(errno))
      sock.terminate();
      m_chan->setSource();
      return;
    }
  }

  int buf_size = 255;
  char buf[255];
  int wv_i = 0;
  int er_i = 0;
  int r = 0;
  int i = 0;
  //reading festival response, stoping on s_wv_pattern || s_er_pattern
  Debug("TTSModule", DebugAll, "Reading festival response...");
  while(s_wv_pattern[wv_i]!='' && s_er_pattern[er_i]!='') {
    r = sock.readData(buf, buf_size);
    if (!r) {
      Debug("TTSModule", DebugWarn, "Failed to read from festival server at %s/%d", host, port); //, strerror(errno))
      sock.terminate();
      m_chan->setSource();
      return;
    }
  
    String readd(buf, r);
    XDebug("TTSModule", DebugAll, "read: %s", readd.c_str());
    for(i = 0; i < r; i++) {
      if(buf[i] == s_wv_pattern[wv_i]) {
	wv_i++;
	if(s_wv_pattern[wv_i]=='') {
	  break;
	}
      } else {
	wv_i = 0;
      } 
      if(buf[i] == s_er_pattern[er_i]) {
	er_i++;
	if(s_er_pattern[er_i]=='') {
	  break;
	}
      } else {
	er_i = 0;
      }
    }
  }

  if(s_er_pattern[er_i]=='') {
    sock.terminate();
    m_chan->setSource();
    Debug("TTSModule", DebugWarn, "Error while communicating with festival: %s", buf);
    return;
  }

  DataBlock data(buf + i, r - i - 1);

  unsigned long long tpos = Time::now();

  if(r - i - 1 > 0)
      Forward(data, data.length()*8000/m_brate);  

  XDebug("TTSModule", DebugAll, "Forwarding data...");

  data.assign(0,(m_brate*20)/1000);
  int fe_i = 0;
  do {
    r = sock.readData(data.data(), data.length());
    if (!r) {
      Debug("TTSModule", DebugWarn, "Failed to read from festival server at %s/%d: %s", host, port, strerror(errno));
      sock.terminate();
      m_chan->setSource();
      return;
    }
    for(int i = 0; i < r; i++) {
      if(((char *)data.data())[i] == s_festival_pattern[fe_i]) {
        fe_i++;
	if(s_festival_pattern[fe_i]=='') {
          data.truncate(i - strlen(s_festival_pattern) - 1);
	  break;
	}
      } else {
        fe_i = 0;
      }
    }
    long long dly = tpos - Time::now();
    if (dly > 0) {
      XDebug("TTSSource",DebugAll,"Sleeping for %lld usec", dly);
      Thread::usleep((unsigned long)dly);
    }
    Forward(data, data.length()*8000/m_brate);  
    tpos += (r*1000000ULL/m_brate);
  } while(s_festival_pattern[fe_i]!='');
  Debug("TTSModule", DebugAll,"TTSSource::~TTSSource()");
  sock.terminate();
  m_chan->setSource();

}

TTSSource::~TTSSource()
{
    Debug("TTSModule", DebugAll,"~TTSSource() [%p]",this);
    if(m_notify) {
      Message *m = new Message("chan.notify");
      m->addParam("targetid", m_notify);
      m->addParam("notifier","source");
      m->userData(m_chan);
      Engine::enqueue(m);
    }
    if(m_autoclose)
        m_chan->disconnect();
}

class AttachHandler : public MessageHandler
{
public:
    AttachHandler() : MessageHandler("chan.attach") { }
    virtual bool received(Message &msg);
};

class TTSDriver : public Driver
{
public:
    TTSDriver();
    ~TTSDriver();
    virtual void initialize();
    virtual bool msgExecute(Message& msg, String& dest);
private:
    AttachHandler *m_attach_handler;
};

INIT_PLUGIN(TTSDriver);

TTSChan::TTSChan(const String &text, const String &voice, Message *m)
    : Channel(__plugin)
{
    Debug("TTSModule", DebugAll,"TTSChan(\"%s\") [%p]",text.c_str(),this);
    TTSSource *s = new TTSSource(text, voice, this, true, m);    
    setSource(s);
    s->deref();
}

TTSChan::~TTSChan()
{
    Debug("TTSModule", DebugAll,"~TTSChan() %s [%p]",id().c_str(),this);
}


bool TTSDriver::msgExecute(Message& msg, String& dest)
{
    String voice;
    String text;
    if (dest.null())
	return false;

    Regexp r("^voice\([^/]*\)/\(.*\)$");
    if (!dest.matches(r)) {
      voice = s_cfg.getValue("general", "default_voice", "voice_kal_diphone");
      Debug("TTSModule", DebugAll,"Setting voice: %s", voice.c_str());
      text = dest;
    } else {
      voice = dest.matchString(1);
      voice = "voice" + voice;
      Debug("TTSModule", DebugAll,"Setting voice: %s", voice.c_str());
      text = dest.matchString(2);
    }

    Debug("TTSModule", DebugAll,"Text to say: %s", text.c_str());

    CallEndpoint *dd = static_cast(msg.userData());
    if (dd) {
        Message* m = new Message("call.answered");
        m->addParam("driver","dumb");
        m->addParam("targetid", *msg.getParam("id"));
	TTSChan *tc = new TTSChan(text, voice, m);
	if (dd->connect(tc)) {
	    msg.setParam("targetid", tc->id());
	    tc->deref();
	    return true;
	} else {
	    tc->destruct();
	    return false;
	}
    } else {
	const char *targ = msg.getValue("target");
	if (!targ) {
	    Debug("TTSModule", DebugWarn, "TTS outgoing call with no target!");
	    return false;
	}
	Message m("call.route");
	m.addParam("id",dest);
	m.addParam("caller",dest);
	m.addParam("called",targ);
	if (Engine::dispatch(m)) {
	    m = "call.execute";
	    m.addParam("callto",m.retValue());
	    m.retValue() = 0;
	    TTSChan *tc = new TTSChan(dest.matchString(1).c_str(), voice);
	    m.setParam("id",tc->id());
	    m.userData(tc);
	    if (Engine::dispatch(m)) {
		tc->deref();
		return true;
	    }
	    Debug("TTSModule", DebugWarn,"TTS outgoing call not accepted!");
	    tc->destruct();
	}
	else
	    Debug("TTSModule", DebugWarn,"TTS outgoing call but no route!");
	return false;
    }
    return true;
}

bool AttachHandler::received(Message &msg)
{
    String src(msg.getValue("source"));
    if (src.null())
	return false;

    String voice;

    Regexp r("^tts/voice\([^/]*\)/\(.*\)$");
    if (!src.matches(r)) {
        Regexp r2("^tts/\(.*\)$");
        if (!src.matches(r2)) {
            return false;
	} else {
            voice = s_cfg.getValue("general", "default_voice", "voice_kal_diphone");
            Debug("TTSModule", DebugAll,"Setting voice: %s", voice.c_str());
            src = src.matchString(1);
	}
    } else {
      voice = src.matchString(1);
      voice = "voice" + voice;
      Debug("TTSModule", DebugAll,"Setting voice: %s", voice.c_str());
      src = src.matchString(2);
    }

    CallEndpoint *dd = static_cast(msg.userData());
    if (dd) {
      TTSSource *s = new TTSSource(src, voice, dd, false);
      s->setNotify(msg.getValue("notify"));
      dd->setSource(s);
      s->deref();
      return true;
    }
    else {
       Debug("TTSModule", DebugWarn,"TTS '%s' attach request with no data channel!",src.c_str());
    }
    return false;
}

TTSDriver::TTSDriver()
    : Driver("tts", "misc"), m_attach_handler(0)
{
    Output("Loaded module TTS");
}

TTSDriver::~TTSDriver()
{
  Output("Unloading module TTS");
}

void TTSDriver::initialize()
{
    Output("Initializing module TTS");
    setup();
    s_cfg = Engine::configFile("tts");
    s_cfg.load();
    Engine::install(new AttachHandler);
}

/* vi: set ts=8 sw=4 sts=4 noet: */