Fix include
[kdeaccessibility.git] / kttsd / plugins / epos / eposproc.cpp
blob55899108415b2f69aa595043f35512c1c1cc7198
1 /***************************************************** vim:set ts=4 sw=4 sts=4:
2 eposproc.cpp
3 Main speaking functions for the Epos Plug in
4 -------------------
5 Copyright:
6 (C) 2004 by Gary Cramblitt <garycramblitt@comcast.net>
7 -------------------
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 ******************************************************************************/
25 // C++ includes.
26 #include <math.h>
28 // Qt includes.
30 #include <QTextCodec>
31 #include <QFile>
32 #include <QTextStream>
34 // KDE includes.
35 #include <kdebug.h>
36 #include <kconfig.h>
37 #include <kstandarddirs.h>
38 #include <k3process.h>
40 // Epos Plugin includes.
41 #include "eposproc.h"
42 #include "eposproc.moc"
44 /** Constructor */
45 EposProc::EposProc( QObject* parent, const QStringList& ) :
46 PlugInProc( parent, "eposproc" ){
47 kDebug() << "EposProc::EposProc: Running" << endl;
48 m_state = psIdle;
49 m_waitingStop = false;
50 m_eposServerProc = 0;
51 m_eposProc = 0;
54 /** Destructor */
55 EposProc::~EposProc(){
56 kDebug() << "EposProc::~EposProc:: Running" << endl;
57 if (m_eposProc)
59 stopText();
60 delete m_eposProc;
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;
100 return true;
103 /**
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(
149 const QString &text,
150 const QString &suggestedFilename,
151 const QString& eposServerExePath,
152 const QString& eposClientExePath,
153 const QString& eposServerOptions,
154 const QString& eposClientOptions,
155 QTextCodec *codec,
156 const QString& eposLanguage,
157 int time,
158 int pitch)
160 // kDebug() << "Running: EposProc::synth(const QString &text)" << endl;
162 if (m_eposProc)
164 if (m_eposProc->isRunning()) m_eposProc->kill();
165 delete m_eposProc;
166 m_eposProc = 0;
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);
182 // Encode the text.
183 // 1.a) encode the text
184 m_encText = QByteArray();
185 QTextStream ts (m_encText, QIODevice::WriteOnly);
186 ts.setCodec(codec);
187 ts << text;
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;
207 // Language.
208 if (!eposLanguage.isEmpty())
209 *m_eposProc << QString("--language=%1").arg(eposLanguage);
210 // Rate (speed).
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)));
216 // Center at 0.
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;
225 // Output file.
226 if (!suggestedFilename.isEmpty())
227 *m_eposProc << "-o";
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())
242 m_state = psSaying;
243 else
244 m_state = psSynthing;
246 // Ok, let's rock.
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;
252 m_state = psIdle;
253 return;
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
283 * actually stopped.
285 * The plugin should change to the psIdle state after stopping the
286 * operation.
288 void EposProc::stopText(){
289 kDebug() << "EposProc::stopText:: Running" << endl;
290 if (m_eposProc)
292 if (m_eposProc->isRunning())
294 kDebug() << "EposProc::stopText: killing Epos." << endl;
295 m_waitingStop = true;
296 m_eposProc->kill();
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;
306 if (m_waitingStop)
308 m_waitingStop = false;
309 m_state = psIdle;
310 emit stopped();
311 } else {
312 m_state = psFinished;
313 if (prevState == psSaying)
314 emit sayFinished();
315 else
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.
345 * @see pluginState
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)
361 m_state = psIdle;
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; }