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
)
37 TTSBase::Capabilities
TTSCarbon::capabilities()
42 bool TTSCarbon::configOk()
48 bool TTSCarbon::start(QString
*errStr
)
52 VoiceSpec
* vspecref
= NULL
;
53 VoiceDescription vdesc
;
56 = RbSettings::subValue("carbon", RbSettings::TtsVoice
).toString();
59 error
= CountVoices(&numVoices
);
60 for(voiceIndex
= 1; voiceIndex
< numVoices
; ++voiceIndex
) {
61 error
= GetIndVoice(voiceIndex
, &vspec
);
62 error
= GetVoiceDescription(&vspec
, &vdesc
, sizeof(vdesc
));
63 // name is pascal string, i.e. the first byte is the length.
64 QString name
= QString::fromLocal8Bit((const char*)&vdesc
.name
[1],
66 if(name
== selectedVoice
) {
68 if(vdesc
.script
!= -1)
69 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.script
;
71 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.reserved
[0];
75 if(voiceIndex
== numVoices
) {
76 // voice not found. Add user notification here and proceed with
77 // system default voice.
78 qDebug() << "selected voice not found, using system default!";
79 GetVoiceDescription(&vspec
, &vdesc
, sizeof(vdesc
));
80 if(vdesc
.script
!= -1)
81 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.script
;
83 m_voiceScript
= (CFStringBuiltInEncodings
)vdesc
.reserved
[0];
86 error
= NewSpeechChannel(vspecref
, &m_channel
);
87 //SetSpeechInfo(channel, soSpeechDoneCallBack, speechDone);
88 Fixed rate
= (Fixed
)(0x10000 * RbSettings::subValue("carbon",
89 RbSettings::TtsSpeed
).toInt());
91 SetSpeechRate(m_channel
, rate
);
92 return (error
== 0) ? true : false;
96 bool TTSCarbon::stop(void)
98 DisposeSpeechChannel(m_channel
);
103 void TTSCarbon::generateSettings(void)
105 QStringList voiceNames
;
110 VoiceDescription vdesc
;
113 error
= GetVoiceDescription(NULL
, &vdesc
, sizeof(vdesc
));
115 = QString::fromLocal8Bit((const char*)&vdesc
.name
[1], vdesc
.name
[0]);
116 // get list of all voices
117 CountVoices(&numVoices
);
118 for(SInt16 i
= 1; i
< numVoices
; ++i
) {
119 error
= GetIndVoice(i
, &vspec
);
120 error
= GetVoiceDescription(&vspec
, &vdesc
, sizeof(vdesc
));
121 // name is pascal string, i.e. the first byte is the length.
123 = QString::fromLocal8Bit((const char*)&vdesc
.name
[1], vdesc
.name
[0]);
124 voiceNames
.append(name
.trimmed());
127 EncTtsSetting
* setting
;
129 = RbSettings::subValue("carbon", RbSettings::TtsVoice
).toString();
132 setting
= new EncTtsSetting(this, EncTtsSetting::eSTRINGLIST
,
133 tr("Voice:"), voice
, voiceNames
, EncTtsSetting::eNOBTN
);
134 insertSetting(ConfigVoice
, setting
);
137 int speed
= RbSettings::subValue("carbon", RbSettings::TtsSpeed
).toInt();
138 setting
= new EncTtsSetting(this, EncTtsSetting::eINT
,
139 tr("Speed (words/min):"), speed
, 80, 500,
140 EncTtsSetting::eNOBTN
);
141 insertSetting(ConfigSpeed
, setting
);
145 void TTSCarbon::saveSettings(void)
147 // save settings in user config
148 RbSettings::setSubValue("carbon", RbSettings::TtsVoice
,
149 getSetting(ConfigVoice
)->current().toString());
150 RbSettings::setSubValue("carbon", RbSettings::TtsSpeed
,
151 getSetting(ConfigSpeed
)->current().toInt());
156 /** @brief create wav file from text using the selected TTS voice.
158 TTSStatus
TTSCarbon::voice(QString text
, QString wavfile
, QString
* errStr
)
160 TTSStatus status
= NoError
;
163 QString aifffile
= wavfile
+ ".aiff";
164 // FIXME: find out why we need to do this.
165 // Create a local copy of the temporary file filename.
166 // Not doing so causes weird issues (path contains trailing spaces)
167 unsigned int len
= aifffile
.size() + 1;
168 char* tmpfile
= (char*)malloc(len
* sizeof(char));
169 strncpy(tmpfile
, aifffile
.toLocal8Bit().constData(), len
);
170 CFStringRef tmpfileref
= CFStringCreateWithCString(kCFAllocatorDefault
,
171 tmpfile
, kCFStringEncodingUTF8
);
172 CFURLRef urlref
= CFURLCreateWithFileSystemPath(kCFAllocatorDefault
,
173 tmpfileref
, kCFURLPOSIXPathStyle
, false);
174 SetSpeechInfo(m_channel
, soOutputToFileWithCFURL
, urlref
);
177 // Convert the string to the encoding requested by the voice. Do this
178 // via CFString, as this allows to directly use the destination encoding
179 // as CFString uses the same values as the voice.
181 // allocate enough space to allow storing the string in a 2 byte encoding
182 unsigned int textlen
= 2 * text
.length() + 1;
183 char* textbuf
= (char*)calloc(textlen
, sizeof(char));
184 char* utf8data
= (char*)text
.toUtf8().constData();
185 int utf8bytes
= text
.toUtf8().size();
186 CFStringRef cfstring
= CFStringCreateWithBytes(kCFAllocatorDefault
,
187 (UInt8
*)utf8data
, utf8bytes
,
188 kCFStringEncodingUTF8
, (Boolean
)false);
191 range
.location
= 0; // character in string to start.
192 range
.length
= text
.length(); // number of _characters_ in string
193 // FIXME: check if converting between encodings was lossless.
194 CFStringGetBytes(cfstring
, range
, m_voiceScript
, ' ',
195 false, (UInt8
*)textbuf
, textlen
, &usedBuf
);
197 error
= SpeakText(m_channel
, textbuf
, (unsigned long)usedBuf
);
198 while(SpeechBusy()) {
199 // FIXME: add small delay here to make calls less frequent
200 QCoreApplication::processEvents();
203 *errStr
= tr("Could not voice string");
209 // convert the temporary aiff file to wav
211 && convertAiffToWav(tmpfile
, wavfile
.toLocal8Bit().constData()) != 0) {
212 *errStr
= tr("Could not convert intermediate file");
215 // remove temporary aiff file
223 unsigned long TTSCarbon::be2u32(unsigned char* buf
)
225 return (buf
[0]&0xff)<<24 | (buf
[1]&0xff)<<16 | (buf
[2]&0xff)<<8 | (buf
[3]&0xff);
229 unsigned long TTSCarbon::be2u16(unsigned char* buf
)
231 return buf
[1]&0xff | (buf
[0]&0xff)<<8;
235 unsigned char* TTSCarbon::u32tobuf(unsigned char* buf
, uint32_t val
)
238 buf
[1] = (val
>> 8) & 0xff;
239 buf
[2] = (val
>>16) & 0xff;
240 buf
[3] = (val
>>24) & 0xff;
245 unsigned char* TTSCarbon::u16tobuf(unsigned char* buf
, uint16_t val
)
248 buf
[1] = (val
>> 8) & 0xff;
253 /** @brief convert 80 bit extended ("long double") to int.
254 * This is simplified to handle the usual audio sample rates. Everything else
255 * might break. If the value isn't supported it will return 0.
256 * Conversion taken from Rockbox aiff codec.
258 unsigned int TTSCarbon::extended2int(unsigned char* buf
)
260 unsigned int result
= 0;
261 /* value negative? */
264 /* check exponent. Int can handle up to 2^31. */
265 int exponent
= buf
[0] << 8 | buf
[1];
266 if(exponent
< 0x4000 || exponent
> (0x4000 + 30))
268 result
= ((buf
[2]<<24) | (buf
[3]<<16) | (buf
[4]<<8) | buf
[5]) + 1;
269 result
>>= (16 + 14 - buf
[1]);
274 /** @brief Convert aiff file to wav. Returns 0 on success.
276 int TTSCarbon::convertAiffToWav(const char* aiff
, const char* wav
)
279 unsigned long chunksize
;
280 unsigned short channels
;
281 unsigned long frames
;
287 unsigned long chunksize
;
288 unsigned long offset
;
289 unsigned long blocksize
;
294 unsigned char obuf
[4];
296 /* minimum file size for a valid aiff file is 46 bytes:
297 * - FORM chunk: 12 bytes
298 * - COMM chunk: 18 bytes
299 * - SSND chunk: 16 bytes (with no actual data)
301 struct stat filestat
;
302 stat(aiff
, &filestat
);
303 if(filestat
.st_size
< 46)
305 /* read input file into memory */
306 buf
= (unsigned char*)malloc(filestat
.st_size
* sizeof(unsigned char));
307 if(!buf
) /* error out if malloc() failed */
309 in
= fopen(aiff
, "rb");
310 if(fread(buf
, 1, filestat
.st_size
, in
) < filestat
.st_size
) {
311 printf("could not read file: not enought bytes read\n");
317 /* check input file format */
318 if(memcmp(buf
, "FORM", 4) | memcmp(&buf
[8], "AIFF", 4)) {
319 printf("No valid AIFF header found.\n");
323 /* read COMM chunk */
324 unsigned char* commstart
= &buf
[12];
325 struct commchunk comm
;
326 if(memcmp(commstart
, "COMM", 4)) {
327 printf("COMM chunk not at beginning.\n");
331 comm
.chunksize
= be2u32(&commstart
[4]);
332 comm
.channels
= be2u16(&commstart
[8]);
333 comm
.frames
= be2u32(&commstart
[10]);
334 comm
.size
= be2u16(&commstart
[14]);
335 comm
.rate
= extended2int(&commstart
[16]);
337 /* find SSND as next chunk */
338 unsigned char* ssndstart
= commstart
+ 8 + comm
.chunksize
;
339 while(memcmp(ssndstart
, "SSND", 4) && ssndstart
< (buf
+ filestat
.st_size
)) {
340 printf("Skipping chunk.\n");
341 ssndstart
+= be2u32(&ssndstart
[4]) + 8;
343 if(ssndstart
> (buf
+ filestat
.st_size
)) {
348 struct ssndchunk ssnd
;
349 ssnd
.chunksize
= be2u32(&ssndstart
[4]);
350 ssnd
.offset
= be2u32(&ssndstart
[8]);
351 ssnd
.blocksize
= be2u32(&ssndstart
[12]);
353 /* Calculate the total length of the resulting RIFF chunk.
354 * The length is given by frames * samples * bytes/sample.
356 * - 16 bytes: fmt chunk header
357 * - 8 bytes: data chunk header
358 * - 4 bytes: wave chunk identifier
360 out
= fopen(wav
, "wb+");
362 /* write the wav header */
363 unsigned short blocksize
= comm
.channels
* (comm
.size
>> 3);
364 unsigned long rifflen
= blocksize
* comm
.frames
+ 28;
365 fwrite("RIFF", 1, 4, out
);
366 fwrite(u32tobuf(obuf
, rifflen
), 1, 4, out
);
367 fwrite("WAVE", 1, 4, out
);
369 /* write the fmt chunk and chunk size (always 16) */
370 /* write fmt chunk header:
371 * header, size (always 0x10, format code (always 0x0001)
373 fwrite("fmt \x10\x00\x00\x00\x01\x00", 1, 10, out
);
374 /* number of channels (2 bytes) */
375 fwrite(u16tobuf(obuf
, comm
.channels
), 1, 2, out
);
376 /* sampling rate (4 bytes) */
377 fwrite(u32tobuf(obuf
, comm
.rate
), 1, 4, out
);
379 /* data rate, i.e. bytes/sec */
380 fwrite(u32tobuf(obuf
, comm
.rate
* blocksize
), 1, 4, out
);
382 /* data block size */
383 fwrite(u16tobuf(obuf
, blocksize
), 1, 2, out
);
385 /* bits per sample */
386 fwrite(u16tobuf(obuf
, comm
.size
), 1, 2, out
);
388 /* write the data chunk */
390 fwrite("data", 1, 4, out
);
391 /* chunk size: 4 bytes. */
392 unsigned long cs
= blocksize
* comm
.frames
;
393 fwrite(u32tobuf(obuf
, cs
), 1, 4, out
);
396 unsigned char* data
= ssndstart
;
397 unsigned long pos
= ssnd
.chunksize
;
398 /* byteswap if samples are 16 bit */
399 if(comm
.size
== 16) {
401 obuf
[1] = *data
++ & 0xff;
402 obuf
[0] = *data
++ & 0xff;
403 fwrite(obuf
, 1, 2, out
);
407 /* 8 bit samples have need no conversion so we can bulk copy.
408 * Everything that is not 16 bit is considered 8. */
410 fwrite(data
, 1, pos
, out
);
412 /* number of bytes has to be even, even if chunksize is not. */
414 fwrite(obuf
, 1, 1, out
);