Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 634 lines (478 sloc) 15.005 kb
dc5be1c0 » uli
2009-07-04 Initial check-in.
1 //
2 // UKSpeechSynthesizer.m
3 // UKSpeechSynthesizer
4 //
5 // Created by Uli Kusterer on Mon Jun 30 2003.
720b0e7b »
2010-03-04 Added licensing information to the source files.
6 // Copyright (c) 2003 M. Uli Kusterer. Uli Kusterer
7 //
8 // This software is provided 'as-is', without any express or implied
9 // warranty. In no event will the authors be held liable for any damages
10 // arising from the use of this software.
11 //
12 // Permission is granted to anyone to use this software for any purpose,
13 // including commercial applications, and to alter it and redistribute it
14 // freely, subject to the following restrictions:
15 //
16 // 1. The origin of this software must not be misrepresented; you must not
17 // claim that you wrote the original software. If you use this software
18 // in a product, an acknowledgment in the product documentation would be
19 // appreciated but is not required.
20 //
21 // 2. Altered source versions must be plainly marked as such, and must not be
22 // misrepresented as being the original software.
23 //
24 // 3. This notice may not be removed or altered from any source
25 // distribution.
dc5be1c0 » uli
2009-07-04 Initial check-in.
26 //
27
28 /* -----------------------------------------------------------------------------
29 Headers:
30 -------------------------------------------------------------------------- */
31
32 #import <Carbon/Carbon.h>
33 #import "UKSpeechSynthesizer.h"
34 #import "UKHelperMacros.h"
35
36
37 /* -----------------------------------------------------------------------------
38 Constants:
39 -------------------------------------------------------------------------- */
40
41 // This is what we prefix when returning voice identifiers so we're compatible with NSSpeechChannel:
42 #define UK_SPEECH_VOICE_PREFIX @"com.apple.speech.synthesis.voice."
43
44 NSString* const NSSpeechPitchBaseProperty = @"NSSpeechPitchBaseProperty";
45
46
47 /* -----------------------------------------------------------------------------
48 Prototypes:
49 -------------------------------------------------------------------------- */
50
51 pascal void MyPhonemeCallback( SpeechChannel chan, long refCon, short phonemeOpcode );
52 pascal void MySpeechDoneCallback( SpeechChannel chan, long refCon );
53
54
55 @interface UKSpeechSynthesizer (PrivateMethods)
56
57 -(id) reallocSpeechChannelWithVoice: (VoiceSpec*)spec;
58 -(void) setPhonemeOpcode: (short)n;
59 -(void) notifySpeechDoneObject: (id)dummy;
60 -(void) notifySpeechPhonemeObject: (id)dummy;
61
62 @end
63
64
65 @implementation UKSpeechSynthesizer
66
67 /* -----------------------------------------------------------------------------
68 Class methods:
69 -------------------------------------------------------------------------- */
70
71 +(id) speechSynthesizer
72 {
73 return [[[self alloc] autorelease] init];
74 }
75
76
77 +(id) speechSynthesizerWithVoice: (NSString*)voiceName
78 {
79 return [[[self alloc] autorelease] initWithVoice: voiceName];
80 }
81
82
83
84 +(VoiceSpec) voiceSpecFromVoice: (NSString*)voiceName
85 {
86 VoiceSpec spec = { 0 };
87 short count, x;
88 VoiceDescription vInfo;
89
90 if( CountVoices( &count ) != noErr )
91 return spec;
92
93 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
94 for( x = 0; x < count; x++ )
95 {
96 if( GetIndVoice( x, &spec ) == noErr )
97 {
98 if( GetVoiceDescription( &spec, &vInfo, sizeof(vInfo) ) == noErr )
99 {
100 if( [[UK_SPEECH_VOICE_PREFIX stringByAppendingString: [NSString stringWithCString:(char*)(vInfo.name +1) length:(vInfo.name[0])]] isEqualToString:voiceName] )
101 {
102 [pool release];
103 return spec;
104 }
105 }
106 }
107 }
108
109 spec.id = 0; spec.creator = 0;
110
111 [pool release];
112 return spec;
113 }
114
115
116 +(NSArray*) availableVoices
117 {
118 VoiceSpec spec = { 0 };
119 short count, x;
120 VoiceDescription vInfo;
121 NSMutableArray* theArray = [NSMutableArray array];
122
123 if( CountVoices( &count ) != noErr )
124 return nil;
125
126 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
127 for( x = 0; x < count; x++ )
128 {
129 if( GetIndVoice( x, &spec ) == noErr )
130 {
131 if( GetVoiceDescription( &spec, &vInfo, sizeof(vInfo) ) == noErr )
132 [theArray addObject: [UK_SPEECH_VOICE_PREFIX stringByAppendingString: [NSString stringWithCString:(char*)(vInfo.name +1) length:(vInfo.name[0])]]];
133 }
134 }
135
136 [pool release];
137 return theArray;
138 }
139
140
141 +(NSString*) voiceFromVoiceSpec: (VoiceSpec*)spec
142 {
143 VoiceDescription vInfo;
144
145 if( GetVoiceDescription( spec, &vInfo, sizeof(vInfo) ) == noErr )
146 {
147 return( [UK_SPEECH_VOICE_PREFIX stringByAppendingString: [NSString stringWithCString:(char*)(vInfo.name +1) length:(vInfo.name[0])]] );
148 }
149 else
150 return nil;
151 }
152
153
154 +(NSString*) defaultVoice
155 {
156 return[self voiceFromVoiceSpec: NULL];
157 }
158
159
160 +(NSDictionary*) attributesForVoice:(NSString*)voice
161 {
162 VoiceDescription vInfo;
163 VoiceSpec spec = [self voiceSpecFromVoice: voice];
164 if( GetVoiceDescription( &spec, &vInfo, sizeof(vInfo) ) == noErr )
165 {
166 NSMutableDictionary* dict = [NSMutableDictionary dictionary];
167 [dict setObject: voice forKey: NSVoiceIdentifier];
168 [dict setObject: [NSString stringWithCString:(char*)(vInfo.name +1) length:(vInfo.name[0])] forKey: NSVoiceName];
169 [dict setObject: [NSString stringWithCString:(char*)(vInfo.comment +1) length:(vInfo.comment[0])] forKey: NSVoiceDemoText];
170 [dict setObject: [NSNumber numberWithShort: vInfo.age] forKey: NSVoiceAge];
171
172 NSString* genders[3] = { NSVoiceGenderNeuter,
173 NSVoiceGenderMale,
174 NSVoiceGenderFemale };
175
176 [dict setObject: genders[vInfo.gender] forKey: NSVoiceGender];
177 [dict setObject: [NSNumber numberWithShort: vInfo.language] forKey: NSVoiceLanguage];
178
179 return dict;
180 }
181 else
182 return nil;
183 }
184
185
186 +(BOOL) isAnyApplicationSpeaking
187 {
188 return SpeechBusySystemWide();
189 }
190
191
192 /* -----------------------------------------------------------------------------
193 Instance methods:
194 -------------------------------------------------------------------------- */
195
196 -(id) init
197 {
198 if( self = [super init] )
199 {
200 speechChannel = nil;
201 speechPhonemeUPP = speechDoneUPP = nil;
202 delegate = nil;
203 buffer = nil;
204
205 speechPhonemeUPP = NewSpeechPhonemeUPP( &MyPhonemeCallback );
206 speechDoneUPP = NewSpeechDoneUPP( &MySpeechDoneCallback );
207
208 if( [self reallocSpeechChannelWithVoice: nil] == nil )
209 return nil;
210 }
211 return self;
212 }
213
214 -(id) initWithVoice: (NSString*)voiceName
215 {
216 if( self = [super init] )
217 {
218 VoiceSpec spec = [UKSpeechSynthesizer voiceSpecFromVoice:voiceName];
219 speechChannel = nil;
220 speechPhonemeUPP = speechDoneUPP = nil;
221 delegate = nil;
222 buffer = nil;
223
224 speechPhonemeUPP = NewSpeechPhonemeUPP( &MyPhonemeCallback );
225 speechDoneUPP = NewSpeechDoneUPP( &MySpeechDoneCallback );
226
227 if( [self reallocSpeechChannelWithVoice: &spec] == nil )
228 return nil;
229 }
230 return self;
231 }
232
233 -(void) dealloc
234 {
235 DisposeSpeechChannel( speechChannel );
236 if( speechDoneUPP != nil )
237 DisposeSpeechDoneUPP( (SpeechDoneUPP) speechDoneUPP);
238 if( speechPhonemeUPP != nil )
239 DisposeSpeechPhonemeUPP( (SpeechPhonemeUPP) speechPhonemeUPP);
240 if( buffer )
241 free( buffer );
242
243 [super dealloc];
244 }
245
246
247 - (oneway void)release
248 {
249 if( [self retainCount] == 1 )
250 UKLog(@"UKSpeechChannel released.");
251 [super release];
252 }
253
254
255
256 -(id) reallocSpeechChannelWithVoice: (VoiceSpec*)spec
257 {
258 if( speechChannel )
259 {
260 DisposeSpeechChannel( speechChannel );
261 speechChannel = nil;
262 }
263
264 if( NewSpeechChannel( spec, &speechChannel ) != noErr )
265 return nil;
266
267 if( SetSpeechInfo( speechChannel, soRefCon, (Ptr)self ) != noErr ) return nil;
268 if( SetSpeechInfo( speechChannel, soPhonemeCallBack, speechPhonemeUPP ) != noErr ) return nil;
269 if( SetSpeechInfo( speechChannel, soSpeechDoneCallBack, speechDoneUPP ) != noErr ) return nil;
270
271 if( spec == nil )
272 {
273 [currVoice release];
274 currVoice = [[UKSpeechSynthesizer defaultVoice] retain];
275 }
276
277 return self;
278 }
279
280
281 -(BOOL) usesFeedbackWindow
282 {
283 return usesFeedbackWindow;
284 }
285
286
287 -(void) setUsesFeedbackWindow: (BOOL)n
288 {
289 usesFeedbackWindow = n;
290 }
291
292
293 -(void) setVoice: (NSString*)voiceName
294 {
295 VoiceSpec spec;
296
297 if( voiceName == nil )
298 [self reallocSpeechChannelWithVoice: nil];
299 else
300 {
301 [currVoice release];
302 currVoice = [voiceName retain];
303 spec = [UKSpeechSynthesizer voiceSpecFromVoice: voiceName];
304 [self reallocSpeechChannelWithVoice: &spec];
305 }
306 }
307
308
309 -(NSString*) voice
310 {
311 return currVoice;
312 }
313
314
315 -(void) startSpeakingString: (NSString*)str
316 {
317 if( isSpeaking )
318 {
319 UKLog(@"Stopping previous speech.");
320 [self stopSpeaking]; // Can't do this unconditionally! It'll send an additional "speech done" callback!
321 }
322
323 if( buffer )
324 {
325 free( buffer );
326 buffer = nil;
327 }
328 buffer = malloc( [str cStringLength] +1 );
329 [str getCString:buffer];
330
331 [self retain];
332
333 UKLog(@"About to speak: \"%@\"",str);
334 SpeakText( speechChannel, buffer, strlen(buffer) );
335 isSpeaking = YES;
336
337 if( [str length] == 0 )
338 {
339 UKLog(@"Triggering Speech Done Notification because [str length] == 0");
340 [self notifySpeechDoneObject: nil];
341 }
342 }
343
344
345 -(BOOL) isSpeaking
346 {
347 return isSpeaking;
348 }
349
350
351 -(void) stopSpeaking
352 {
353 StopSpeech( speechChannel );
354 }
355
356
357 -(void) stopSpeakingAt: (long)whereToStop
358 {
359 StopSpeechAt( speechChannel, whereToStop );
360 }
361
362
363 -(void) pauseSpeakingAt: (long)whereToStop
364 {
365 PauseSpeechAt( speechChannel, whereToStop );
366 }
367
368
369 -(void) continueSpeaking
370 {
371 ContinueSpeech( speechChannel );
372 }
373
374
375 -(void) setSpeechPitch: (double)pitch
376 {
377 SetSpeechPitch( speechChannel, X2Fix( pitch ) );
378 }
379
380
381 -(double) speechPitch
382 {
383 Fixed nb;
384 GetSpeechPitch( speechChannel, &nb );
385
386 return Fix2X( nb );
387 }
388
389 - (id)objectForProperty:(NSString *)property error:(NSError **)outError
390 {
391 if( [property isEqualToString: NSSpeechPitchBaseProperty] )
392 {
393 if( outError )
394 *outError = nil;
395 return [NSNumber numberWithDouble: [self speechPitch]];
396 }
397 else
398 {
399 if( outError )
400 *outError = [NSError errorWithDomain: @"UKSpeechSynthesizerErrorDomain" code: 1 userInfo: [NSDictionary dictionary]];
401 return nil;
402 }
403 }
404
405
406 - (BOOL)setObject:(id)object forProperty:(NSString *)property error:(NSError **)outError
407 {
408 if( [property isEqualToString: NSSpeechPitchBaseProperty] )
409 {
410 if( outError )
411 *outError = nil;
412 [self setSpeechPitch: [object doubleValue]];
413 return YES;
414 }
415 else
416 {
417 if( outError )
418 *outError = [NSError errorWithDomain: @"UKSpeechSynthesizerErrorDomain" code: 1 userInfo: [NSDictionary dictionary]];
419 return NO;
420 }
421 }
422
423
424 -(void) setRate: (float)n
425 {
426 Fixed vVolume;
427
428 vVolume = X2Fix(n);
429 SetSpeechInfo( speechChannel, soRate, &vVolume );
430 }
431
432
433 -(float) rate
434 {
435 Fixed vVolume;
436 float n = 0;
437
438 if( GetSpeechInfo( speechChannel, soRate, &vVolume ) == noErr )
439 n = Fix2X( vVolume );
440
441 return n;
442 }
443
444
445 -(void) notifySpeechDoneObject: (id)dummy
446 {
447 if( buffer )
448 {
449 free( buffer );
450 buffer = nil;
451 }
452 UKLog(@"Speech Done Notification.");
453
454 if( isSpeaking )
455 {
456 [self release];
457 isSpeaking = NO;
458 }
459 [delegate speechSynthesizer: (NSSpeechSynthesizer*) self didFinishSpeaking: YES];
460 }
461
462
463 -(void) notifySpeechPhonemeObject: (id)dummy
464 {
465 [delegate speechSynthesizer: (NSSpeechSynthesizer*) self willSpeakPhoneme: phonemeOpcode];
466 }
467
468
469 -(void) setVolume: (float)n
470 {
471 Fixed vVolume;
472
473 vVolume = X2Fix(n);
474 SetSpeechInfo( speechChannel, soVolume, &vVolume );
475 }
476
477
478 -(float) volume
479 {
480 Fixed vVolume;
481 float n = -1;
482
483 if( GetSpeechInfo( speechChannel, soVolume, &vVolume ) == noErr )
484 {
485 n = Fix2X( vVolume );
486 }
487
488 return n;
489 }
490
491
492 -(void) setDelegate: (id)delly
493 {
494 delegate = delly;
495 }
496
497
498 -(id) delegate
499 {
500 return delegate;
501 }
502
503 -(void) setPhonemeOpcode: (short)n
504 {
505 phonemeOpcode = n;
506 }
507
508
509 -(SpeechChannel) channel
510 {
511 return speechChannel;
512 }
513
514 -(NSDictionary*) settingsDictionary
515 {
516 NSMutableDictionary* dict = [NSMutableDictionary dictionary];
517
518 [dict setObject: [self voice] forKey: @"voice"];
519 [dict setObject: [NSNumber numberWithInt: [self usesFeedbackWindow]] forKey: @"usesFeedbackWindow"];
520 [dict setObject: [NSNumber numberWithFloat: [self volume]] forKey: @"speechVolume"];
521 [dict setObject: [NSNumber numberWithDouble: [self speechPitch]] forKey: @"speechPitch"];
522 [dict setObject: [NSNumber numberWithFloat: [self rate]] forKey: @"speechRate"];
523
524 UKLog(@"speechSettingsDict(OUT) = %@", dict);
525
526 return dict;
527 }
528
529
530 -(void) setSettingsDictionary: (NSDictionary*)dict
531 {
532 UKLog(@"speechSettingsDict(IN) = %@",dict);
533
534 [self setVoice: [dict objectForKey: @"voice"]];
535 [self setUsesFeedbackWindow: [[dict objectForKey: @"usesFeedbackWindow"] boolValue]];
536 [self setVolume: [[dict objectForKey: @"speechVolume"] floatValue]];
537 [self setSpeechPitch: [[dict objectForKey: @"speechPitch"] doubleValue]];
538 [self setRate: [[dict objectForKey: @"speechRate"] floatValue]];
539 }
540
541
542 // Remove any speech commands from the specified string. You can use this for displaying the string being spoken:
543 +(NSString*) prettifyString: (NSString*)inString
544 {
545 NSMutableString* str = [inString mutableCopy];
546 NSRange commandRange = { 0, 0 },
547 cmdEndRange;
548
549 if( !str )
550 return str;
551
552 while( commandRange.location != NSNotFound || commandRange.length != 0 )
553 {
554 commandRange = [str rangeOfString: @"[["];
555
556 if( commandRange.location == NSNotFound && commandRange.length == 0 )
557 break;
558
559 cmdEndRange = [str rangeOfString: @"]]"];
560 if( cmdEndRange.location == NSNotFound && cmdEndRange.length == 0 )
561 break;
562
563 commandRange.length += ((cmdEndRange.location +cmdEndRange.length) -(commandRange.location +commandRange.length));
564 [str deleteCharactersInRange: commandRange];
565 }
566
567 return str;
568 }
569
570 @end
571
572
573 @implementation NSObject (UKSpeechSynthesizerDelegate)
574
575 - (void)speechSynthesizer:(NSSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking
576 {
577
578 }
579
580
581 - (void)speechSynthesizer:(NSSpeechSynthesizer *)sender willSpeakPhoneme:(short)phonemeOpcode
582 {
583
584 }
585
586
587 @end
588
589
590
591
592 /* --------------------------------------------------------------------------------
593 MyPhonemeCallback:
594 Phoneme callback procedure for lip syncronization.
595
596 REVISIONS:
597 2000-11-02 UK Created.
598 ----------------------------------------------------------------------------- */
599
600 pascal void MyPhonemeCallback( SpeechChannel chan, long refCon, short phonemeOpcode )
601 {
602 CREATE_AUTORELEASE_POOL(pool);
603 if( [(UKSpeechSynthesizer*)refCon isSpeaking] )
604 {
605 UKLog(@"Phoneme %d",phonemeOpcode);
606 [((UKSpeechSynthesizer*)refCon) setPhonemeOpcode:phonemeOpcode];
607 [((UKSpeechSynthesizer*)refCon) performSelectorOnMainThread:@selector(notifySpeechPhonemeObject:)
608 withObject:nil waitUntilDone: NO];
609 }
610 else
611 UKLog(@"Ignoring Phoneme %d which arrived after speech done callback.",phonemeOpcode);
612 DESTROY(pool);
613 }
614
615
616 /* --------------------------------------------------------------------------------
617 MySpeechDoneCallback:
618 Speech output has ended. Notify the speech channel so it can broadcast a
619 message that may be used to hide any speech feedback elements or to reset
620 lip-synched mouths to a default position.
621
622 REVISIONS:
623 2001-08-18 UK Created.
624 ----------------------------------------------------------------------------- */
625
626 pascal void MySpeechDoneCallback( SpeechChannel chan, long refCon )
627 {
628 CREATE_AUTORELEASE_POOL(pool);
629 UKLog(@"Sending speech done on main thread.");
630 //((UKSpeechSynthesizer*)refCon)->isSpeaking = NO;
631 [((UKSpeechSynthesizer*)refCon) performSelectorOnMainThread:@selector(notifySpeechDoneObject:)
632 withObject:nil waitUntilDone: NO];
633 DESTROY(pool);
634 }
635
636
637
638
639
640
641
Something went wrong with that request. Please try again.