1 /***************************************************************************
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
9 * Copyright (C) 2010 by Dominik Riebeling
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 ****************************************************************************/
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>
33 TTSCarbon::TTSCarbon(QObject
* parent
) : TTSBase(parent
)
38 bool TTSCarbon::configOk()
44 bool TTSCarbon::start(QString
*errStr
)
49 VoiceDescription vdesc
;
52 = RbSettings::subValue("carbon", RbSettings::TtsVoice
).toString();
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],
62 if(name
== selectedVoice
) {
64 if(vdesc
.script
!= -1)
65 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.script
;
67 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.reserved
[0];
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!";
76 GetVoiceDescription(&vspec
, &vdesc
, sizeof(vdesc
));
77 if(vdesc
.script
!= -1)
78 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.script
;
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());
88 SetSpeechRate(m_channel
, rate
);
89 return (error
== 0) ? true : false;
93 bool TTSCarbon::stop(void)
95 DisposeSpeechChannel(m_channel
);
100 void TTSCarbon::generateSettings(void)
102 QStringList voiceNames
;
107 VoiceDescription vdesc
;
110 error
= GetVoiceDescription(NULL
, &vdesc
, sizeof(vdesc
));
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.
120 = QString::fromLocal8Bit((const char*)&vdesc
.name
[1], vdesc
.name
[0]);
121 voiceNames
.append(name
.trimmed());
124 EncTtsSetting
* setting
;
126 = RbSettings::subValue("carbon", RbSettings::TtsVoice
).toString();
129 setting
= new EncTtsSetting(this, EncTtsSetting::eSTRINGLIST
,
130 tr("Voice:"), voice
, voiceNames
, EncTtsSetting::eNOBTN
);
131 insertSetting(ConfigVoice
, setting
);
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());
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
;
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
);
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);
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();
200 *errStr
= tr("Could not voice string");
206 // convert the temporary aiff file to wav
208 && convertAiffToWav(tmpfile
, wavfile
.toLocal8Bit().constData()) != 0) {
209 *errStr
= tr("Could not convert intermediate file");
212 // remove temporary aiff file
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
)
235 buf
[1] = (val
>> 8) & 0xff;
236 buf
[2] = (val
>>16) & 0xff;
237 buf
[3] = (val
>>24) & 0xff;
242 unsigned char* TTSCarbon::u16tobuf(unsigned char* buf
, uint16_t val
)
245 buf
[1] = (val
>> 8) & 0xff;
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? */
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))
265 result
= ((buf
[2]<<24) | (buf
[3]<<16) | (buf
[4]<<8) | buf
[5]) + 1;
266 result
>>= (16 + 14 - buf
[1]);
271 /** @brief Convert aiff file to wav. Returns 0 on success.
273 int TTSCarbon::convertAiffToWav(const char* aiff
, const char* wav
)
276 unsigned long chunksize
;
277 unsigned short channels
;
278 unsigned long frames
;
284 unsigned long chunksize
;
285 unsigned long offset
;
286 unsigned long blocksize
;
291 unsigned char obuf
[4];
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)
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 */
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");
314 /* check input file format */
315 if(memcmp(buf
, "FORM", 4) | memcmp(&buf
[8], "AIFF", 4)) {
316 printf("No valid AIFF header found.\n");
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");
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
)) {
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.
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 */
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
);
393 unsigned char* data
= ssndstart
;
394 unsigned long pos
= ssnd
.chunksize
;
395 /* byteswap if samples are 16 bit */
396 if(comm
.size
== 16) {
398 obuf
[1] = *data
++ & 0xff;
399 obuf
[0] = *data
++ & 0xff;
400 fwrite(obuf
, 1, 2, out
);
404 /* 8 bit samples have need no conversion so we can bulk copy.
405 * Everything that is not 16 bit is considered 8. */
407 fwrite(data
, 1, pos
, out
);
409 /* number of bytes has to be even, even if chunksize is not. */
411 fwrite(obuf
, 1, 1, out
);