Packard Bell Vibe 500: correct the path to a proper one in rbutil (proper directory...
[kugel-rb.git] / rbutil / rbutilqt / base / ttscarbon.cpp
bloba74cb23da38091bcdb007692b2dc00a2eed02b7c
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2010 by Dominik Riebeling
10 * $Id$
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
20 #include <QtCore>
21 #include "ttsbase.h"
22 #include "ttscarbon.h"
23 #include "encttssettings.h"
24 #include "rbsettings.h"
26 #include <CoreFoundation/CoreFoundation.h>
27 #include <ApplicationServices/ApplicationServices.h>
28 #include <Carbon/Carbon.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <inttypes.h>
33 TTSCarbon::TTSCarbon(QObject* parent) : TTSBase(parent)
38 bool TTSCarbon::configOk()
40 return true;
44 bool TTSCarbon::start(QString *errStr)
46 (void)errStr;
47 VoiceSpec vspec;
48 VoiceSpec* vspecref;
49 VoiceDescription vdesc;
50 OSErr error;
51 QString selectedVoice
52 = RbSettings::subValue("carbon", RbSettings::TtsVoice).toString();
53 SInt16 numVoices;
54 SInt16 voiceIndex;
55 error = CountVoices(&numVoices);
56 for(voiceIndex = 1; voiceIndex < numVoices; ++voiceIndex) {
57 error = GetIndVoice(voiceIndex, &vspec);
58 error = GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc));
59 // name is pascal string, i.e. the first byte is the length.
60 QString name = QString::fromLocal8Bit((const char*)&vdesc.name[1],
61 vdesc.name[0]);
62 if(name == selectedVoice) {
63 vspecref = &vspec;
64 if(vdesc.script != -1)
65 m_voiceScript = (CFStringBuiltInEncodings)vdesc.script;
66 else
67 m_voiceScript = (CFStringBuiltInEncodings)vdesc.reserved[0];
68 break;
71 if(voiceIndex == numVoices) {
72 // voice not found. Add user notification here and proceed with
73 // system default voice.
74 qDebug() << "selected voice not found, using system default!";
75 vspecref = NULL;
76 GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc));
77 if(vdesc.script != -1)
78 m_voiceScript = (CFStringBuiltInEncodings)vdesc.script;
79 else
80 m_voiceScript = (CFStringBuiltInEncodings)vdesc.reserved[0];
83 error = NewSpeechChannel(vspecref, &m_channel);
84 //SetSpeechInfo(channel, soSpeechDoneCallBack, speechDone);
85 Fixed rate = (Fixed)(0x10000 * RbSettings::subValue("carbon",
86 RbSettings::TtsSpeed).toInt());
87 if(rate != 0)
88 SetSpeechRate(m_channel, rate);
89 return (error == 0) ? true : false;
93 bool TTSCarbon::stop(void)
95 DisposeSpeechChannel(m_channel);
96 return true;
100 void TTSCarbon::generateSettings(void)
102 QStringList voiceNames;
103 QString systemVoice;
104 SInt16 numVoices;
105 OSErr error;
106 VoiceSpec vspec;
107 VoiceDescription vdesc;
109 // get system voice
110 error = GetVoiceDescription(NULL, &vdesc, sizeof(vdesc));
111 systemVoice
112 = QString::fromLocal8Bit((const char*)&vdesc.name[1], vdesc.name[0]);
113 // get list of all voices
114 CountVoices(&numVoices);
115 for(SInt16 i = 1; i < numVoices; ++i) {
116 error = GetIndVoice(i, &vspec);
117 error = GetVoiceDescription(&vspec, &vdesc, sizeof(vdesc));
118 // name is pascal string, i.e. the first byte is the length.
119 QString name
120 = QString::fromLocal8Bit((const char*)&vdesc.name[1], vdesc.name[0]);
121 voiceNames.append(name.trimmed());
123 // voice
124 EncTtsSetting* setting;
125 QString voice
126 = RbSettings::subValue("carbon", RbSettings::TtsVoice).toString();
127 if(voice.isEmpty())
128 voice = systemVoice;
129 setting = new EncTtsSetting(this, EncTtsSetting::eSTRINGLIST,
130 tr("Voice:"), voice, voiceNames, EncTtsSetting::eNOBTN);
131 insertSetting(ConfigVoice, setting);
133 // speed
134 int speed = RbSettings::subValue("carbon", RbSettings::TtsSpeed).toInt();
135 setting = new EncTtsSetting(this, EncTtsSetting::eINT,
136 tr("Speed (words/min):"), speed, 80, 500,
137 EncTtsSetting::eNOBTN);
138 insertSetting(ConfigSpeed, setting);
142 void TTSCarbon::saveSettings(void)
144 // save settings in user config
145 RbSettings::setSubValue("carbon", RbSettings::TtsVoice,
146 getSetting(ConfigVoice)->current().toString());
147 RbSettings::setSubValue("carbon", RbSettings::TtsSpeed,
148 getSetting(ConfigSpeed)->current().toInt());
149 RbSettings::sync();
153 /** @brief create wav file from text using the selected TTS voice.
155 TTSStatus TTSCarbon::voice(QString text, QString wavfile, QString* errStr)
157 TTSStatus status = NoError;
158 OSErr error;
160 QString aifffile = wavfile + ".aiff";
161 // FIXME: find out why we need to do this.
162 // Create a local copy of the temporary file filename.
163 // Not doing so causes weird issues (path contains trailing spaces)
164 unsigned int len = aifffile.size() + 1;
165 char* tmpfile = (char*)malloc(len * sizeof(char));
166 strncpy(tmpfile, aifffile.toLocal8Bit().constData(), len);
167 CFStringRef tmpfileref = CFStringCreateWithCString(kCFAllocatorDefault,
168 tmpfile, kCFStringEncodingUTF8);
169 CFURLRef urlref = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,
170 tmpfileref, kCFURLPOSIXPathStyle, false);
171 SetSpeechInfo(m_channel, soOutputToFileWithCFURL, urlref);
173 // speak it.
174 // Convert the string to the encoding requested by the voice. Do this
175 // via CFString, as this allows to directly use the destination encoding
176 // as CFString uses the same values as the voice.
178 // allocate enough space to allow storing the string in a 2 byte encoding
179 unsigned int textlen = 2 * text.length() + 1;
180 char* textbuf = (char*)calloc(textlen, sizeof(char));
181 char* utf8data = (char*)text.toUtf8().constData();
182 int utf8bytes = text.toUtf8().size();
183 CFStringRef cfstring = CFStringCreateWithBytes(kCFAllocatorDefault,
184 (UInt8*)utf8data, utf8bytes,
185 kCFStringEncodingUTF8, (Boolean)false);
186 CFIndex usedBuf = 0;
187 CFRange range;
188 range.location = 0; // character in string to start.
189 range.length = text.length(); // number of _characters_ in string
190 // FIXME: check if converting between encodings was lossless.
191 CFStringGetBytes(cfstring, range, m_voiceScript, ' ',
192 false, (UInt8*)textbuf, textlen, &usedBuf);
194 error = SpeakText(m_channel, textbuf, (unsigned long)usedBuf);
195 while(SpeechBusy()) {
196 // FIXME: add small delay here to make calls less frequent
197 QCoreApplication::processEvents();
199 if(error != 0) {
200 *errStr = tr("Could not voice string");
201 status = FatalError;
203 free(textbuf);
204 CFRelease(cfstring);
206 // convert the temporary aiff file to wav
207 if(status == NoError
208 && convertAiffToWav(tmpfile, wavfile.toLocal8Bit().constData()) != 0) {
209 *errStr = tr("Could not convert intermediate file");
210 status = FatalError;
212 // remove temporary aiff file
213 unlink(tmpfile);
214 free(tmpfile);
216 return status;
220 unsigned long TTSCarbon::be2u32(unsigned char* buf)
222 return (buf[0]&0xff)<<24 | (buf[1]&0xff)<<16 | (buf[2]&0xff)<<8 | (buf[3]&0xff);
226 unsigned long TTSCarbon::be2u16(unsigned char* buf)
228 return buf[1]&0xff | (buf[0]&0xff)<<8;
232 unsigned char* TTSCarbon::u32tobuf(unsigned char* buf, uint32_t val)
234 buf[0] = val & 0xff;
235 buf[1] = (val>> 8) & 0xff;
236 buf[2] = (val>>16) & 0xff;
237 buf[3] = (val>>24) & 0xff;
238 return buf;
242 unsigned char* TTSCarbon::u16tobuf(unsigned char* buf, uint16_t val)
244 buf[0] = val & 0xff;
245 buf[1] = (val>> 8) & 0xff;
246 return buf;
250 /** @brief convert 80 bit extended ("long double") to int.
251 * This is simplified to handle the usual audio sample rates. Everything else
252 * might break. If the value isn't supported it will return 0.
253 * Conversion taken from Rockbox aiff codec.
255 unsigned int TTSCarbon::extended2int(unsigned char* buf)
257 unsigned int result = 0;
258 /* value negative? */
259 if(buf[0] & 0x80)
260 return 0;
261 /* check exponent. Int can handle up to 2^31. */
262 int exponent = buf[0] << 8 | buf[1];
263 if(exponent < 0x4000 || exponent > (0x4000 + 30))
264 return 0;
265 result = ((buf[2]<<24) | (buf[3]<<16) | (buf[4]<<8) | buf[5]) + 1;
266 result >>= (16 + 14 - buf[1]);
267 return result;
271 /** @brief Convert aiff file to wav. Returns 0 on success.
273 int TTSCarbon::convertAiffToWav(const char* aiff, const char* wav)
275 struct commchunk {
276 unsigned long chunksize;
277 unsigned short channels;
278 unsigned long frames;
279 unsigned short size;
280 int rate;
283 struct ssndchunk {
284 unsigned long chunksize;
285 unsigned long offset;
286 unsigned long blocksize;
289 FILE* in;
290 FILE* out;
291 unsigned char obuf[4];
292 unsigned char* buf;
293 /* minimum file size for a valid aiff file is 46 bytes:
294 * - FORM chunk: 12 bytes
295 * - COMM chunk: 18 bytes
296 * - SSND chunk: 16 bytes (with no actual data)
298 struct stat filestat;
299 stat(aiff, &filestat);
300 if(filestat.st_size < 46)
301 return -1;
302 /* read input file into memory */
303 buf = (unsigned char*)malloc(filestat.st_size * sizeof(unsigned char));
304 if(!buf) /* error out if malloc() failed */
305 return -1;
306 in = fopen(aiff, "rb");
307 if(fread(buf, 1, filestat.st_size, in) < filestat.st_size) {
308 printf("could not read file: not enought bytes read\n");
309 fclose(in);
310 return -1;
312 fclose(in);
314 /* check input file format */
315 if(memcmp(buf, "FORM", 4) | memcmp(&buf[8], "AIFF", 4)) {
316 printf("No valid AIFF header found.\n");
317 free(buf);
318 return -1;
320 /* read COMM chunk */
321 unsigned char* commstart = &buf[12];
322 struct commchunk comm;
323 if(memcmp(commstart, "COMM", 4)) {
324 printf("COMM chunk not at beginning.\n");
325 free(buf);
326 return -1;
328 comm.chunksize = be2u32(&commstart[4]);
329 comm.channels = be2u16(&commstart[8]);
330 comm.frames = be2u32(&commstart[10]);
331 comm.size = be2u16(&commstart[14]);
332 comm.rate = extended2int(&commstart[16]);
334 /* find SSND as next chunk */
335 unsigned char* ssndstart = commstart + 8 + comm.chunksize;
336 while(memcmp(ssndstart, "SSND", 4) && ssndstart < (buf + filestat.st_size)) {
337 printf("Skipping chunk.\n");
338 ssndstart += be2u32(&ssndstart[4]) + 8;
340 if(ssndstart > (buf + filestat.st_size)) {
341 free(buf);
342 return -1;
345 struct ssndchunk ssnd;
346 ssnd.chunksize = be2u32(&ssndstart[4]);
347 ssnd.offset = be2u32(&ssndstart[8]);
348 ssnd.blocksize = be2u32(&ssndstart[12]);
350 /* Calculate the total length of the resulting RIFF chunk.
351 * The length is given by frames * samples * bytes/sample.
352 * We need to add:
353 * - 16 bytes: fmt chunk header
354 * - 8 bytes: data chunk header
355 * - 4 bytes: wave chunk identifier
357 out = fopen(wav, "wb+");
359 /* write the wav header */
360 unsigned short blocksize = comm.channels * (comm.size >> 3);
361 unsigned long rifflen = blocksize * comm.frames + 28;
362 fwrite("RIFF", 1, 4, out);
363 fwrite(u32tobuf(obuf, rifflen), 1, 4, out);
364 fwrite("WAVE", 1, 4, out);
366 /* write the fmt chunk and chunk size (always 16) */
367 /* write fmt chunk header:
368 * header, size (always 0x10, format code (always 0x0001)
370 fwrite("fmt \x10\x00\x00\x00\x01\x00", 1, 10, out);
371 /* number of channels (2 bytes) */
372 fwrite(u16tobuf(obuf, comm.channels), 1, 2, out);
373 /* sampling rate (4 bytes) */
374 fwrite(u32tobuf(obuf, comm.rate), 1, 4, out);
376 /* data rate, i.e. bytes/sec */
377 fwrite(u32tobuf(obuf, comm.rate * blocksize), 1, 4, out);
379 /* data block size */
380 fwrite(u16tobuf(obuf, blocksize), 1, 2, out);
382 /* bits per sample */
383 fwrite(u16tobuf(obuf, comm.size), 1, 2, out);
385 /* write the data chunk */
386 /* chunk id */
387 fwrite("data", 1, 4, out);
388 /* chunk size: 4 bytes. */
389 unsigned long cs = blocksize * comm.frames;
390 fwrite(u32tobuf(obuf, cs), 1, 4, out);
392 /* write data */
393 unsigned char* data = ssndstart;
394 unsigned long pos = ssnd.chunksize;
395 /* byteswap if samples are 16 bit */
396 if(comm.size == 16) {
397 while(pos) {
398 obuf[1] = *data++ & 0xff;
399 obuf[0] = *data++ & 0xff;
400 fwrite(obuf, 1, 2, out);
401 pos -= 2;
404 /* 8 bit samples have need no conversion so we can bulk copy.
405 * Everything that is not 16 bit is considered 8. */
406 else {
407 fwrite(data, 1, pos, out);
409 /* number of bytes has to be even, even if chunksize is not. */
410 if(cs % 2) {
411 fwrite(obuf, 1, 1, out);
414 fclose(out);
415 free(buf);
416 return 0;