| Index | Submit class | Submit snippet | Submission feed | List |

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:


Comments

Name

Website

Do you hate spammers? (Answer "Yes")