summaryrefslogtreecommitdiffstats
path: root/kttsd/kttsd/threadedplugin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kttsd/kttsd/threadedplugin.cpp')
-rw-r--r--kttsd/kttsd/threadedplugin.cpp282
1 files changed, 282 insertions, 0 deletions
diff --git a/kttsd/kttsd/threadedplugin.cpp b/kttsd/kttsd/threadedplugin.cpp
new file mode 100644
index 0000000..babc792
--- /dev/null
+++ b/kttsd/kttsd/threadedplugin.cpp
@@ -0,0 +1,282 @@
+/***************************************************** vim:set ts=4 sw=4 sts=4:
+ Converts a synchronous plugin into an asynchronous one.
+ -------------------
+ Copyright:
+ (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net>
+ -------------------
+ Original author: Gary Cramblitt <garycramblitt@comcast.net>
+
+ 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ ******************************************************************************/
+
+#include <qevent.h>
+#include <qapplication.h>
+
+#include <kdebug.h>
+
+#include "speaker.h"
+#include "threadedplugin.h"
+
+/**
+* Constructor.
+*/
+ThreadedPlugIn::ThreadedPlugIn(PlugInProc* plugin,
+ QObject *parent /*= 0*/, const char *name /*= 0*/):
+ PlugInProc(parent, name),
+ QThread(),
+ m_plugin(plugin),
+ m_filename(QString::null),
+ m_requestExit(false),
+ m_supportsSynth(false)
+{
+ m_waitingStop = false;
+ m_state = psIdle;
+}
+
+/**
+* Destructor.
+*/
+ThreadedPlugIn::~ThreadedPlugIn()
+{
+ if (running())
+ {
+ // If thread is busy, try to stop it.
+ m_requestExit = true;
+ if (m_threadRunningMutex.locked()) m_plugin->stopText();
+ m_action = paNone;
+ m_waitCondition.wakeOne();
+ wait(5000);
+ // If thread still active, stopText didn't succeed. Terminate the thread.
+ if (running())
+ {
+ terminate();
+ wait();
+ }
+ }
+ delete m_plugin;
+}
+
+/**
+* Initialize the speech plugin.
+*/
+bool ThreadedPlugIn::init(KConfig *config, const QString &configGroup)
+{
+ bool stat = m_plugin->init(config, configGroup);
+ m_supportsSynth = m_plugin->supportsSynth();
+ // Start thread running, which will immediately go to sleep.
+ start();
+ return stat;
+}
+
+/**
+* Say a text. Synthesize and audibilize it.
+* @param text The text to be spoken.
+*
+* If the plugin supports asynchronous operation, it should return immediately
+* and emit sayFinished signal when synthesis and audibilizing is finished.
+*/
+void ThreadedPlugIn::sayText(const QString &text)
+{
+ kdDebug() << "ThreadedPlugin::sayText running with text " << text << endl;
+ waitThreadNotBusy();
+ m_action = paSayText;
+ m_text = text;
+ m_state = psSaying;
+ m_waitCondition.wakeOne();
+}
+
+/**
+* Synthesize text into an audio file, but do not send to the audio device.
+* @param text The text to be synthesized.
+* @param suggestedFilename Full pathname of file to create. The plugin
+* may ignore this parameter and choose its own
+* filename. KTTSD will query the generated
+* filename using getFilename().
+*
+* If the plugin supports asynchronous operation, it should return immediately
+* and emit synthFinished signal when synthesis is completed.
+*/
+void ThreadedPlugIn::synthText(const QString &text, const QString &suggestedFilename)
+{
+ waitThreadNotBusy();
+ m_action = paSynthText;
+ m_text = text;
+ m_filename = suggestedFilename;
+ m_state = psSynthing;
+ m_waitCondition.wakeOne();
+}
+
+/**
+* Get the generated audio filename from synthText.
+* @return Name of the audio file the plugin generated.
+* Null if no such file.
+*
+* The plugin must not re-use the filename.
+*/
+QString ThreadedPlugIn::getFilename()
+{
+ return m_filename;
+}
+
+/**
+* Stop current operation (saying or synthesizing text).
+* This function only makes sense in asynchronus modes.
+* The plugin should return to the psIdle state.
+*/
+void ThreadedPlugIn::stopText()
+{
+ // If thread is busy, call stopText and wait for thread to stop being busy.
+ if (m_threadRunningMutex.locked())
+ {
+ kdDebug() << "ThreadedPlugIn::stopText:: calling m_plugin->stopText" << endl;
+ m_plugin->stopText();
+ // Set flag that will force state to idle once the plugin finishes.
+ m_waitingStop = true;
+// waitThreadNotBusy();
+ } else m_state = psIdle;
+}
+
+/**
+* Return the current state of the plugin.
+* This function only makes sense in asynchronous mode.
+* @return The pluginState of the plugin.
+*
+* @ref pluginState
+*/
+pluginState ThreadedPlugIn::getState()
+{
+ m_stateMutex.unlock();
+ bool emitStopped = false;
+ // If stopText was called, plugin may not have truly stopped, in which
+ // case, if has finally completed the operation, return idle state.
+ if (m_waitingStop)
+ {
+ if (m_state == psFinished) m_state = psIdle;
+ if (m_state == psIdle)
+ {
+ m_waitingStop = false;
+ emitStopped = true;
+ }
+ }
+ pluginState plugState = m_state;
+ m_stateMutex.unlock();
+ if (emitStopped) emit stopped();
+ return plugState;
+}
+
+/**
+* Acknowleges a finished state and resets the plugin state to psIdle.
+*
+* If the plugin is not in state psFinished, nothing happens.
+* The plugin may use this call to do any post-processing cleanup,
+* for example, blanking the stored filename (but do not delete the file).
+* Calling program should call getFilename prior to ackFinished.
+*/
+void ThreadedPlugIn::ackFinished()
+{
+ // Since plugin should not be running, don't bother with Mutex here.
+ if (m_state == psFinished) m_state = psIdle;
+ m_filename = QString::null;
+}
+
+/**
+* Returns True if the plugin supports asynchronous processing,
+* i.e., returns immediately from sayText or synthText.
+* @return True if this plugin supports asynchronous processing.
+*
+* Since this is a threaded wrapper, return True.
+*/
+bool ThreadedPlugIn::supportsAsync() { return true; }
+
+/**
+* Returns True if the plugin supports synthText method,
+* i.e., is able to synthesize text to a sound file without
+* audibilizing the text.
+* @return True if this plugin supports synthText method.
+*/
+bool ThreadedPlugIn::supportsSynth() { return m_supportsSynth; }
+
+/**
+* Waits for the thread to go to sleep.
+*/
+void ThreadedPlugIn::waitThreadNotBusy()
+{
+ m_threadRunningMutex.lock();
+ m_threadRunningMutex.unlock();
+}
+
+/**
+* Base function, where the thread will start.
+*/
+void ThreadedPlugIn::run()
+{
+ while (!m_requestExit)
+ {
+
+ if (!m_threadRunningMutex.locked()) m_threadRunningMutex.lock();
+ // Go to sleep until asked to do something.
+ // Mutex unlocks as we go to sleep and locks as we wake up.
+ kdDebug() << "ThreadedPlugIn::run going to sleep." << endl;
+ m_waitCondition.wait(&m_threadRunningMutex);
+ kdDebug() << "ThreadedPlugIn::run waking up." << endl;
+ // Woken up.
+ // See if we've been told to exit.
+ if (m_requestExit)
+ {
+ m_threadRunningMutex.unlock();
+ return;
+ }
+
+ // Branch on requested action.
+ switch( m_action )
+ {
+ case paNone: break;
+
+ case paSayText:
+ {
+ m_stateMutex.lock();
+ m_state = psSaying;
+ m_stateMutex.unlock();
+ kdDebug() << "ThreadedPlugIn::run calling sayText" << endl;
+ m_plugin->sayText(m_text);
+ kdDebug() << "ThreadedPlugIn::run back from sayText" << endl;
+ m_stateMutex.lock();
+ if (m_state == psSaying) m_state = psFinished;
+ m_stateMutex.unlock();
+ emit sayFinished();
+ break;
+ }
+
+ case paSynthText:
+ {
+ m_stateMutex.lock();
+ m_state = psSynthing;
+ m_stateMutex.unlock();
+ QString filename = m_filename;
+ m_filename = QString::null;
+ kdDebug() << "ThreadedPlugIn::run calling synthText" << endl;
+ m_plugin->synthText(m_text, filename);
+ kdDebug() << "ThreadedPlugIn::run back from synthText" << endl;
+ m_filename = m_plugin->getFilename();
+ m_stateMutex.lock();
+ if (m_state == psSynthing) m_state = psFinished;
+ m_stateMutex.unlock();
+ emit synthFinished();
+ break;
+ }
+ }
+ }
+ if (m_threadRunningMutex.locked()) m_threadRunningMutex.unlock();
+}