UKSpeechSynthesizer
Language: Objective-C, Author: Uli Kusterer
License: MIT/X11
A wrapper class around the Carbon SpeechChannel that tries to be compatible with but also expands upon the features of Apple's NSSpeechChannel.
Also included is UKSpeechSettingsView, which provides a simple user interface for modifying the settings of a speech channel
UKSpeechSynthesizer source preview
// // UKSpeechSynthesizer.m // UKSpeechSynthesizer // // Created by Uli Kusterer on Mon Jun 30 2003. // Copyright (c) 2003 M. Uli Kusterer. All rights reserved. // /* ----------------------------------------------------------------------------- Headers: -------------------------------------------------------------------------- */ #import <Carbon/Carbon.h> #import "UKSpeechSynthesizer.h" /* ----------------------------------------------------------------------------- Prototypes: -------------------------------------------------------------------------- */ pascal void MyPhonemeCallback( SpeechChannel chan, long refCon, short phonemeOpcode ); pascal void MySpeechDoneCallback( SpeechChannel chan, long refCon ); @interface UKSpeechSynthesizer (PrivateMethods) -(id) reallocSpeechChannelWithVoice: (VoiceSpec*)spec; -(void) setPhonemeOpcode: (short)n; -(void) notifySpeechDoneObject: (id)dummy; -(void) notifySpeechPhonemeObject: (id)dummy; @end @implementation UKSpeechSynthesizer /* ----------------------------------------------------------------------------- Class methods: -------------------------------------------------------------------------- */ +(id) speechSynthesizer { return [[[self alloc] autorelease] init]; } +(id) speechSynthesizerWithVoice: (NSString*)voiceName { return [[[self alloc] autorelease] initWithVoice: voiceName]; } +(VoiceSpec) voiceSpecFromVoice: (NSString*)voiceName { VoiceSpec spec = { 0 }; short count, x; VoiceDescription vInfo; if( CountVoices( &count ) != noErr ) return spec; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; for( x = 0; x < count; x++ ) { if( GetIndVoice( x, &spec ) == noErr ) { if( GetVoiceDescription( &spec, &vInfo, sizeof(vInfo) ) == noErr ) { if( [[UK_SPEECH_VOICE_PREFIX stringByAppendingString: [NSString stringWithCString:(vInfo.name +1) length:(vInfo.name[0])]] isEqualToString:voiceName] ) { [pool release]; return spec; } } } } spec.id = 0; spec.creator = 0; [pool release]; return spec; } +(NSArray*) availableVoices { VoiceSpec spec = { 0 }; short count, x; VoiceDescription vInfo; NSMutableArray* theArray = [NSMutableArray array]; if( CountVoices( &count ) != noErr ) return nil; NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; for( x = 0; x < count; x++ ) { if( GetIndVoice( x, &spec ) == noErr ) { if( GetVoiceDescription( &spec, &vInfo, sizeof(vInfo) ) == noErr ) [theArray addObject: [UK_SPEECH_VOICE_PREFIX stringByAppendingString: [NSString stringWithCString:(vInfo.name +1) length:(vInfo.name[0])]]]; } } [pool release]; return theArray; } +(NSString*) voiceFromVoiceSpec: (VoiceSpec*)spec { VoiceDescription vInfo; if( GetVoiceDescription( spec, &vInfo, sizeof(vInfo) ) == noErr ) { return( [UK_SPEECH_VOICE_PREFIX stringByAppendingString: [NSString stringWithCString:(vInfo.name +1) length:(vInfo.name[0])]] ); } else return nil; } +(NSString*) defaultVoice { return[self voiceFromVoiceSpec: NULL]; } +(NSDictionary*) attributesForVoice:(NSString*)voice { VoiceDescription vInfo; VoiceSpec spec = [self voiceSpecFromVoice: voice]; if( GetVoiceDescription( &spec, &vInfo, sizeof(vInfo) ) == noErr ) { NSMutableDictionary* dict = [NSMutableDictionary dictionary]; [dict setObject: voice forKey: NSVoiceIdentifier]; [dict setObject: [NSString stringWithCString:(vInfo.name +1) length:(vInfo.name[0])] forKey: NSVoiceName]; [dict setObject: [NSString stringWithCString:(vInfo.comment +1) length:(vInfo.comment[0])] forKey: NSVoiceDemoText]; [dict setObject: [NSNumber numberWithShort: vInfo.age] forKey: NSVoiceAge]; NSString* genders[3] = { NSVoiceGenderNeuter, NSVoiceGenderMale, NSVoiceGenderFemale }; [dict setObject: genders[vInfo.gender] forKey: NSVoiceGender]; [dict setObject: [NSNumber numberWithShort: vInfo.language] forKey: NSVoiceLanguage]; return dict; } else return nil; } +(BOOL) isAnyApplicationSpeaking { return SpeechBusySystemWide(); } /* ----------------------------------------------------------------------------- Instance methods: -------------------------------------------------------------------------- */ -(id) init { if( self = [super init] ) { speechChannel = nil; speechPhonemeUPP = speechDoneUPP = nil; delegate = nil; buffer = nil; speechPhonemeUPP = NewSpeechPhonemeUPP( &MyPhonemeCallback ); speechDoneUPP = NewSpeechDoneUPP( &MySpeechDoneCallback ); if( [self reallocSpeechChannelWithVoice: nil] == nil ) return nil; } return self; } -(id) initWithVoice: (NSString*)voiceName { if( self = [super init] ) { VoiceSpec spec = [UKSpeechSynthesizer voiceSpecFromVoice:voiceName]; speechChannel = nil; speechPhonemeUPP = speechDoneUPP = nil; delegate = nil; buffer = nil; speechPhonemeUPP = NewSpeechPhonemeUPP( &MyPhonemeCallback ); speechDoneUPP = NewSpeechDoneUPP( &MySpeechDoneCallback ); if( [self reallocSpeechChannelWithVoice: &spec] == nil ) return nil; } return self; } -(void) dealloc { DisposeSpeechChannel( speechChannel ); if( speechDoneUPP != nil ) DisposeSpeechDoneUPP( (SpeechDoneUPP) speechDoneUPP); if( speechPhonemeUPP != nil ) DisposeSpeechPhonemeUPP( (SpeechPhonemeUPP) speechPhonemeUPP); if( buffer ) free( buffer ); [super dealloc]; } - (oneway void)release { if( [self retainCount] == 1 ) NSLog(@"UKSpeechChannel released."); [super release]; } -(id) reallocSpeechChannelWithVoice: (VoiceSpec*)spec { if( speechChannel ) { DisposeSpeechChannel( speechChannel ); speechChannel = nil; } if( NewSpeechChannel( spec, &speechChannel ) != noErr ) return nil; if( SetSpeechInfo( speechChannel, soRefCon, (Ptr)self ) != noErr ) return nil; if( SetSpeechInfo( speechChannel, soPhonemeCallBack, speechPhonemeUPP ) != noErr ) return nil; if( SetSpeechInfo( speechChannel, soSpeechDoneCallBack, speechDoneUPP ) != noErr ) return nil; if( spec == nil ) { [currVoice release]; currVoice = [[UKSpeechSynthesizer defaultVoice] retain]; } return self; } -(BOOL) usesFeedbackWindow { return usesFeedbackWindow; } -(void) setUsesFeedbackWindow: (BOOL)n { usesFeedbackWindow = n; } -(void) setVoice: (NSString*)voiceName { VoiceSpec spec; if( voiceName == nil ) [self reallocSpeechChannelWithVoice: nil]; else { [currVoice release]; currVoice = [voiceName retain]; spec = [UKSpeechSynthesizer voiceSpecFromVoice: voiceName]; [self reallocSpeechChannelWithVoice: &spec]; } } -(NSString*) voice { return currVoice; } -(void) startSpeakingString: (NSString*)str { [self stopSpeaking]; // Just make sure. if( buffer ) { free( buffer ); buffer = nil; } buffer = malloc( [str cStringLength] +1 ); [str getCString:buffer]; [self retain]; SpeakText( speechChannel, buffer, strlen(buffer) ); isSpeaking = YES; if( [str length] == 0 ) { //NSLog(@"Triggering Speech Done Notification because [str length] == 0"); [self notifySpeechDoneObject: nil]; } } -(BOOL) isSpeaking { return isSpeaking; } -(void) stopSpeaking { StopSpeech( speechChannel ); } -(void) stopSpeakingAt: (long)whereToStop { StopSpeechAt( speechChannel, whereToStop ); } -(void) pauseSpeakingAt: (long)whereToStop { PauseSpeechAt( speechChannel, whereToStop ); } -(void) continueSpeaking { ContinueSpeech( speechChannel ); } -(void) setSpeechPitch: (double)pitch { SetSpeechPitch( speechChannel, X2Fix( pitch ) ); } -(double) speechPitch { Fixed nb; GetSpeechPitch( speechChannel, &nb ); return Fix2X( nb ); } -(void) setSpeechRate: (unsigned short)n { Fixed vVolume; vVolume = Long2Fix(n); SetSpeechInfo( speechChannel, soRate, &vVolume ); } -(unsigned short) speechRate { Fixed vVolume; unsigned short n = 0; if( GetSpeechInfo( speechChannel, soRate, &vVolume ) == noErr ) n = Fix2Long( vVolume ); return n; } -(void) notifySpeechDoneObject: (id)dummy { if( buffer ) { free( buffer ); buffer = nil; } //NSLog(@"Speech Done Notification."); if( isSpeaking ) { [self release]; isSpeaking = NO; } [delegate speechSynthesizer: self didFinishSpeaking: YES]; } -(void) notifySpeechPhonemeObject: (id)dummy { [delegate speechSynthesizer: self willSpeakPhoneme: phonemeOpcode]; } -(void) setSpeechVolume: (short)n { Fixed vVolume; vVolume = Long2Fix(n); vVolume = FixDiv( vVolume, 0x000A0000 ); // Divide by 10. SetSpeechInfo( speechChannel, soVolume, &vVolume ); } -(short) speechVolume { Fixed vVolume; short n = -1; if( GetSpeechInfo( speechChannel, soVolume, &vVolume ) == noErr ) { vVolume = FixMul( vVolume, 0x000A0000 ); // Multiply by 10. n = Fix2Long( vVolume ); } return n; } -(void) setDelegate: (id)delly { delegate = delly; } -(id) delegate { return delegate; } -(void) setPhonemeOpcode: (short)n { phonemeOpcode = n; } -(SpeechChannel) channel { return speechChannel; } -(NSDictionary*) settingsDictionary { NSMutableDictionary* dict = [NSMutableDictionary dictionary]; [dict setObject: [self voice] forKey: @"voice"]; [dict setObject: [NSNumber numberWithInt: [self usesFeedbackWindow]] forKey: @"usesFeedbackWindow"]; [dict setObject: [NSNumber numberWithInt: [self speechVolume]] forKey: @"speechVolume"]; [dict setObject: [NSNumber numberWithDouble: [self speechPitch]] forKey: @"speechPitch"]; [dict setObject: [NSNumber numberWithInt: [self speechRate]] forKey: @"speechRate"]; //NSLog(@"speechSettingsDict(OUT) = %@", dict); return dict; } -(void) setSettingsDictionary: (NSDictionary*)dict { //NSLog(@"speechSettingsDict(IN) = %@",dict); [self setVoice: [dict objectForKey: @"voice"]]; [self setUsesFeedbackWindow: [[dict objectForKey: @"usesFeedbackWindow"] boolValue]]; [self setSpeechVolume: [[dict objectForKey: @"speechVolume"] intValue]]; [self setSpeechPitch: [[dict objectForKey: @"speechPitch"] doubleValue]]; [self setSpeechRate: [[dict objectForKey: @"speechRate"] intValue]]; } // Remove any speech commands from the specified string. You can use this for displaying the string being spoken: +(NSString*) prettifyString: (NSString*)inString { NSMutableString* str = [inString mutableCopy]; NSRange commandRange = { 0, 0 }, cmdEndRange; if( !str ) return str; while( commandRange.location != NSNotFound || commandRange.length != 0 ) { commandRange = [str rangeOfString: @"[["]; if( commandRange.location == NSNotFound && commandRange.length == 0 ) break; cmdEndRange = [str rangeOfString: @"]]"]; if( cmdEndRange.location == NSNotFound && cmdEndRange.length == 0 ) break; commandRange.length += ((cmdEndRange.location +cmdEndRange.length) -(commandRange.location +commandRange.length)); [str deleteCharactersInRange: commandRange]; } return str; } @end @implementation NSObject (UKSpeechSynthesizerDelegate) - (void)speechSynthesizer:(UKSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking { } - (void)speechSynthesizer:(UKSpeechSynthesizer *)sender willSpeakPhoneme:(short)phonemeOpcode { } @end /* -------------------------------------------------------------------------------- MyPhonemeCallback: Phoneme callback procedure for lip syncronization. REVISIONS: 2000-11-02 UK Created. ----------------------------------------------------------------------------- */ pascal void MyPhonemeCallback( SpeechChannel chan, long refCon, short phonemeOpcode ) { [((UKSpeechSynthesizer*)refCon) setPhonemeOpcode:phonemeOpcode]; [((UKSpeechSynthesizer*)refCon) performSelectorOnMainThread:@selector(notifySpeechPhonemeObject:) withObject:nil waitUntilDone:NO]; } /* -------------------------------------------------------------------------------- MySpeechDoneCallback: Speech output has ended. Notify the speech channel so it can broadcast a message that may be used to hide any speech feedback elements or to reset lip-synched mouths to a default position. REVISIONS: 2001-08-18 UK Created. ----------------------------------------------------------------------------- */ pascal void MySpeechDoneCallback( SpeechChannel chan, long refCon ) { //((UKSpeechSynthesizer*)refCon)->isSpeaking = NO; [((UKSpeechSynthesizer*)refCon) performSelectorOnMainThread:@selector(notifySpeechDoneObject:) withObject:nil waitUntilDone:NO]; }
UKSpeechSynthesizer header preview
// // UKSpeechSynthesizer.h // UKSpeechSynthesizer // // Created by Uli Kusterer on Mon Jun 30 2003. // Copyright (c) 2003 M. Uli Kusterer. All rights reserved. // /* ----------------------------------------------------------------------------- Headers: -------------------------------------------------------------------------- */ #import <Foundation/Foundation.h> /* ----------------------------------------------------------------------------- Forwards: -------------------------------------------------------------------------- */ // This avoids our users from having to include all of Carbon.h every time: #ifndef __SPEECHSYNTHESIS__ typedef struct SpeechChannelRecord SpeechChannelRecord; typedef SpeechChannelRecord* SpeechChannel; #endif /* ----------------------------------------------------------------------------- UKSpeechSynthesizer: -------------------------------------------------------------------------- */ @interface UKSpeechSynthesizer : NSObject { SpeechChannel speechChannel; // The actual Carbon speech channel that is used for speech output. IBOutlet id delegate; // The delegate that gets notified of all those callbacks. BOOL usesFeedbackWindow; // Dummy for NSSpeechSynthesizer compatibility. BOOL isSpeaking; // Keep track whether we're speaking. NSString* currVoice; // The currently assigned voice. // private: short phonemeOpcode; // We can't pass parameters when calling back to the main thread, so we stash the phoneme here. void* speechDoneUPP; // cast to SpeechDoneUPP void* speechPhonemeUPP; // cast to SpeechPhonemeUPP char* buffer; // Keeps a copy of the text being spoken. } // Class methods: +(id) speechSynthesizer; // UKSpeechSynthesizer-specific. +(id) speechSynthesizerWithVoice: (NSString*)voiceName; // UKSpeechSynthesizer-specific. +(NSArray*) availableVoices; +(NSDictionary*)attributesForVoice:(NSString*)voice; +(BOOL) isAnyApplicationSpeaking; +(VoiceSpec) voiceSpecFromVoice: (NSString*)voiceName; // UKSpeechSynthesizer-specific. +(NSString*) voiceFromVoiceSpec: (VoiceSpec*)spec; // UKSpeechSynthesizer-specific. +(NSString*) prettifyString: (NSString*)inString; // UKSpeechSynthesizer-specific. // Instance methods: -(id) init; -(id) initWithVoice: (NSString*)voiceName; -(void) setDelegate: (id)delly; -(id) delegate; -(void) setVoice: (NSString*)voiceName; // Recreates the internal speech channel. -(NSString*) voice; -(void) startSpeakingString: (NSString*)str; // This retains the channel until speech is done. -(BOOL) isSpeaking; -(void) stopSpeaking; // Dummied out: -(void) setUsesFeedbackWindow: (BOOL)n; // This remembers the state, but doesn't actually bring up a feedback window. -(BOOL) usesFeedbackWindow; //-(void) startSpeakingString: (NSString*)str toURL:(NSURL*)url; // UKSpeechChannel-specific: -(void) stopSpeakingAt: (long)whereToStop; -(void) pauseSpeakingAt: (long)whereToStop; -(void) continueSpeaking; -(void) setSpeechVolume: (short)n; -(short) speechVolume; -(void) setSpeechPitch: (double)pitch; -(double) speechPitch; -(void) setSpeechRate: (unsigned short)n; -(unsigned short) speechRate; -(NSDictionary*) settingsDictionary; -(void) setSettingsDictionary: (NSDictionary*)dict; -(SpeechChannel) channel; @end // Delegate methods: (informal protocol) @interface NSObject (UKSpeechSynthesizerDelegate) - (void)speechSynthesizer:(UKSpeechSynthesizer *)sender didFinishSpeaking:(BOOL)finishedSpeaking; //- (void)speechSynthesizer:(UKSpeechSynthesizer *)sender willSpeakWord:(NSRange)characterRange ofString:(NSString *)string; - (void)speechSynthesizer:(UKSpeechSynthesizer *)sender willSpeakPhoneme:(short)phonemeOpcode; @end // This is what we prefix when returning voice identifiers so we're compatible with NSSpeechChannel: #define UK_SPEECH_VOICE_PREFIX @"com.apple.speech.synthesis.voice." #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_3 #define NSVoiceName @"NSVoiceName" #define NSVoiceIdentifier @"NSVoiceIdentifier" #define NSVoiceAge @"NSVoiceAge" #define NSVoiceGender @"NSVoiceGender" #define NSVoiceDemoText @"NSVoiceDemoText" #define NSVoiceLanguage @"NSVoiceLanguage" #define NSVoiceGenderNeuter @"NSVoiceGenderNeuter" #define NSVoiceGenderMale @"NSVoiceGenderMale" #define NSVoiceGenderFemale @"NSVoiceGenderFemale" #endifDownload Archive
Compatible with:
- Mac OS X 10.3
- Mac OS X 10.4 PPC
- Mac OS X 10.4 Intel
- Mac OS X 10.5 PPC
- Mac OS X 10.5 Intel
Comments
Comment feed
