1 /***************************************************** vim:set ts=4 sw=4 sts=4:
3 Main speaking functions for the Epos Plug in
6 (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net>
8 Original author: Gary Cramblitt <garycramblitt@comcast.net>
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 ******************************************************************************/
32 #include <QTextStream>
37 #include <kstandarddirs.h>
38 #include <k3process.h>
40 // Epos Plugin includes.
42 #include "eposproc.moc"
45 EposProc::EposProc( QObject
* parent
, const QStringList
& ) :
46 PlugInProc( parent
, "eposproc" ){
47 kDebug() << "EposProc::EposProc: Running" << endl
;
49 m_waitingStop
= false;
55 EposProc::~EposProc(){
56 kDebug() << "EposProc::~EposProc:: Running" << endl
;
62 delete m_eposServerProc
;
65 /** Initialize the speech */
66 bool EposProc::init(KConfig
* c
, const QString
& configGroup
)
68 // kDebug() << "EposProc::init: Running" << endl;
69 // kDebug() << "Initializing plug in: Epos" << endl;
70 // Retrieve path to epos executable.
71 KConfigGroup
config(c
, configGroup
);
72 m_eposServerExePath
= config
.readEntry("EposServerExePath", "epos");
73 m_eposClientExePath
= config
.readEntry("EposClientExePath", "say");
74 m_eposLanguage
= config
.readEntry("Language", QString());
75 m_time
= config
.readEntry("time", 100);
76 m_pitch
= config
.readEntry("pitch", 100);
77 m_eposServerOptions
= config
.readEntry("EposServerOptions", QString());
78 m_eposClientOptions
= config
.readEntry("EposClientOptions", QString());
79 kDebug() << "EposProc::init: path to epos server: " << m_eposServerExePath
<< endl
;
80 kDebug() << "EposProc::init: path to epos client: " << m_eposClientExePath
<< endl
;
82 QString codecString
= config
.readEntry("Codec", "Local");
83 m_codec
= codecNameToCodec(codecString
);
84 // Start the Epos server if not already started.
85 if (!m_eposServerProc
)
87 K3Process
* m_eposServerProc
= new K3Process
;
88 *m_eposServerProc
<< m_eposServerExePath
;
89 if (!m_eposServerOptions
.isEmpty())
90 *m_eposServerProc
<< m_eposServerOptions
;
91 connect(m_eposServerProc
, SIGNAL(receivedStdout(K3Process
*, char*, int)),
92 this, SLOT(slotReceivedStdout(K3Process
*, char*, int)));
93 connect(m_eposServerProc
, SIGNAL(receivedStderr(K3Process
*, char*, int)),
94 this, SLOT(slotReceivedStderr(K3Process
*, char*, int)));
95 m_eposServerProc
->start(K3Process::DontCare
, K3Process::AllOutput
);
98 kDebug() << "EposProc::init: Initialized with codec: " << codecString
<< endl
;
104 * Say a text. Synthesize and audibilize it.
105 * @param text The text to be spoken.
107 * If the plugin supports asynchronous operation, it should return immediately.
109 void EposProc::sayText(const QString
&text
)
111 synth(text
, QString(), m_eposServerExePath
, m_eposClientExePath
,
112 m_eposServerOptions
, m_eposClientOptions
,
113 m_codec
, m_eposLanguage
, m_time
, m_pitch
);
117 * Synthesize text into an audio file, but do not send to the audio device.
118 * @param text The text to be synthesized.
119 * @param suggestedFilename Full pathname of file to create. The plugin
120 * may ignore this parameter and choose its own
121 * filename. KTTSD will query the generated
122 * filename using getFilename().
124 * If the plugin supports asynchronous operation, it should return immediately.
126 void EposProc::synthText(const QString
& text
, const QString
& suggestedFilename
)
128 synth(text
, suggestedFilename
, m_eposServerExePath
, m_eposClientExePath
,
129 m_eposServerOptions
, m_eposClientOptions
,
130 m_codec
, m_eposLanguage
, m_time
, m_pitch
);
134 * Say or Synthesize text.
135 * @param text The text to be synthesized.
136 * @param suggestedFilename If not Null, synthesize only to this filename, otherwise
137 * synthesize and audibilize the text.
138 * @param eposServerExePath Path to the Epos server executable.
139 * @param eposClientExePath Path to the Epos client executable.
140 * @param eposServerOptions Options passed to Epos server executable.
141 * @param eposClientOptions Options passed to Epos client executable (don't include -o).
142 * @param codec Codec for encoding of text.
143 * @param eposLanguage Epos language setting. "czech", "slovak",
144 * or null (default language).
145 * @param time Speed percentage. 50 to 200. 200% = 2x normal.
146 * @param pitch Pitch persentage. 50 to 200.
148 void EposProc::synth(
150 const QString
&suggestedFilename
,
151 const QString
& eposServerExePath
,
152 const QString
& eposClientExePath
,
153 const QString
& eposServerOptions
,
154 const QString
& eposClientOptions
,
156 const QString
& eposLanguage
,
160 // kDebug() << "Running: EposProc::synth(const QString &text)" << endl;
164 if (m_eposProc
->isRunning()) m_eposProc
->kill();
168 // Start the Epos server if not already started.
169 if (!m_eposServerProc
)
171 K3Process
* m_eposServerProc
= new K3Process
;
172 *m_eposServerProc
<< eposServerExePath
;
173 if (!eposServerOptions
.isEmpty())
174 *m_eposServerProc
<< eposServerOptions
;
175 connect(m_eposServerProc
, SIGNAL(receivedStdout(K3Process
*, char*, int)),
176 this, SLOT(slotReceivedStdout(K3Process
*, char*, int)));
177 connect(m_eposServerProc
, SIGNAL(receivedStderr(K3Process
*, char*, int)),
178 this, SLOT(slotReceivedStderr(K3Process
*, char*, int)));
179 m_eposServerProc
->start(K3Process::DontCare
, K3Process::AllOutput
);
183 // 1.a) encode the text
184 m_encText
= QByteArray();
185 QTextStream
ts (m_encText
, QIODevice::WriteOnly
);
188 ts
<< endl
; // Some synths need this, eg. flite.
190 // Quote the text as one parameter.
191 // QString escText = K3ShellProcess::quote(encText);
193 // kDebug()<< "EposProc::synth: Creating Epos object" << endl;
194 m_eposProc
= new K3Process
;
195 m_eposProc
->setUseShell(true);
196 QString languageCode
;
197 if (eposLanguage
== "czech")
198 languageCode
== "cz";
199 else if (eposLanguage
== "slovak")
200 languageCode
== "sk";
201 if (!languageCode
.isEmpty())
203 m_eposProc
->setEnvironment("LANG", languageCode
+ '.' + codec
->name());
204 m_eposProc
->setEnvironment("LC_CTYPE", languageCode
+ '.' + codec
->name());
206 *m_eposProc
<< eposClientExePath
;
208 if (!eposLanguage
.isEmpty())
209 *m_eposProc
<< QString("--language=%1").arg(eposLanguage
);
211 // Map 50% to 200% onto 0 to 1000.
212 // slider = alpha * (log(percent)-log(50))
213 // with alpha = 1000/(log(200)-log(50))
214 double alpha
= 1000 / (log(200) - log(50));
215 int slider
= (int)floor (0.5 + alpha
* (log(time
)-log(50)));
217 slider
= slider
- 500;
218 // Map -500 to 500 onto 45 to -45 then shift to 130 to 40 (85 midpoint).
219 float stretchValue
= (-float(slider
) * 45.0 / 500.0) + 85.0;
220 QString timeMsg
= QString("--init_t=%1").arg(stretchValue
, 0, 'f', 3);
221 *m_eposProc
<< timeMsg
;
222 // Pitch. Map 50% to 200% onto 50 to 200. easy.
223 QString pitchMsg
= QString("--init_f=%1").arg(pitch
);
224 *m_eposProc
<< pitchMsg
;
226 if (!suggestedFilename
.isEmpty())
228 if (!eposClientOptions
.isEmpty())
229 *m_eposProc
<< eposClientOptions
;
230 *m_eposProc
<< "-"; // Read from StdIn.
231 if (!suggestedFilename
.isEmpty())
232 *m_eposProc
<< " >" + suggestedFilename
;
233 connect(m_eposProc
, SIGNAL(processExited(K3Process
*)),
234 this, SLOT(slotProcessExited(K3Process
*)));
235 connect(m_eposProc
, SIGNAL(receivedStdout(K3Process
*, char*, int)),
236 this, SLOT(slotReceivedStdout(K3Process
*, char*, int)));
237 connect(m_eposProc
, SIGNAL(receivedStderr(K3Process
*, char*, int)),
238 this, SLOT(slotReceivedStderr(K3Process
*, char*, int)));
239 connect(m_eposProc
, SIGNAL(wroteStdin(K3Process
*)),
240 this, SLOT(slotWroteStdin(K3Process
* )));
241 if (suggestedFilename
.isEmpty())
244 m_state
= psSynthing
;
247 m_synthFilename
= suggestedFilename
;
248 kDebug() << "EposProc::synth: Synthing text: '" << text
<< "' using Epos plug in" << endl
;
249 if (!m_eposProc
->start(K3Process::NotifyOnExit
, K3Process::All
))
251 kDebug() << "EposProc::synth: Error starting Epos process. Is epos in the PATH?" << endl
;
255 kDebug()<< "EposProc:synth: Epos initialized" << endl
;
256 if (!m_eposProc
->writeStdin(m_encText
, m_encText
.length()))
257 kDebug() << "EposProc::synth: Error writing to Epos client StdIn." << endl
;
261 * Get the generated audio filename from synthText.
262 * @return Name of the audio file the plugin generated.
263 * Null if no such file.
265 * The plugin must not re-use the filename.
267 QString
EposProc::getFilename()
269 kDebug() << "EposProc::getFilename: returning " << m_synthFilename
<< endl
;
270 return m_synthFilename
;
274 * Stop current operation (saying or synthesizing text).
275 * Important: This function may be called from a thread different from the
276 * one that called sayText or synthText.
277 * If the plugin cannot stop an in-progress @ref sayText or
278 * @ref synthText operation, it must not block waiting for it to complete.
279 * Instead, return immediately.
281 * If a plugin returns before the operation has actually been stopped,
282 * the plugin must emit the @ref stopped signal when the operation has
285 * The plugin should change to the psIdle state after stopping the
288 void EposProc::stopText(){
289 kDebug() << "EposProc::stopText:: Running" << endl
;
292 if (m_eposProc
->isRunning())
294 kDebug() << "EposProc::stopText: killing Epos." << endl
;
295 m_waitingStop
= true;
297 } else m_state
= psIdle
;
298 } else m_state
= psIdle
;
299 kDebug() << "EposProc::stopText: Epos stopped." << endl
;
302 void EposProc::slotProcessExited(K3Process
*)
304 kDebug() << "EposProc:slotProcessExited: Epos process has exited." << endl
;
305 pluginState prevState
= m_state
;
308 m_waitingStop
= false;
312 m_state
= psFinished
;
313 if (prevState
== psSaying
)
316 if (prevState
== psSynthing
)
317 emit
synthFinished();
321 void EposProc::slotReceivedStdout(K3Process
*, char* buffer
, int buflen
)
323 QString buf
= QString::fromLatin1(buffer
, buflen
);
324 kDebug() << "EposProc::slotReceivedStdout: Received output from Epos: " << buf
<< endl
;
327 void EposProc::slotReceivedStderr(K3Process
*, char* buffer
, int buflen
)
329 QString buf
= QString::fromLatin1(buffer
, buflen
);
330 kDebug() << "EposProc::slotReceivedStderr: Received error from Epos: " << buf
<< endl
;
333 void EposProc::slotWroteStdin(K3Process
*)
335 kDebug() << "EposProc::slotWroteStdin: closing Stdin" << endl
;
336 m_eposProc
->closeStdin();
337 m_encText
= QByteArray();
341 * Return the current state of the plugin.
342 * This function only makes sense in asynchronous mode.
343 * @return The pluginState of the plugin.
347 pluginState
EposProc::getState() { return m_state
; }
350 * Acknowledges a finished state and resets the plugin state to psIdle.
352 * If the plugin is not in state psFinished, nothing happens.
353 * The plugin may use this call to do any post-processing cleanup,
354 * for example, blanking the stored filename (but do not delete the file).
355 * Calling program should call getFilename prior to ackFinished.
357 void EposProc::ackFinished()
359 if (m_state
== psFinished
)
362 m_synthFilename
.clear();
367 * Returns True if the plugin supports asynchronous processing,
368 * i.e., returns immediately from sayText or synthText.
369 * @return True if this plugin supports asynchronous processing.
371 * If the plugin returns True, it must also implement @ref getState .
372 * It must also emit @ref sayFinished or @ref synthFinished signals when
373 * saying or synthesis is completed.
375 bool EposProc::supportsAsync() { return true; }
378 * Returns True if the plugin supports synthText method,
379 * i.e., is able to synthesize text to a sound file without
380 * audibilizing the text.
381 * @return True if this plugin supports synthText method.
383 bool EposProc::supportsSynth() { return true; }