Fix include
[kdeaccessibility.git] / kttsd / plugins / hadifix / hadifixproc.cpp
blob4a134c65ec4fbe270c755c2e55e67acccc906b56
1 /***************************************************************************
2 hadifixproc.cpp - description
3 -------------------
4 begin : Mon Okt 14 2002
5 copyright : (C) 2002 by Gunnar Schmi Dt
6 email : gunnar@schmi-dt.de
7 current mainainer: : Gary Cramblitt <garycramblitt@comcast.net>
8 ***************************************************************************/
10 /***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
19 // Qt includes
21 #include <QTextCodec>
22 #include <QByteArray>
24 // KDE includes
25 #include <kdebug.h>
26 #include <kconfig.h>
27 #include <k3process.h>
28 #include <kstandarddirs.h>
30 #include "hadifixproc.h"
31 #include "hadifixproc.moc"
33 class HadifixProcPrivate {
34 friend class HadifixProc;
35 private:
36 HadifixProcPrivate () {
37 hadifixProc = 0;
38 waitingStop = false;
39 state = psIdle;
40 synthFilename.clear();
41 gender = false;
42 volume = 100;
43 time = 100;
44 pitch = 100;
45 codec = 0;
48 ~HadifixProcPrivate() {
49 delete hadifixProc;
52 void load(KConfig *c, const QString &configGroup) {
53 KConfigGroup config(c, configGroup);
54 hadifix = config.readEntry ("hadifixExec", QString());
55 mbrola = config.readEntry ("mbrolaExec", QString());
56 voice = config.readEntry ("voice", QString());
57 gender = config.readEntry("gender", false);
58 volume = config.readEntry ("volume", 100);
59 time = config.readEntry ("time", 100);
60 pitch = config.readEntry ("pitch", 100);
61 codec = PlugInProc::codecNameToCodec(config.readEntry ("codec", "Local"));
64 QString hadifix;
65 QString mbrola;
66 QString voice;
67 bool gender;
68 int volume;
69 int time;
70 int pitch;
72 bool waitingStop;
73 K3ShellProcess* hadifixProc;
74 volatile pluginState state;
75 QTextCodec* codec;
76 QString synthFilename;
79 /** Constructor */
80 HadifixProc::HadifixProc( QObject* parent, const QStringList &) :
81 PlugInProc( parent, "hadifixproc" ){
82 // kDebug() << "HadifixProc::HadifixProc: Running" << endl;
83 d = 0;
86 /** Destructor */
87 HadifixProc::~HadifixProc(){
88 // kDebug() << "HadifixProc::~HadifixProc: Running" << endl;
90 if (d != 0) {
91 delete d;
92 d = 0;
96 /** Initializate the speech */
97 bool HadifixProc::init(KConfig *config, const QString &configGroup){
98 // kDebug() << "HadifixProc::init: Initializing plug in: Hadifix" << endl;
100 if (d == 0)
101 d = new HadifixProcPrivate();
102 d->load(config, configGroup);
103 return true;
106 /**
107 * Say a text. Synthesize and audibilize it.
108 * @param text The text to be spoken.
110 * If the plugin supports asynchronous operation, it should return immediately
111 * and emit sayFinished signal when synthesis and audibilizing is finished.
112 * It must also implement the @ref getState method, which must return
113 * psFinished, when saying is completed.
115 void HadifixProc::sayText(const QString& /*text*/)
117 kDebug() << "HadifixProc::sayText: Warning, sayText not implemented." << endl;
118 return;
122 * Synthesize text into an audio file, but do not send to the audio device.
123 * @param text The text to be synthesized.
124 * @param suggestedFilename Full pathname of file to create. The plugin
125 * may ignore this parameter and choose its own
126 * filename. KTTSD will query the generated
127 * filename using getFilename().
129 * If the plugin supports asynchronous operation, it should return immediately
130 * and emit @ref synthFinished signal when synthesis is completed.
131 * It must also implement the @ref getState method, which must return
132 * psFinished, when synthesis is completed.
134 void HadifixProc::synthText(const QString &text, const QString &suggestedFilename)
136 if (d == 0) return; // Caller should have called init.
137 synth(text, d->hadifix, d->gender, d->mbrola, d->voice, d->volume,
138 d->time, d->pitch, d->codec, suggestedFilename);
142 * Synthesize text using a specified configuration.
143 * @param text The text to synthesize.
144 * @param hadifix Command to run hadifix (txt2pho).
145 * @param isMale True to use male voice.
146 * @param mbrola Command to run mbrola.
147 * @param voice Voice file for mbrola to use.
148 * @param volume Volume percent. 100 = normal
149 * @param time Speed percent. 100 = normal
150 * @param pitch Frequency. 100 = normal
151 * @param waveFilename Name of file to synthesize to.
153 void HadifixProc::synth(QString text,
154 QString hadifix, bool isMale,
155 QString mbrola, QString voice,
156 int volume, int time, int pitch,
157 QTextCodec *codec,
158 const QString waveFilename)
160 // kDebug() << "HadifixProc::synth: Saying text: '" << text << "' using Hadifix plug in" << endl;
161 if (d == 0)
163 d = new HadifixProcPrivate();
165 if (hadifix.isNull() || hadifix.isEmpty())
166 return;
167 if (mbrola.isNull() || mbrola.isEmpty())
168 return;
169 if (voice.isNull() || voice.isEmpty())
170 return;
172 // If process exists, delete it so we can create a new one.
173 // kDebug() << "HadifixProc::synth: creating process" << endl;
174 if (d->hadifixProc) delete d->hadifixProc;
176 // Create process.
177 d->hadifixProc = new K3ShellProcess;
179 // Set up txt2pho and mbrola commands.
180 // kDebug() << "HadifixProc::synth: setting up commands" << endl;
181 QString hadifixCommand = d->hadifixProc->quote(hadifix);
182 if (isMale)
183 hadifixCommand += " -m";
184 else
185 hadifixCommand += " -f";
187 QString mbrolaCommand = d->hadifixProc->quote(mbrola);
188 mbrolaCommand += " -e"; //Ignore fatal errors on unknown diphone
189 mbrolaCommand += QString(" -v %1").arg(volume/100.0); // volume ratio
190 mbrolaCommand += QString(" -f %1").arg(pitch/100.0); // freqency ratio
191 mbrolaCommand += QString(" -t %1").arg(1/(time/100.0)); // time ratio
192 mbrolaCommand += ' ' + d->hadifixProc->quote(voice);
193 mbrolaCommand += " - " + d->hadifixProc->quote(waveFilename);
195 // kDebug() << "HadifixProc::synth: Hadifix command: " << hadifixCommand << endl;
196 // kDebug() << "HadifixProc::synth: Mbrola command: " << mbrolaCommand << endl;
198 QString command = hadifixCommand + '|' + mbrolaCommand;
199 *(d->hadifixProc) << command;
201 // Connect signals from process.
202 connect(d->hadifixProc, SIGNAL(processExited(K3Process *)),
203 this, SLOT(slotProcessExited(K3Process *)));
204 connect(d->hadifixProc, SIGNAL(wroteStdin(K3Process *)),
205 this, SLOT(slotWroteStdin(K3Process *)));
207 // Store off name of wave file to be generated.
208 d->synthFilename = waveFilename;
209 // Set state, busy synthing.
210 d->state = psSynthing;
211 if (!d->hadifixProc->start(K3Process::NotifyOnExit, K3Process::Stdin))
213 kDebug() << "HadifixProc::synth: start process failed." << endl;
214 d->state = psIdle;
215 } else {
216 QByteArray encodedText;
217 if (codec) {
218 encodedText = codec->fromUnicode(text);
219 // kDebug() << "HadifixProc::synth: encoding using " << codec->name() << endl;
220 } else
221 encodedText = text.toLatin1(); // Should not happen, but just in case.
222 // Send the text to be synthesized to process.
223 d->hadifixProc->writeStdin(encodedText, encodedText.length());
228 * Get the generated audio filename from call to @ref synthText.
229 * @return Name of the audio file the plugin generated.
230 * Null if no such file.
232 * The plugin must not re-use or delete the filename. The file may not
233 * be locked when this method is called. The file will be deleted when
234 * KTTSD is finished using it.
236 QString HadifixProc::getFilename() { return d->synthFilename; }
239 * Stop current operation (saying or synthesizing text).
240 * Important: This function may be called from a thread different from the
241 * one that called sayText or synthText.
242 * If the plugin cannot stop an in-progress @ref sayText or
243 * @ref synthText operation, it must not block waiting for it to complete.
244 * Instead, return immediately.
246 * If a plugin returns before the operation has actually been stopped,
247 * the plugin must emit the @ref stopped signal when the operation has
248 * actually stopped.
250 * The plugin should change to the psIdle state after stopping the
251 * operation.
253 void HadifixProc::stopText(){
254 // kDebug() << "Running: HadifixProc::stopText()" << endl;
255 if (d->hadifixProc)
257 if (d->hadifixProc->isRunning())
259 // kDebug() << "HadifixProc::stopText: killing Hadifix shell." << endl;
260 d->waitingStop = true;
261 d->hadifixProc->kill();
262 } else d->state = psIdle;
263 } else d->state = psIdle;
264 // d->state = psIdle;
265 // kDebug() << "HadifixProc::stopText: Hadifix stopped." << endl;
269 * Return the current state of the plugin.
270 * This function only makes sense in asynchronous mode.
271 * @return The pluginState of the plugin.
273 * @see pluginState
275 pluginState HadifixProc::getState() { return d->state; }
278 * Acknowledges a finished state and resets the plugin state to psIdle.
280 * If the plugin is not in state psFinished, nothing happens.
281 * The plugin may use this call to do any post-processing cleanup,
282 * for example, blanking the stored filename (but do not delete the file).
283 * Calling program should call getFilename prior to ackFinished.
285 void HadifixProc::ackFinished()
287 if (d->state == psFinished)
289 d->state = psIdle;
290 d->synthFilename.clear();
295 * Returns True if the plugin supports asynchronous processing,
296 * i.e., returns immediately from sayText or synthText.
297 * @return True if this plugin supports asynchronous processing.
299 * If the plugin returns True, it must also implement @ref getState .
300 * It must also emit @ref sayFinished or @ref synthFinished signals when
301 * saying or synthesis is completed.
303 bool HadifixProc::supportsAsync() { return true; }
306 * Returns True if the plugin supports synthText method,
307 * i.e., is able to synthesize text to a sound file without
308 * audibilizing the text.
309 * @return True if this plugin supports synthText method.
311 * If the plugin returns True, it must also implement the following methods:
312 * - @ref synthText
313 * - @ref getFilename
314 * - @ref ackFinished
316 * If the plugin returns True, it need not implement @ref sayText .
318 bool HadifixProc::supportsSynth() { return true; }
321 void HadifixProc::slotProcessExited(K3Process*)
323 // kDebug() << "HadifixProc:hadifixProcExited: Hadifix process has exited." << endl;
324 pluginState prevState = d->state;
325 if (d->waitingStop)
327 d->waitingStop = false;
328 d->state = psIdle;
329 emit stopped();
330 } else {
331 d->state = psFinished;
332 if (prevState == psSynthing)
333 emit synthFinished();
337 void HadifixProc::slotWroteStdin(K3Process*)
339 // kDebug() << "HadifixProc::slotWroteStdin: closing Stdin" << endl;
340 d->hadifixProc->closeStdin();
344 /***************************************************************************/
347 * Static function to determine whether the voice file is male or female.
348 * @param mbrola the mbrola executable
349 * @param voice the voice file
350 * @param output the output of mbrola will be written into this QString*
351 * @return HadifixSpeech::MaleGender if the voice is male,
352 * HadifixSpeech::FemaleGender if the voice is female,
353 * HadifixSpeech::NoGender if the gender cannot be determined,
354 * HadifixSpeech::NoVoice if there is an error in the voice file
356 HadifixProc::VoiceGender HadifixProc::determineGender(QString mbrola, QString voice, QString *output)
358 QString command = mbrola + " -i " + voice + " - -";
360 // create a new process
361 HadifixProc speech;
362 K3ShellProcess proc;
363 proc << command;
364 connect(&proc, SIGNAL(receivedStdout(K3Process *, char *, int)),
365 &speech, SLOT(receivedStdout(K3Process *, char *, int)));
366 connect(&proc, SIGNAL(receivedStderr(K3Process *, char *, int)),
367 &speech, SLOT(receivedStderr(K3Process *, char *, int)));
369 speech.stdOut.clear();
370 speech.stdErr.clear();
371 proc.start (K3Process::Block, K3Process::AllOutput);
373 VoiceGender result;
374 if (!speech.stdErr.isNull() && !speech.stdErr.isEmpty()) {
375 if (output != 0)
376 *output = speech.stdErr;
377 result = NoVoice;
379 else {
380 if (output != 0)
381 *output = speech.stdOut;
382 if (speech.stdOut.contains("female", Qt::CaseInsensitive))
383 result = FemaleGender;
384 else if (speech.stdOut.contains("male", Qt::CaseInsensitive))
385 result = MaleGender;
386 else
387 result = NoGender;
390 return result;
393 void HadifixProc::receivedStdout (K3Process *, char *buffer, int buflen) {
394 stdOut += QString::fromLatin1(buffer, buflen);
397 void HadifixProc::receivedStderr (K3Process *, char *buffer, int buflen) {
398 stdErr += QString::fromLatin1(buffer, buflen);
402 * Returns the name of an XSLT stylesheet that will convert a valid SSML file
403 * into a format that can be processed by the synth. For example,
404 * The Festival plugin returns a stylesheet that will convert SSML into
405 * SABLE. Any tags the synth cannot handle should be stripped (leaving
406 * their text contents though). The default stylesheet strips all
407 * tags and converts the file to plain text.
408 * @return Name of the XSLT file.
410 QString HadifixProc::getSsmlXsltFilename()
412 return KGlobal::dirs()->resourceDirs("data").last() + "kttsd/hadifix/xslt/SSMLtoTxt2pho.xsl";