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"
#endif
Download 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
