[ previous ] [ next ] [ threads ]
 To :  yate@v...
 From :  Maciek Kaminski <maciejka@t...>
 Subject :  Module to interface with fastival tts engine.
 Date :  Mon, 25 Apr 2005 13:07:16 +0200
Small module for festival/tts in yate 0.8.*.

Maciek Kaminski



;It needs festival_server to be run.
[general]
host=127.0.0.1
port=1314
default_voice=voice_cstr_pl_em_diphone



/**
 * moh.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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


using namespace TelEngine;

static Configuration s_cfg;

static Mutex mutex(true);

class TTSChan : public DataEndpoint
{
public:
  TTSChan(const String& text, const String& voice, Message *m = NULL);
  ~TTSChan();
  inline const String &id() const
  { return m_id; }
  String m_id;
private:
  static int s_nextid;
};


class TTSSource : public ThreadedSource
{
public:
  TTSSource(const String &text, const String &voice, DataEndpoint *m_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:
  DataEndpoint *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, DataEndpoint *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(DebugAll,"TTSSource::TTSSource(\"\n%s\") [%p]", m_command.c_str(), this);
}

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

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

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

  int sock = socket(AF_INET, SOCK_STREAM, 0);
  dest_addr.sin_family = AF_INET;       
  dest_addr.sin_port = htons(port);
  dest_addr.sin_addr.s_addr = inet_addr(host);
  Debug(DebugAll,"Connecting to festival server at: %s/%d", host, port);
  if(connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr))>=0) {
    Debug(DebugAll,"Connected to festival server at: %s/%d", host, port);
  } else {
    Debug(DebugWarn, "Failed to connect to festival server at %s/%d: %s", host, port, strerror(errno));
    ::close(sock);
    m_chan->setSource();
    return;
  }

  unsigned int written= 0;
  int sent = 0;
  for(; written < m_command.length(); written += sent) {
    Debug(DebugAll, "Writing to festival server:\n%s", m_command.c_str() + written);
    sent = ::write(sock, m_command.c_str() + written, m_command.length() - written);
    if(sent==-1){
      Debug(DebugWarn, "Failed to write command(%s) to festival server: %s",  m_command.c_str(), strerror(errno));
      ::close(sock);
      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
  while(s_wv_pattern[wv_i]!='' && s_er_pattern[er_i]!='') {
    r = ::read(sock, buf, buf_size);
    if (r==-1) {
      Debug(DebugWarn, "Failed to read from festival server at %s/%d: %s", host, port, strerror(errno));
      ::close(sock);
      m_chan->setSource();
      return;
    }
    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]=='') {
    ::close(sock);
    m_chan->setSource();
    Debug(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);  

  data.assign(0,(m_brate*20)/1000);
  int fe_i = 0;
  do {
    r = ::read(sock, data.data(), data.length());
    if (!r) {
      Debug(DebugWarn, "Failed to read from festival server at %s/%d: %s", host, port, strerror(errno));
      ::close(sock);
      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);
      ::usleep((unsigned long)dly);
    }
    Forward(data, data.length()*8000/m_brate);  
    tpos += (r*1000000ULL/m_brate);
  } while(s_festival_pattern[fe_i]!='');
  Debug(DebugAll,"TTSSource::~TTSSource()");
  ::close(sock);
  m_chan->setSource();

}

TTSSource::~TTSSource()
{
    Lock lock(mutex);
    Debug(DebugAll,"TTSSource::~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("eof");
}

class TTSHandler : public MessageHandler
{
public:
    TTSHandler() : MessageHandler("call.execute") { }
    virtual bool received(Message &msg);
};


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

class StatusHandler : public MessageHandler
{
public:
    StatusHandler() : MessageHandler("engine.status") { }
    virtual bool received(Message &msg);
};

class TTSPlugin : public Plugin
{
public:
    TTSPlugin();
    ~TTSPlugin();
    virtual void initialize();
private:
    TTSHandler *m_handler;
};

int TTSChan::s_nextid = 1;

TTSChan::TTSChan(const String &text, const String &voice, Message *m)
    : DataEndpoint("tts")
{
    Debug(DebugAll,"TTSChan::TTSChan(\"%s\") [%p]",text.c_str(),this);
    Lock lock(mutex);
    m_id << "tts/" << s_nextid++;
    if(m) {
      m->addParam("id", m_id);;
    }
    TTSSource *s = new TTSSource(text, voice, this, true, m);    
    setSource(s);
    s->deref();
}

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


bool TTSHandler::received(Message &msg)
{
    String dest(msg.getValue("callto"));
    String voice;
    String text;
    if (dest.null())
	return false;

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

    DataEndpoint *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->m_id);
	    tc->deref();
	    return true;
	} else {
	    tc->destruct();
	    return false;
	}
    } else {
	const char *targ = msg.getValue("target");
	if (!targ) {
	    Debug(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(DebugWarn,"TTS outgoing call not accepted!");
	    tc->destruct();
	}
	else
	    Debug(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(DebugAll,"Setting voice: %s", voice.c_str());
            src = src.matchString(1);
	}
    } else {
      voice = src.matchString(1);
      voice = "voice" + voice;
      Debug(DebugAll,"Setting voice: %s", voice.c_str());
      src = src.matchString(2);
    }

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

bool StatusHandler::received(Message &msg)
{
    const char *sel = msg.getValue("module");
    if (sel && ::strcmp(sel,"tts"))
	return false;
    msg.retValue() << "name=tts\n";
    return false;
}

TTSPlugin::TTSPlugin()
    : m_handler(0)
{
    Output("Loaded module TTS");
}

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

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

INIT_PLUGIN(TTSPlugin);

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