1 /***************************************************************************
2 hadifixproc.cpp - description
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 /***************************************************************************
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. *
17 ***************************************************************************/
27 #include <k3process.h>
28 #include <kstandarddirs.h>
30 #include "hadifixproc.h"
31 #include "hadifixproc.moc"
33 class HadifixProcPrivate
{
34 friend class HadifixProc
;
36 HadifixProcPrivate () {
40 synthFilename
.clear();
48 ~HadifixProcPrivate() {
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"));
73 K3ShellProcess
* hadifixProc
;
74 volatile pluginState state
;
76 QString synthFilename
;
80 HadifixProc::HadifixProc( QObject
* parent
, const QStringList
&) :
81 PlugInProc( parent
, "hadifixproc" ){
82 // kDebug() << "HadifixProc::HadifixProc: Running" << endl;
87 HadifixProc::~HadifixProc(){
88 // kDebug() << "HadifixProc::~HadifixProc: Running" << endl;
96 /** Initializate the speech */
97 bool HadifixProc::init(KConfig
*config
, const QString
&configGroup
){
98 // kDebug() << "HadifixProc::init: Initializing plug in: Hadifix" << endl;
101 d
= new HadifixProcPrivate();
102 d
->load(config
, configGroup
);
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
;
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
,
158 const QString waveFilename
)
160 // kDebug() << "HadifixProc::synth: Saying text: '" << text << "' using Hadifix plug in" << endl;
163 d
= new HadifixProcPrivate();
165 if (hadifix
.isNull() || hadifix
.isEmpty())
167 if (mbrola
.isNull() || mbrola
.isEmpty())
169 if (voice
.isNull() || voice
.isEmpty())
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
;
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
);
183 hadifixCommand
+= " -m";
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
;
216 QByteArray encodedText
;
218 encodedText
= codec
->fromUnicode(text
);
219 // kDebug() << "HadifixProc::synth: encoding using " << codec->name() << endl;
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
250 * The plugin should change to the psIdle state after stopping the
253 void HadifixProc::stopText(){
254 // kDebug() << "Running: HadifixProc::stopText()" << endl;
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.
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
)
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:
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
;
327 d
->waitingStop
= false;
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
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
);
374 if (!speech
.stdErr
.isNull() && !speech
.stdErr
.isEmpty()) {
376 *output
= speech
.stdErr
;
381 *output
= speech
.stdOut
;
382 if (speech
.stdOut
.contains("female", Qt::CaseInsensitive
))
383 result
= FemaleGender
;
384 else if (speech
.stdOut
.contains("male", Qt::CaseInsensitive
))
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";