You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1276 lines
45 KiB
1276 lines
45 KiB
/***************************************************** vim:set ts=4 sw=4 sts=4:
|
|
This contains the SpeechData class which is in charge of maintaining
|
|
all the data on the memory.
|
|
It maintains queues manages the text.
|
|
We could say that this is the common repository between the KTTSD class
|
|
(dcop service) and the Speaker class (speaker, loads plug ins, call plug in
|
|
functions)
|
|
-------------------
|
|
Copyright:
|
|
(C) 2002-2003 by José Pablo Ezequiel "Pupeno" Fernández <pupeno@kde.org>
|
|
(C) 2003-2004 by Olaf Schmidt <ojschmidt@kde.org>
|
|
(C) 2004-2005 by Gary Cramblitt <garycramblitt@comcast.net>
|
|
-------------------
|
|
Original author: José Pablo Ezequiel "Pupeno" Fernández
|
|
******************************************************************************/
|
|
|
|
/******************************************************************************
|
|
* *
|
|
* 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. *
|
|
* *
|
|
******************************************************************************/
|
|
|
|
// C++ includes.
|
|
#include <stdlib.h>
|
|
|
|
// TQt includes.
|
|
#include <tqregexp.h>
|
|
#include <tqpair.h>
|
|
#include <tqvaluelist.h>
|
|
#include <tqdom.h>
|
|
#include <tqfile.h>
|
|
|
|
// KDE includes.
|
|
#include <kdebug.h>
|
|
#include <tdeglobal.h>
|
|
#include <kstandarddirs.h>
|
|
#include <tdeapplication.h>
|
|
|
|
// KTTS includes.
|
|
#include "talkermgr.h"
|
|
#include "notify.h"
|
|
|
|
// SpeechData includes.
|
|
#include "speechdata.h"
|
|
#include "speechdata.moc"
|
|
|
|
// Set this to 1 to turn off filter support, including SBD as a plugin.
|
|
#define NO_FILTERS 0
|
|
|
|
/**
|
|
* Constructor
|
|
* Sets text to be stopped and warnings and messages queues to be autodelete.
|
|
*/
|
|
SpeechData::SpeechData(){
|
|
// kdDebug() << "Running: SpeechData::SpeechData()" << endl;
|
|
// The text should be stoped at the beggining (thread safe)
|
|
jobCounter = 0;
|
|
config = 0;
|
|
textJobs.setAutoDelete(true);
|
|
supportsHTML = false;
|
|
|
|
// Warnings queue to be autodelete (thread safe)
|
|
warnings.setAutoDelete(true);
|
|
|
|
// Messages queue to be autodelete (thread safe)
|
|
messages.setAutoDelete(true);
|
|
|
|
screenReaderOutput.jobNum = 0;
|
|
screenReaderOutput.text = "";
|
|
}
|
|
|
|
bool SpeechData::readConfig(){
|
|
// Load configuration
|
|
delete config;
|
|
//config = TDEGlobal::config();
|
|
config = new TDEConfig("kttsdrc");
|
|
|
|
// Set the group general for the configuration of KTTSD itself (no plug ins)
|
|
config->setGroup("General");
|
|
|
|
// Load the configuration of the text interruption messages and sound
|
|
textPreMsgEnabled = config->readBoolEntry("TextPreMsgEnabled", false);
|
|
textPreMsg = config->readEntry("TextPreMsg");
|
|
|
|
textPreSndEnabled = config->readBoolEntry("TextPreSndEnabled", false);
|
|
textPreSnd = config->readEntry("TextPreSnd");
|
|
|
|
textPostMsgEnabled = config->readBoolEntry("TextPostMsgEnabled", false);
|
|
textPostMsg = config->readEntry("TextPostMsg");
|
|
|
|
textPostSndEnabled = config->readBoolEntry("TextPostSndEnabled", false);
|
|
textPostSnd = config->readEntry("TextPostSnd");
|
|
keepAudio = config->readBoolEntry("KeepAudio", false);
|
|
keepAudioPath = config->readEntry("KeepAudioPath", locateLocal("data", "kttsd/audio/"));
|
|
|
|
// Notification (KNotify).
|
|
notify = config->readBoolEntry("Notify", false);
|
|
notifyExcludeEventsWithSound = config->readBoolEntry("ExcludeEventsWithSound", true);
|
|
loadNotifyEventsFromFile( locateLocal("config", "kttsd_notifyevents.xml"), true );
|
|
|
|
// KTTSMgr auto start and auto exit.
|
|
autoStartManager = config->readBoolEntry("AutoStartManager", false);
|
|
autoExitManager = config->readBoolEntry("AutoExitManager", false);
|
|
|
|
// Clear the pool of filter managers so that filters re-init themselves.
|
|
TQPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs );
|
|
for( ; it.current(); ++it )
|
|
{
|
|
PooledFilterMgr* pooledFilterMgr = it.current();
|
|
delete pooledFilterMgr->filterMgr;
|
|
delete pooledFilterMgr->talkerCode;
|
|
delete pooledFilterMgr;
|
|
}
|
|
m_pooledFilterMgrs.clear();
|
|
|
|
// Create an initial FilterMgr for the pool to save time later.
|
|
PooledFilterMgr* pooledFilterMgr = new PooledFilterMgr();
|
|
FilterMgr* filterMgr = new FilterMgr();
|
|
filterMgr->init(config, "General");
|
|
supportsHTML = filterMgr->supportsHTML();
|
|
pooledFilterMgr->filterMgr = filterMgr;
|
|
pooledFilterMgr->busy = false;
|
|
pooledFilterMgr->job = 0;
|
|
pooledFilterMgr->partNum = 0;
|
|
// Connect signals from FilterMgr.
|
|
connect (filterMgr, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilterMgrFinished()));
|
|
connect (filterMgr, TQT_SIGNAL(filteringStopped()), this, TQT_SLOT(slotFilterMgrStopped()));
|
|
m_pooledFilterMgrs.append(pooledFilterMgr);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Loads notify events from a file. Clearing data if clear is True.
|
|
*/
|
|
void SpeechData::loadNotifyEventsFromFile( const TQString& filename, bool clear)
|
|
{
|
|
// Open existing event list.
|
|
TQFile file( filename );
|
|
if ( !file.open( IO_ReadOnly ) )
|
|
{
|
|
kdDebug() << "SpeechData::loadNotifyEventsFromFile: Unable to open file " << filename << endl;
|
|
}
|
|
// TQDomDocument doc( "http://www.kde.org/share/apps/kttsd/stringreplacer/wordlist.dtd []" );
|
|
TQDomDocument doc( "" );
|
|
if ( !doc.setContent( &file ) ) {
|
|
file.close();
|
|
kdDebug() << "SpeechData::loadNotifyEventsFromFile: File not in proper XML format. " << filename << endl;
|
|
}
|
|
// kdDebug() << "StringReplacerConf::load: document successfully parsed." << endl;
|
|
file.close();
|
|
|
|
if ( clear )
|
|
{
|
|
notifyDefaultPresent = NotifyPresent::Passive;
|
|
notifyDefaultOptions.action = NotifyAction::SpeakMsg;
|
|
notifyDefaultOptions.talker = TQString();
|
|
notifyDefaultOptions.customMsg = TQString();
|
|
notifyAppMap.clear();
|
|
}
|
|
|
|
// Event list.
|
|
TQDomNodeList eventList = doc.elementsByTagName("notifyEvent");
|
|
const int eventListCount = eventList.count();
|
|
for (int eventIndex = 0; eventIndex < eventListCount; ++eventIndex)
|
|
{
|
|
TQDomNode eventNode = eventList.item(eventIndex);
|
|
TQDomNodeList propList = eventNode.childNodes();
|
|
TQString eventSrc;
|
|
TQString event;
|
|
TQString actionName;
|
|
TQString message;
|
|
TalkerCode talkerCode;
|
|
const int propListCount = propList.count();
|
|
for (int propIndex = 0; propIndex < propListCount; ++propIndex)
|
|
{
|
|
TQDomNode propNode = propList.item(propIndex);
|
|
TQDomElement prop = propNode.toElement();
|
|
if (prop.tagName() == "eventSrc") eventSrc = prop.text();
|
|
if (prop.tagName() == "event") event = prop.text();
|
|
if (prop.tagName() == "action") actionName = prop.text();
|
|
if (prop.tagName() == "message") message = prop.text();
|
|
if (prop.tagName() == "talker") talkerCode = TalkerCode(prop.text(), false);
|
|
}
|
|
NotifyOptions notifyOptions;
|
|
notifyOptions.action = NotifyAction::action( actionName );
|
|
notifyOptions.talker = talkerCode.getTalkerCode();
|
|
notifyOptions.customMsg = message;
|
|
if ( eventSrc != "default" )
|
|
{
|
|
notifyOptions.eventName = NotifyEvent::getEventName( eventSrc, event );
|
|
NotifyEventMap notifyEventMap = notifyAppMap[ eventSrc ];
|
|
notifyEventMap[ event ] = notifyOptions;
|
|
notifyAppMap[ eventSrc ] = notifyEventMap;
|
|
} else {
|
|
notifyOptions.eventName = TQString();
|
|
notifyDefaultPresent = NotifyPresent::present( event );
|
|
notifyDefaultOptions = notifyOptions;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destructor
|
|
*/
|
|
SpeechData::~SpeechData(){
|
|
// kdDebug() << "Running: SpeechData::~SpeechData()" << endl;
|
|
// Walk through jobs and emit a textRemoved signal for each job.
|
|
for (mlJob* job = textJobs.first(); (job); job = textJobs.next())
|
|
{
|
|
emit textRemoved(job->appId, job->jobNum);
|
|
}
|
|
|
|
TQPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs );
|
|
for( ; it.current(); ++it )
|
|
{
|
|
PooledFilterMgr* pooledFilterMgr = it.current();
|
|
delete pooledFilterMgr->filterMgr;
|
|
delete pooledFilterMgr->talkerCode;
|
|
delete pooledFilterMgr;
|
|
}
|
|
|
|
delete config;
|
|
}
|
|
|
|
/**
|
|
* Say a message as soon as possible, interrupting any other speech in progress.
|
|
* IMPORTANT: This method is reserved for use by Screen Readers and should not be used
|
|
* by any other applications.
|
|
* @param msg The message to be spoken.
|
|
* @param talker Code for the talker to do the speaking. Example "en".
|
|
* If NULL, defaults to the user's default talker.
|
|
* If no plugin has been configured for the specified Talker code,
|
|
* defaults to the closest matching talker.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
*
|
|
* If an existing Screen Reader output is in progress, it is stopped and discarded and
|
|
* replaced with this new message.
|
|
*/
|
|
void SpeechData::setScreenReaderOutput(const TQString &msg, const TQString &talker, const TQCString &appId)
|
|
{
|
|
screenReaderOutput.text = msg;
|
|
screenReaderOutput.talker = talker;
|
|
screenReaderOutput.appId = appId;
|
|
screenReaderOutput.seq = 1;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the Screen Reader Output.
|
|
*/
|
|
mlText* SpeechData::getScreenReaderOutput()
|
|
{
|
|
mlText* temp = new mlText();
|
|
temp->text = screenReaderOutput.text;
|
|
temp->talker = screenReaderOutput.talker;
|
|
temp->appId = screenReaderOutput.appId;
|
|
temp->seq = screenReaderOutput.seq;
|
|
// Blank the Screen Reader to text to "empty" it.
|
|
screenReaderOutput.text = "";
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Returns true if Screen Reader Output is ready to be spoken.
|
|
*/
|
|
bool SpeechData::screenReaderOutputReady()
|
|
{
|
|
return !screenReaderOutput.text.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Add a new warning to the queue.
|
|
*/
|
|
void SpeechData::enqueueWarning( const TQString &warning, const TQString &talker, const TQCString &appId){
|
|
// kdDebug() << "Running: SpeechData::enqueueWarning( const TQString &warning )" << endl;
|
|
mlJob* job = new mlJob();
|
|
++jobCounter;
|
|
if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums.
|
|
uint jobNum = jobCounter;
|
|
job->jobNum = jobNum;
|
|
job->talker = talker;
|
|
job->appId = appId;
|
|
job->seq = 1;
|
|
job->partCount = 1;
|
|
warnings.enqueue( job );
|
|
job->sentences = TQStringList();
|
|
// Do not apply Sentence Boundary Detection filters to warnings.
|
|
startJobFiltering( job, warning, true );
|
|
// uint count = warnings.count();
|
|
// kdDebug() << "Adding '" << temp->text << "' with talker '" << temp->talker << "' from application " << appId << " to the warnings queue leaving a total of " << count << " items." << endl;
|
|
}
|
|
|
|
/**
|
|
* Pop (get and erase) a warning from the queue.
|
|
* @return Pointer to mlText structure containing the message.
|
|
*
|
|
* Caller is responsible for deleting the structure.
|
|
*/
|
|
mlText* SpeechData::dequeueWarning(){
|
|
// kdDebug() << "Running: SpeechData::dequeueWarning()" << endl;
|
|
mlJob* job = warnings.dequeue();
|
|
waitJobFiltering(job);
|
|
mlText* temp = new mlText();
|
|
temp->jobNum = job->jobNum;
|
|
temp->text = job->sentences.join("");
|
|
temp->talker = job->talker;
|
|
temp->appId = job->appId;
|
|
temp->seq = 1;
|
|
delete job;
|
|
// uint count = warnings.count();
|
|
// kdDebug() << "Removing '" << temp->text << "' with talker '" << temp->talker << "' from the warnings queue leaving a total of " << count << " items." << endl;
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Are there any Warnings?
|
|
*/
|
|
bool SpeechData::warningInQueue(){
|
|
// kdDebug() << "Running: SpeechData::warningInQueue() const" << endl;
|
|
bool temp = !warnings.isEmpty();
|
|
// if(temp){
|
|
// kdDebug() << "The warnings queue is NOT empty" << endl;
|
|
// } else {
|
|
// kdDebug() << "The warnings queue is empty" << endl;
|
|
// }
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Add a new message to the queue.
|
|
*/
|
|
void SpeechData::enqueueMessage( const TQString &message, const TQString &talker, const TQCString& appId){
|
|
// kdDebug() << "Running: SpeechData::enqueueMessage" << endl;
|
|
mlJob* job = new mlJob();
|
|
++jobCounter;
|
|
if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums.
|
|
uint jobNum = jobCounter;
|
|
job->jobNum = jobNum;
|
|
job->talker = talker;
|
|
job->appId = appId;
|
|
job->seq = 1;
|
|
job->partCount = 1;
|
|
messages.enqueue( job );
|
|
job->sentences = TQStringList();
|
|
// Do not apply Sentence Boundary Detection filters to messages.
|
|
startJobFiltering( job, message, true );
|
|
// uint count = messages.count();
|
|
// kdDebug() << "Adding '" << temp->text << "' with talker '" << temp->talker << "' from application " << appId << " to the messages queue leaving a total of " << count << " items." << endl;
|
|
}
|
|
|
|
/**
|
|
* Pop (get and erase) a message from the queue.
|
|
* @return Pointer to mlText structure containing the message.
|
|
*
|
|
* Caller is responsible for deleting the structure.
|
|
*/
|
|
mlText* SpeechData::dequeueMessage(){
|
|
// kdDebug() << "Running: SpeechData::dequeueMessage()" << endl;
|
|
mlJob* job = messages.dequeue();
|
|
waitJobFiltering(job);
|
|
mlText* temp = new mlText();
|
|
temp->jobNum = job->jobNum;
|
|
temp->text = job->sentences.join("");
|
|
temp->talker = job->talker;
|
|
temp->appId = job->appId;
|
|
temp->seq = 1;
|
|
delete job;
|
|
/* mlText *temp = messages.dequeue(); */
|
|
// uint count = messages.count();
|
|
// kdDebug() << "Removing '" << temp->text << "' with talker '" << temp->talker << "' from the messages queue leaving a total of " << count << " items." << endl;
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Are there any Messages?
|
|
*/
|
|
bool SpeechData::messageInQueue(){
|
|
// kdDebug() << "Running: SpeechData::messageInQueue() const" << endl;
|
|
bool temp = !messages.isEmpty();
|
|
// if(temp){
|
|
// kdDebug() << "The messages queue is NOT empty" << endl;
|
|
// } else {
|
|
// kdDebug() << "The messages queue is empty" << endl;
|
|
// }
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Determines whether the given text is SSML markup.
|
|
*/
|
|
bool SpeechData::isSsml(const TQString &text)
|
|
{
|
|
/// This checks to see if the root tag of the text is a <speak> tag.
|
|
TQDomDocument ssml;
|
|
ssml.setContent(text, false); // No namespace processing.
|
|
/// Check to see if this is SSML
|
|
TQDomElement root = ssml.documentElement();
|
|
return (root.tagName() == "speak");
|
|
}
|
|
|
|
/**
|
|
* Parses a block of text into sentences using the application-specified regular expression
|
|
* or (if not specified), the default regular expression.
|
|
* @param text The message to be spoken.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
* @return List of parsed sentences.
|
|
*
|
|
* If the text contains SSML, it is not parsed into sentences at all.
|
|
* TODO: Need a way to preserve SSML but still parse into sentences.
|
|
* We will walk before we run for now and not sentence parse.
|
|
*/
|
|
|
|
TQStringList SpeechData::parseText(const TQString &text, const TQCString &appId /*=NULL*/)
|
|
{
|
|
// There has to be a better way
|
|
// kdDebug() << "I'm getting: " << endl << text << " from application " << appId << endl;
|
|
if (isSsml(text))
|
|
{
|
|
TQString tempList(text);
|
|
return tempList;
|
|
}
|
|
// See if app has specified a custom sentence delimiter and use it, otherwise use default.
|
|
TQRegExp sentenceDelimiter;
|
|
if (sentenceDelimiters.find(appId) != sentenceDelimiters.end())
|
|
sentenceDelimiter = TQRegExp(sentenceDelimiters[appId]);
|
|
else
|
|
sentenceDelimiter = TQRegExp("([\\.\\?\\!\\:\\;]\\s)|(\\n *\\n)");
|
|
TQString temp = text;
|
|
// Replace spaces, tabs, and formfeeds with a single space.
|
|
temp.replace(TQRegExp("[ \\t\\f]+"), " ");
|
|
// Replace sentence delimiters with tab.
|
|
temp.replace(sentenceDelimiter, "\\1\t");
|
|
// Replace remaining newlines with spaces.
|
|
temp.replace("\n"," ");
|
|
temp.replace("\r"," ");
|
|
// Remove leading spaces.
|
|
temp.replace(TQRegExp("\\t +"), "\t");
|
|
// Remove trailing spaces.
|
|
temp.replace(TQRegExp(" +\\t"), "\t");
|
|
// Remove blank lines.
|
|
temp.replace(TQRegExp("\t\t+"),"\t");
|
|
// Split into sentences.
|
|
TQStringList tempList = TQStringList::split("\t", temp, false);
|
|
|
|
// for ( TQStringList::Iterator it = tempList.begin(); it != tempList.end(); ++it ) {
|
|
// kdDebug() << "'" << *it << "'" << endl;
|
|
// }
|
|
return tempList;
|
|
}
|
|
|
|
/**
|
|
* Queues a text job.
|
|
*/
|
|
uint SpeechData::setText( const TQString &text, const TQString &talker, const TQCString &appId)
|
|
{
|
|
// kdDebug() << "Running: SpeechData::setText" << endl;
|
|
mlJob* job = new mlJob;
|
|
++jobCounter;
|
|
if (jobCounter == 0) ++jobCounter; // Overflow is OK, but don't want any 0 jobNums.
|
|
uint jobNum = jobCounter;
|
|
job->jobNum = jobNum;
|
|
job->appId = appId;
|
|
job->talker = talker;
|
|
job->state = KSpeech::jsQueued;
|
|
job->seq = 0;
|
|
job->partCount = 1;
|
|
#if NO_FILTERS
|
|
TQStringList tempList = parseText(text, appId);
|
|
job->sentences = tempList;
|
|
job->partSeqNums.append(tempList.count());
|
|
textJobs.append(job);
|
|
emit textSet(appId, jobNum);
|
|
#else
|
|
job->sentences = TQStringList();
|
|
job->partSeqNums = TQValueList<int>();
|
|
textJobs.append(job);
|
|
startJobFiltering(job, text, false);
|
|
#endif
|
|
return jobNum;
|
|
}
|
|
|
|
/**
|
|
* Adds another part to a text job. Does not start speaking the text.
|
|
* (thread safe)
|
|
* @param jobNum Job number of the text job.
|
|
* If zero, applies to the last job queued by the application,
|
|
* but if no such job, applies to the last job queued by any application.
|
|
* @param text The message to be spoken.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
* @return Part number for the added part. Parts are numbered starting at 1.
|
|
*
|
|
* The text is parsed into individual sentences. Call getTextCount to retrieve
|
|
* the sentence count. Call startText to mark the job as speakable and if the
|
|
* job is the first speakable job in the queue, speaking will begin.
|
|
* @see setText.
|
|
* @see startText.
|
|
*/
|
|
int SpeechData::appendText(const TQString &text, const uint jobNum, const TQCString& /*appId*/)
|
|
{
|
|
// kdDebug() << "Running: SpeechData::appendText" << endl;
|
|
int newPartNum = 0;
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job)
|
|
{
|
|
job->partCount++;
|
|
#if NO_FILTERS
|
|
TQStringList tempList = parseText(text, appId);
|
|
int sentenceCount = job->sentences.count();
|
|
job->sentences += tempList;
|
|
job->partSeqNums.append(sentenceCount + tempList.count());
|
|
newPartNum = job->partSeqNums.count() + 1;
|
|
emit textAppended(job->appId, jobNum, newPartNum);
|
|
#else
|
|
newPartNum = job->partSeqNums.count() + 1;
|
|
startJobFiltering(job, text, false);
|
|
#endif
|
|
}
|
|
return newPartNum;
|
|
}
|
|
|
|
/**
|
|
* Given an appId, returns the last (most recently queued) job with that appId.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
* @return Pointer to the text job.
|
|
* If no such job, returns 0.
|
|
* If appId is NULL, returns the last job in the queue.
|
|
* Does not change textJobs.current().
|
|
*/
|
|
mlJob* SpeechData::findLastJobByAppId(const TQCString& appId)
|
|
{
|
|
if (appId == NULL)
|
|
return textJobs.getLast();
|
|
else
|
|
{
|
|
TQPtrListIterator<mlJob> it(textJobs);
|
|
for (it.toLast() ; it.current(); --it )
|
|
{
|
|
if (it.current()->appId == appId)
|
|
{
|
|
return it.current();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given an appId, returns the last (most recently queued) job with that appId,
|
|
* or if no such job, the last (most recent) job in the queue.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
* @return Pointer to the text job.
|
|
* If no such job, returns 0.
|
|
* If appId is NULL, returns the last job in the queue.
|
|
* Does not change textJobs.current().
|
|
*/
|
|
mlJob* SpeechData::findAJobByAppId(const TQCString& appId)
|
|
{
|
|
mlJob* job = findLastJobByAppId(appId);
|
|
// if (!job) job = textJobs.getLast();
|
|
return job;
|
|
}
|
|
|
|
/**
|
|
* Given an appId, returns the last (most recently queued) Job Number with that appId,
|
|
* or if no such job, the Job Number of the last (most recent) job in the queue.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
* @return Job Number of the text job.
|
|
* If no such job, returns 0.
|
|
* If appId is NULL, returns the Job Number of the last job in the queue.
|
|
* Does not change textJobs.current().
|
|
*/
|
|
uint SpeechData::findAJobNumByAppId(const TQCString& appId)
|
|
{
|
|
mlJob* job = findAJobByAppId(appId);
|
|
if (job)
|
|
return job->jobNum;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Given a jobNum, returns the first job with that jobNum.
|
|
* @return Pointer to the text job.
|
|
* If no such job, returns 0.
|
|
* Does not change textJobs.current().
|
|
*/
|
|
mlJob* SpeechData::findJobByJobNum(const uint jobNum)
|
|
{
|
|
TQPtrListIterator<mlJob> it(textJobs);
|
|
for ( ; it.current(); ++it )
|
|
{
|
|
if (it.current()->jobNum == jobNum)
|
|
{
|
|
return it.current();
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Given a jobNum, returns the appId of the application that owns the job.
|
|
* @param jobNum Job number of the text job.
|
|
* @return appId of the job.
|
|
* If no such job, returns "".
|
|
* Does not change textJobs.current().
|
|
*/
|
|
TQCString SpeechData::getAppIdByJobNum(const uint jobNum)
|
|
{
|
|
TQCString appId;
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job) appId = job->appId;
|
|
return appId;
|
|
}
|
|
|
|
/**
|
|
* Sets pointer to the TalkerMgr object.
|
|
*/
|
|
void SpeechData::setTalkerMgr(TalkerMgr* talkerMgr) { m_talkerMgr = talkerMgr; }
|
|
|
|
/**
|
|
* Remove a text job from the queue.
|
|
* (thread safe)
|
|
* @param jobNum Job number of the text job.
|
|
*
|
|
* The job is deleted from the queue and the textRemoved signal is emitted.
|
|
*/
|
|
void SpeechData::removeText(const uint jobNum)
|
|
{
|
|
// kdDebug() << "Running: SpeechData::removeText" << endl;
|
|
uint removeJobNum = 0;
|
|
TQCString removeAppId; // The appId of the removed (and stopped) job.
|
|
mlJob* removeJob = findJobByJobNum(jobNum);
|
|
if (removeJob)
|
|
{
|
|
removeAppId = removeJob->appId;
|
|
removeJobNum = removeJob->jobNum;
|
|
// If filtering on the job, cancel it.
|
|
TQPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs );
|
|
for ( ; it.current(); ++it ) {
|
|
PooledFilterMgr* pooledFilterMgr = it.current();
|
|
if (pooledFilterMgr->job && (pooledFilterMgr->job->jobNum == removeJobNum))
|
|
{
|
|
pooledFilterMgr->busy = false;
|
|
pooledFilterMgr->job = 0;
|
|
pooledFilterMgr->partNum = 0;
|
|
delete pooledFilterMgr->talkerCode;
|
|
pooledFilterMgr->talkerCode = 0;
|
|
pooledFilterMgr->filterMgr->stopFiltering();
|
|
}
|
|
}
|
|
// Delete the job.
|
|
textJobs.removeRef(removeJob);
|
|
}
|
|
if (removeJobNum) emit textRemoved(removeAppId, removeJobNum);
|
|
}
|
|
|
|
/**
|
|
* Given a job and a sequence number, returns the part that sentence is in.
|
|
* If no such job or sequence number, returns 0.
|
|
* @param job The text job.
|
|
* @param seq Sequence number of the sentence. Sequence numbers begin with 1.
|
|
* @return Part number of the part the sentence is in. Parts are numbered
|
|
* beginning with 1. If no such job or sentence, returns 0.
|
|
*
|
|
*/
|
|
int SpeechData::getJobPartNumFromSeq(const mlJob& job, const int seq)
|
|
{
|
|
int foundPartNum = 0;
|
|
int desiredSeq = seq;
|
|
uint partNum = 0;
|
|
// Wait until all filtering has stopped for the job.
|
|
waitJobFiltering(&job);
|
|
while (partNum < job.partSeqNums.count())
|
|
{
|
|
if (desiredSeq <= job.partSeqNums[partNum])
|
|
{
|
|
foundPartNum = partNum + 1;
|
|
break;
|
|
}
|
|
desiredSeq = desiredSeq - job.partSeqNums[partNum];
|
|
++partNum;
|
|
}
|
|
return foundPartNum;
|
|
}
|
|
|
|
|
|
/**
|
|
* Delete expired jobs. At most, one finished job is kept on the queue.
|
|
* @param finishedJobNum Job number of a job that just finished.
|
|
* The just finished job is not deleted, but any other finished jobs are.
|
|
* Does not change the textJobs.current() pointer.
|
|
*/
|
|
void SpeechData::deleteExpiredJobs(const uint finishedJobNum)
|
|
{
|
|
// Save current pointer.
|
|
typedef TQPair<TQCString, uint> removedJob;
|
|
typedef TQValueList<removedJob> removedJobsList;
|
|
removedJobsList removedJobs;
|
|
// Walk through jobs and delete any other finished jobs.
|
|
for (mlJob* job = textJobs.first(); (job); job = textJobs.next())
|
|
{
|
|
if (job->jobNum != finishedJobNum && job->state == KSpeech::jsFinished)
|
|
{
|
|
removedJobs.append(removedJob(job->appId, job->jobNum));
|
|
textJobs.removeRef(job);
|
|
}
|
|
}
|
|
// Emit signals for removed jobs.
|
|
removedJobsList::const_iterator it;
|
|
removedJobsList::const_iterator endRemovedJobsList(removedJobs.constEnd());
|
|
for (it = removedJobs.constBegin(); it != endRemovedJobsList ; ++it)
|
|
{
|
|
TQCString appId = (*it).first;
|
|
uint jobNum = (*it).second;
|
|
textRemoved(appId, jobNum);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a Job Number, returns the next speakable text job on the queue.
|
|
* @param prevJobNum Current job number (which should not be returned).
|
|
* @return Pointer to mlJob structure of the first speakable job
|
|
* not equal prevJobNum. If no such job, returns null.
|
|
*
|
|
* Caller must not delete the job.
|
|
*/
|
|
mlJob* SpeechData::getNextSpeakableJob(const uint prevJobNum)
|
|
{
|
|
for (mlJob* job = textJobs.first(); (job); job = textJobs.next())
|
|
if (job->jobNum != prevJobNum)
|
|
if (job->state == KSpeech::jsSpeakable)
|
|
{
|
|
waitJobFiltering(job);
|
|
return job;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Given previous job number and sequence number, returns the next sentence from the
|
|
* text queue. If no such sentence is available, either because we've run out of
|
|
* jobs, or because all jobs are paused, returns null.
|
|
* @param prevJobNum Previous Job Number.
|
|
* @param prevSeq Previous sequency number.
|
|
* @return Pointer to n mlText structure containing the next sentence. If no
|
|
* sentence, returns null.
|
|
*
|
|
* Caller is responsible for deleting the returned mlText structure (if not null).
|
|
*/
|
|
mlText* SpeechData::getNextSentenceText(const uint prevJobNum, const uint prevSeq)
|
|
{
|
|
// kdDebug() << "SpeechData::getNextSentenceText running with prevJobNum " << prevJobNum << " prevSeq " << prevSeq << endl;
|
|
mlText* temp = 0;
|
|
uint jobNum = prevJobNum;
|
|
mlJob* job = 0;
|
|
uint seq = prevSeq;
|
|
++seq;
|
|
if (!jobNum)
|
|
{
|
|
job = getNextSpeakableJob(jobNum);
|
|
if (job) seq =+ job->seq;
|
|
} else
|
|
job = findJobByJobNum(prevJobNum);
|
|
if (!job)
|
|
{
|
|
job = getNextSpeakableJob(jobNum);
|
|
if (job) seq =+ job->seq;
|
|
}
|
|
else
|
|
{
|
|
if ((job->state != KSpeech::jsSpeakable) && (job->state != KSpeech::jsSpeaking))
|
|
{
|
|
job = getNextSpeakableJob(job->jobNum);
|
|
if (job) seq =+ job->seq;
|
|
}
|
|
}
|
|
if (job)
|
|
{
|
|
// If we run out of sentences in the job, move on to next job.
|
|
jobNum = job->jobNum;
|
|
if (seq > job->sentences.count())
|
|
{
|
|
job = getNextSpeakableJob(jobNum);
|
|
if (job) seq =+ job->seq;
|
|
}
|
|
}
|
|
if (job)
|
|
{
|
|
if (seq == 0) seq = 1;
|
|
temp = new mlText;
|
|
temp->text = job->sentences[seq - 1];
|
|
temp->appId = job->appId;
|
|
temp->talker = job->talker;
|
|
temp->jobNum = job->jobNum;
|
|
temp->seq = seq;
|
|
// kdDebug() << "SpeechData::getNextSentenceText: return job number " << temp->jobNum << " seq " << temp->seq << " sentence count = " << job->sentences.count() << endl;
|
|
} // else kdDebug() << "SpeechData::getNextSentenceText: no more sentences in queue" << endl;
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Given a Job Number, sets the current sequence number of the job.
|
|
* @param jobNum Job Number.
|
|
* @param seq Sequence number.
|
|
* If for some reason, the job does not exist, nothing happens.
|
|
*/
|
|
void SpeechData::setJobSequenceNum(const uint jobNum, const uint seq)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job) job->seq = seq;
|
|
}
|
|
|
|
/**
|
|
* Given a Job Number, returns the current sequence number of the job.
|
|
* @param jobNum Job Number.
|
|
* @return Sequence number of the job. If no such job, returns 0.
|
|
*/
|
|
uint SpeechData::getJobSequenceNum(const uint jobNum)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job)
|
|
return job->seq;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Sets the GREP pattern that will be used as the sentence delimiter.
|
|
* @param delimiter A valid GREP pattern.
|
|
* @param appId The DCOP senderId of the application. NULL if kttsd.
|
|
*
|
|
* The default delimiter is
|
|
@verbatim
|
|
([\\.\\?\\!\\:\\;])\\s
|
|
@endverbatim
|
|
*
|
|
* Note that backward slashes must be escaped.
|
|
*
|
|
* Changing the sentence delimiter does not affect other applications.
|
|
* @see sentenceparsing
|
|
*/
|
|
void SpeechData::setSentenceDelimiter(const TQString &delimiter, const TQCString appId)
|
|
{
|
|
sentenceDelimiters[appId] = delimiter;
|
|
}
|
|
|
|
/**
|
|
* Get the number of sentences in a text job.
|
|
* (thread safe)
|
|
* @param jobNum Job number of the text job.
|
|
* @return The number of sentences in the job. -1 if no such job.
|
|
*
|
|
* The sentences of a job are given sequence numbers from 1 to the number returned by this
|
|
* method. The sequence numbers are emitted in the sentenceStarted and sentenceFinished signals.
|
|
*/
|
|
int SpeechData::getTextCount(const uint jobNum)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
int temp;
|
|
if (job)
|
|
{
|
|
waitJobFiltering(job);
|
|
temp = job->sentences.count();
|
|
} else
|
|
temp = -1;
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Get the number of jobs in the text job queue.
|
|
* (thread safe)
|
|
* @return Number of text jobs in the queue. 0 if none.
|
|
*/
|
|
uint SpeechData::getTextJobCount()
|
|
{
|
|
return textJobs.count();
|
|
}
|
|
|
|
/**
|
|
* Get a comma-separated list of text job numbers in the queue.
|
|
* @return Comma-separated list of text job numbers in the queue.
|
|
*/
|
|
TQString SpeechData::getTextJobNumbers()
|
|
{
|
|
TQString jobs;
|
|
TQPtrListIterator<mlJob> it(textJobs);
|
|
for ( ; it.current(); ++it )
|
|
{
|
|
if (!jobs.isEmpty()) jobs.append(",");
|
|
jobs.append(TQString::number(it.current()->jobNum));
|
|
}
|
|
return jobs;
|
|
}
|
|
|
|
/**
|
|
* Get the state of a text job.
|
|
* (thread safe)
|
|
* @param jobNum Job number of the text job.
|
|
* @return State of the job. -1 if invalid job number.
|
|
*/
|
|
int SpeechData::getTextJobState(const uint jobNum)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
int temp;
|
|
if (job)
|
|
temp = job->state;
|
|
else
|
|
temp = -1;
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Set the state of a text job.
|
|
* @param jobNum Job Number of the job.
|
|
* @param state New state for the job.
|
|
*
|
|
* If the new state is Finished, deletes other expired jobs.
|
|
*
|
|
**/
|
|
void SpeechData::setTextJobState(const uint jobNum, const KSpeech::kttsdJobState state)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job)
|
|
{
|
|
job->state = state;
|
|
if (state == KSpeech::jsFinished) deleteExpiredJobs(jobNum);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get information about a text job.
|
|
* @param jobNum Job number of the text job.
|
|
* @return A TQDataStream containing information about the job.
|
|
* Blank if no such job.
|
|
*
|
|
* The stream contains the following elements:
|
|
* - int state Job state.
|
|
* - TQCString appId DCOP senderId of the application that requested the speech job.
|
|
* - TQString talker Language code in which to speak the text.
|
|
* - int seq Current sentence being spoken. Sentences are numbered starting at 1.
|
|
* - int sentenceCount Total number of sentences in the job.
|
|
* - int partNum Current part of the job begin spoken. Parts are numbered starting at 1.
|
|
* - int partCount Total number of parts in the job.
|
|
*
|
|
* Note that sequence numbers apply to the entire job.
|
|
* They do not start from 1 at the beginning of each part.
|
|
*
|
|
* The following sample code will decode the stream:
|
|
@verbatim
|
|
TQByteArray jobInfo = getTextJobInfo(jobNum);
|
|
TQDataStream stream(jobInfo, IO_ReadOnly);
|
|
int state;
|
|
TQCString appId;
|
|
TQString talker;
|
|
int seq;
|
|
int sentenceCount;
|
|
int partNum;
|
|
int partCount;
|
|
stream >> state;
|
|
stream >> appId;
|
|
stream >> talker;
|
|
stream >> seq;
|
|
stream >> sentenceCount;
|
|
stream >> partNum;
|
|
stream >> partCount;
|
|
@endverbatim
|
|
*/
|
|
TQByteArray SpeechData::getTextJobInfo(const uint jobNum)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
TQByteArray temp;
|
|
if (job)
|
|
{
|
|
waitJobFiltering(job);
|
|
TQDataStream stream(temp, IO_WriteOnly);
|
|
stream << job->state;
|
|
stream << job->appId;
|
|
stream << job->talker;
|
|
stream << job->seq;
|
|
stream << job->sentences.count();
|
|
stream << getJobPartNumFromSeq(*job, job->seq);
|
|
stream << job->partSeqNums.count();
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Return a sentence of a job.
|
|
* @param jobNum Job number of the text job.
|
|
* @param seq Sequence number of the sentence.
|
|
* @return The specified sentence in the specified job. If no such
|
|
* job or sentence, returns "".
|
|
*/
|
|
TQString SpeechData::getTextJobSentence(const uint jobNum, const uint seq /*=1*/)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
TQString temp;
|
|
if (job)
|
|
{
|
|
waitJobFiltering(job);
|
|
temp = job->sentences[seq - 1];
|
|
}
|
|
return temp;
|
|
}
|
|
|
|
/**
|
|
* Change the talker for a text job.
|
|
* @param jobNum Job number of the text job.
|
|
* If zero, applies to the last job queued by the application,
|
|
* but if no such job, applies to the last job queued by any application.
|
|
* @param talker New code for the talker to do the speaking. Example "en".
|
|
* If NULL, defaults to the user's default talker.
|
|
* If no plugin has been configured for the specified Talker code,
|
|
* defaults to the closest matching talker.
|
|
*/
|
|
void SpeechData::changeTextTalker(const TQString &talker, uint jobNum)
|
|
{
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job) job->talker = talker;
|
|
}
|
|
|
|
/**
|
|
* Move a text job down in the queue so that it is spoken later.
|
|
* @param jobNum Job number of the text job.
|
|
*/
|
|
void SpeechData::moveTextLater(const uint jobNum)
|
|
{
|
|
// kdDebug() << "Running: SpeechData::moveTextLater" << endl;
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job)
|
|
{
|
|
// Get index of the job.
|
|
uint index = textJobs.findRef(job);
|
|
// Move job down one position in the queue.
|
|
// kdDebug() << "In SpeechData::moveTextLater, moving jobNum " << movedJobNum << endl;
|
|
if (textJobs.insert(index + 2, job)) textJobs.take(index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Jump to the first sentence of a specified part of a text job.
|
|
* @param partNum Part number of the part to jump to. Parts are numbered starting at 1.
|
|
* @param jobNum Job number of the text job.
|
|
* @return Part number of the part actually jumped to.
|
|
*
|
|
* If partNum is greater than the number of parts in the job, jumps to last part.
|
|
* If partNum is 0, does nothing and returns the current part number.
|
|
* If no such job, does nothing and returns 0.
|
|
* Does not affect the current speaking/not-speaking state of the job.
|
|
*/
|
|
int SpeechData::jumpToTextPart(const int partNum, const uint jobNum)
|
|
{
|
|
// kdDebug() << "Running: SpeechData::jumpToTextPart" << endl;
|
|
int newPartNum = 0;
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job)
|
|
{
|
|
waitJobFiltering(job);
|
|
if (partNum > 0)
|
|
{
|
|
newPartNum = partNum;
|
|
int partCount = job->partSeqNums.count();
|
|
if (newPartNum > partCount) newPartNum = partCount;
|
|
if (newPartNum > 1)
|
|
job->seq = job->partSeqNums[newPartNum - 1];
|
|
else
|
|
job->seq = 0;
|
|
}
|
|
else
|
|
newPartNum = getJobPartNumFromSeq(*job, job->seq);
|
|
}
|
|
return newPartNum;
|
|
}
|
|
|
|
/**
|
|
* Advance or rewind N sentences in a text job.
|
|
* @param n Number of sentences to advance (positive) or rewind (negative)
|
|
* in the job.
|
|
* @param jobNum Job number of the text job.
|
|
* @return Sequence number of the sentence actually moved to. Sequence numbers
|
|
* are numbered starting at 1.
|
|
*
|
|
* If no such job, does nothing and returns 0.
|
|
* If n is zero, returns the current sequence number of the job.
|
|
* Does not affect the current speaking/not-speaking state of the job.
|
|
*/
|
|
uint SpeechData::moveRelTextSentence(const int n, const uint jobNum /*=0*/)
|
|
{
|
|
// kdDebug() << "Running: SpeechData::moveRelTextSentence" << endl;
|
|
int newSeqNum = 0;
|
|
mlJob* job = findJobByJobNum(jobNum);
|
|
if (job)
|
|
{
|
|
waitJobFiltering(job);
|
|
int oldSeqNum = job->seq;
|
|
newSeqNum = oldSeqNum + n;
|
|
if (n != 0)
|
|
{
|
|
if (newSeqNum < 0) newSeqNum = 0;
|
|
int sentenceCount = job->sentences.count();
|
|
if (newSeqNum > sentenceCount) newSeqNum = sentenceCount;
|
|
job->seq = newSeqNum;
|
|
}
|
|
}
|
|
return newSeqNum;
|
|
}
|
|
|
|
/**
|
|
* Assigns a FilterMgr to a job and starts filtering on it.
|
|
*/
|
|
void SpeechData::startJobFiltering(mlJob* job, const TQString& text, bool noSBD)
|
|
{
|
|
uint jobNum = job->jobNum;
|
|
int partNum = job->partCount;
|
|
// kdDebug() << "SpeechData::startJobFiltering: jobNum = " << jobNum << " partNum = " << partNum << " text.left(500) = " << text.left(500) << endl;
|
|
// Find an idle FilterMgr, if any.
|
|
// If filtering is already in progress for this job and part, do nothing.
|
|
PooledFilterMgr* pooledFilterMgr = 0;
|
|
TQPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs );
|
|
for( ; it.current(); ++it )
|
|
{
|
|
if (it.current()->busy) {
|
|
if ((it.current()->job->jobNum == jobNum) && (it.current()->partNum == partNum)) return;
|
|
} else {
|
|
if (!it.current()->job && !pooledFilterMgr) pooledFilterMgr = it.current();
|
|
}
|
|
}
|
|
// Create a new FilterMgr if needed and add to pool.
|
|
if (!pooledFilterMgr)
|
|
{
|
|
// kdDebug() << "SpeechData::startJobFiltering: adding new pooledFilterMgr for job " << jobNum << " part " << partNum << endl;
|
|
pooledFilterMgr = new PooledFilterMgr();
|
|
FilterMgr* filterMgr = new FilterMgr();
|
|
filterMgr->init(config, "General");
|
|
pooledFilterMgr->filterMgr = filterMgr;
|
|
// Connect signals from FilterMgr.
|
|
connect (filterMgr, TQT_SIGNAL(filteringFinished()), this, TQT_SLOT(slotFilterMgrFinished()));
|
|
connect (filterMgr, TQT_SIGNAL(filteringStopped()), this, TQT_SLOT(slotFilterMgrStopped()));
|
|
m_pooledFilterMgrs.append(pooledFilterMgr);
|
|
}
|
|
// else kdDebug() << "SpeechData::startJobFiltering: re-using idle pooledFilterMgr for job " << jobNum << " part " << partNum << endl;
|
|
// Flag the FilterMgr as busy and set it going.
|
|
pooledFilterMgr->busy = true;
|
|
pooledFilterMgr->job = job;
|
|
pooledFilterMgr->partNum = partNum;
|
|
pooledFilterMgr->filterMgr->setNoSBD( noSBD );
|
|
// Get TalkerCode structure of closest matching Talker.
|
|
pooledFilterMgr->talkerCode = m_talkerMgr->talkerToTalkerCode(job->talker);
|
|
// Pass Sentence Boundary regular expression (if app overrode default);
|
|
if (sentenceDelimiters.find(job->appId) != sentenceDelimiters.end())
|
|
pooledFilterMgr->filterMgr->setSbRegExp(sentenceDelimiters[job->appId]);
|
|
pooledFilterMgr->filterMgr->asyncConvert(text, pooledFilterMgr->talkerCode, job->appId);
|
|
}
|
|
|
|
/**
|
|
* Waits for filtering to be completed on a job.
|
|
* This is typically called because an app has requested job info that requires
|
|
* filtering to be completed, such as getJobInfo.
|
|
*/
|
|
void SpeechData::waitJobFiltering(const mlJob* job)
|
|
{
|
|
#if NO_FILTERS
|
|
return;
|
|
#endif
|
|
uint jobNum = job->jobNum;
|
|
bool waited = false;
|
|
TQPtrListIterator<PooledFilterMgr> it(m_pooledFilterMgrs);
|
|
for ( ; it.current(); ++it )
|
|
{
|
|
PooledFilterMgr* pooledFilterMgr = it.current();
|
|
if (pooledFilterMgr->busy)
|
|
{
|
|
if (pooledFilterMgr->job->jobNum == jobNum)
|
|
{
|
|
if (!pooledFilterMgr->filterMgr->noSBD())
|
|
kdDebug() << "SpeechData::waitJobFiltering: Waiting for filter to finish. Not optimium. " <<
|
|
"Try waiting for textSet signal before querying for job information." << endl;
|
|
pooledFilterMgr->filterMgr->waitForFinished();
|
|
// kdDebug() << "SpeechData::waitJobFiltering: waiting for job " << jobNum << endl;
|
|
waited = true;
|
|
}
|
|
}
|
|
}
|
|
if (waited)
|
|
doFiltering();
|
|
}
|
|
|
|
/**
|
|
* Processes filters by looping across the pool of FilterMgrs.
|
|
* As each FilterMgr finishes, emits appropriate signals and flags it as no longer busy.
|
|
*/
|
|
void SpeechData::doFiltering()
|
|
{
|
|
// kdDebug() << "SpeechData::doFiltering: Running. " << m_pooledFilterMgrs.count() << " filters in pool." << endl;
|
|
bool again = true;
|
|
while (again)
|
|
{
|
|
again = false;
|
|
TQPtrListIterator<PooledFilterMgr> it( m_pooledFilterMgrs );
|
|
for( ; it.current(); ++it )
|
|
{
|
|
PooledFilterMgr* pooledFilterMgr = it.current();
|
|
// If FilterMgr is busy, see if it is now finished.
|
|
if (pooledFilterMgr->busy)
|
|
{
|
|
FilterMgr* filterMgr = pooledFilterMgr->filterMgr;
|
|
if (filterMgr->getState() == FilterMgr::fsFinished)
|
|
{
|
|
mlJob* job = pooledFilterMgr->job;
|
|
// kdDebug() << "SpeechData::doFiltering: filter finished, jobNum = " << job->jobNum << " partNum = " << pooledFilterMgr->partNum << endl;
|
|
// We have to retrieve parts in order, but parts may not be completed in order.
|
|
// See if this is the next part we need.
|
|
if ((int)job->partSeqNums.count() == (pooledFilterMgr->partNum - 1))
|
|
{
|
|
pooledFilterMgr->busy = false;
|
|
// Retrieve text from FilterMgr.
|
|
TQString text = filterMgr->getOutput();
|
|
// kdDebug() << "SpeechData::doFiltering: text.left(500) = " << text.left(500) << endl;
|
|
filterMgr->ackFinished();
|
|
// Convert the TalkerCode back into string.
|
|
job->talker = pooledFilterMgr->talkerCode->getTalkerCode();
|
|
// TalkerCode object no longer needed.
|
|
delete pooledFilterMgr->talkerCode;
|
|
pooledFilterMgr->talkerCode = 0;
|
|
if (filterMgr->noSBD())
|
|
job->sentences = text;
|
|
else
|
|
{
|
|
// Split the text into sentences and store in the job.
|
|
// The SBD plugin does all the real sentence parsing, inserting tabs at each
|
|
// sentence boundary.
|
|
TQStringList sentences = TQStringList::split("\t", text, false);
|
|
int sentenceCount = job->sentences.count();
|
|
job->sentences += sentences;
|
|
job->partSeqNums.append(sentenceCount + sentences.count());
|
|
}
|
|
int partNum = job->partSeqNums.count();
|
|
// Clean up.
|
|
pooledFilterMgr->job = 0;
|
|
pooledFilterMgr->partNum = 0;
|
|
// Emit signal.
|
|
if (!filterMgr->noSBD())
|
|
{
|
|
if (partNum == 1)
|
|
emit textSet(job->appId, job->jobNum);
|
|
else
|
|
emit textAppended(job->appId, job->jobNum, partNum);
|
|
}
|
|
} else {
|
|
// A part is ready, but need to first process a finished preceeding part
|
|
// that follows this one in the pool of filter managers.
|
|
again = true;
|
|
// kdDebug() << "SpeechData::doFiltering: filter is finished, but must wait for earlier part to finish filter, job = " << pooledFilterMgr->job->jobNum << endl;
|
|
}
|
|
}
|
|
// else kdDebug() << "SpeechData::doFiltering: filter for job " << pooledFilterMgr->job->jobNum << " is busy." << endl;
|
|
}
|
|
// else kdDebug() << "SpeechData::doFiltering: filter is idle" << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SpeechData::slotFilterMgrFinished()
|
|
{
|
|
// kdDebug() << "SpeechData::slotFilterMgrFinished: received signal FilterMgr finished signal." << endl;
|
|
doFiltering();
|
|
}
|
|
|
|
void SpeechData::slotFilterMgrStopped()
|
|
{
|
|
doFiltering();
|
|
}
|
|
|