CocoaSpeechSynthesisExample/SpeakingTextWindow.m
/* |
Copyright (C) 2015 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
The main window hosting all the apps speech features. |
*/ |
#import "SpeakingTextWindow.h" |
#import "SpeakingCharacterView.h" |
@interface SpeakingTextWindow () |
{ |
// Main window outlets |
IBOutlet NSWindow *fWindow; |
IBOutlet NSTextView *fSpokenTextView; |
IBOutlet NSButton *fStartStopButton; |
IBOutlet NSButton *fPauseContinueButton; |
IBOutlet NSButton *fSaveAsFileButton; |
// Options panel outlets |
IBOutlet NSButton *fImmediatelyRadioButton; |
IBOutlet NSButton *fAfterWordRadioButton; |
IBOutlet NSButton *fAfterSentenceRadioButton; |
IBOutlet NSPopUpButton *fVoicesPopUpButton; |
IBOutlet NSButton *fCharByCharCheckboxButton; |
IBOutlet NSButton *fDigitByDigitCheckboxButton; |
IBOutlet NSButton *fPhonemeModeCheckboxButton; |
IBOutlet NSButton *fDumpPhonemesButton; |
IBOutlet NSButton *fUseDictionaryButton; |
// Parameters panel outlets |
IBOutlet NSTextField *fRateDefaultEditableField; |
IBOutlet NSTextField *fPitchBaseDefaultEditableField; |
IBOutlet NSTextField *fPitchModDefaultEditableField; |
IBOutlet NSTextField *fVolumeDefaultEditableField; |
IBOutlet NSTextField *fRateCurrentStaticField; |
IBOutlet NSTextField *fPitchBaseCurrentStaticField; |
IBOutlet NSTextField *fPitchModCurrentStaticField; |
IBOutlet NSTextField *fVolumeCurrentStaticField; |
IBOutlet NSButton *fResetButton; |
// Callbacks panel outlets |
IBOutlet NSButton *fHandleWordCallbacksCheckboxButton; |
IBOutlet NSButton *fHandlePhonemeCallbacksCheckboxButton; |
IBOutlet NSButton *fHandleSyncCallbacksCheckboxButton; |
IBOutlet NSButton *fHandleErrorCallbacksCheckboxButton; |
IBOutlet NSButton *fHandleSpeechDoneCallbacksCheckboxButton; |
IBOutlet NSButton *fHandleTextDoneCallbacksCheckboxButton; |
IBOutlet SpeakingCharacterView *fCharacterView; |
// Misc. instance variables |
NSRange fOrgSelectionRange; |
long fSelectedVoiceID; |
long fSelectedVoiceCreator; |
SpeechChannel fCurSpeechChannel; |
long fOffsetToSpokenText; |
unsigned long fLastErrorCode; |
BOOL fLastSpeakingValue; |
BOOL fLastPausedValue; |
BOOL fCurrentlySpeaking; |
BOOL fCurrentlyPaused; |
BOOL fSavingToFile; |
NSData *fTextData; |
NSString *fTextDataType; |
NSString *fErrorFormatString; |
} |
- (instancetype)init; |
// Getters/Setters |
@property (NS_NONATOMIC_IOSONLY, copy) NSData *textData; |
@property (NS_NONATOMIC_IOSONLY, copy) NSString *textDataType; |
@property (NS_NONATOMIC_IOSONLY, readonly, strong) SpeakingCharacterView *characterView; |
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldDisplayWordCallbacks; |
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldDisplayPhonemeCallbacks; |
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldDisplayErrorCallbacks; |
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldDisplaySyncCallbacks; |
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldDisplaySpeechDoneCallbacks; |
@property (NS_NONATOMIC_IOSONLY, readonly) BOOL shouldDisplayTextDoneCallbacks; |
@end |
#pragma mark - Constants |
NSString *kPlainTextDataTypeString = @"Plain Text"; |
NSString *kDefaultWindowTextString = @"Welcome to Cocoa Speech Synthesis Example. " |
"This application provides an example of using Apple's speech synthesis technology in a Cocoa-based application."; |
NSString *kWordCallbackParamPosition = @"ParamPosition"; |
NSString *kWordCallbackParamLength = @"ParamLength"; |
NSString *kErrorCallbackParamPosition = @"ParamPosition"; |
NSString *kErrorCallbackParamError = @"ParamError"; |
#pragma mark - Prototypes |
static void OurTextDoneCallBackProc(SpeechChannel inSpeechChannel, |
SRefCon inRefCon, |
const void ** inNextBuf, |
unsigned long * inByteLen, |
long * inControlFlags); |
static void OurSpeechDoneCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon); |
static void OurSyncCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, OSType inSyncMessage); |
static void OurPhonemeCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, short inPhonemeOpcode); |
static void OurErrorCFCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, CFErrorRef inCFErrorRef); |
static void OurWordCFCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, CFStringRef inCFStringRef, CFRange inWordCFRange); |
#pragma mark - |
@implementation SpeakingTextWindow |
/*---------------------------------------------------------------------------------------- |
init |
Set the default text of the window. |
----------------------------------------------------------------------------------------*/ |
- (instancetype)init { |
self = [super init]; |
if (self) { |
// set our default window text |
const char *p = [kDefaultWindowTextString UTF8String]; |
[self setTextData:[NSData dataWithBytes:p length:strlen(p)]]; |
[self setTextDataType:kPlainTextDataTypeString]; |
} |
return (self); |
} // init |
/*---------------------------------------------------------------------------------------- |
close |
Make sure to stop speech when closing. |
----------------------------------------------------------------------------------------*/ |
- (void)close { |
[self startStopButtonPressed:fStartStopButton]; |
} |
/*---------------------------------------------------------------------------------------- |
setTextData: |
Set our text data variable and update text in window if showing. |
----------------------------------------------------------------------------------------*/ |
- (void)setTextData:(NSData *)theData { |
fTextData = theData; |
// If the window is showing, update the text view. |
if (fSpokenTextView) { |
if ([[self textDataType] isEqualToString:@"RTF Document"]) { |
[fSpokenTextView replaceCharactersInRange:NSMakeRange(0, [[fSpokenTextView string] length]) withRTF:[self textData]]; |
} else { |
[fSpokenTextView replaceCharactersInRange:NSMakeRange(0, [[fSpokenTextView string] length]) |
withString:[NSString stringWithUTF8String:[[self textData] bytes]]]; |
} |
} |
} // setTextData |
/*---------------------------------------------------------------------------------------- |
textData |
Returns autoreleased copy of text data. |
----------------------------------------------------------------------------------------*/ |
- (NSData *)textData { |
return ([fTextData copy]); |
} |
/*---------------------------------------------------------------------------------------- |
setTextDataType: |
Set our text data type variable. |
----------------------------------------------------------------------------------------*/ |
- (void)setTextDataType:(NSString *)theType { |
fTextDataType = theType; |
} // setTextDataType |
/*---------------------------------------------------------------------------------------- |
textDataType |
Returns autoreleased copy of text data. |
----------------------------------------------------------------------------------------*/ |
- (NSString *)textDataType { |
return ([fTextDataType copy]); |
} |
/*---------------------------------------------------------------------------------------- |
textDataType |
Returns reference to character view for callbacks. |
----------------------------------------------------------------------------------------*/ |
- (SpeakingCharacterView *)characterView { |
return (fCharacterView); |
} |
/*---------------------------------------------------------------------------------------- |
shouldDisplayWordCallbacks |
Returns true if user has chosen to have words hightlight during synthesis. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)shouldDisplayWordCallbacks { |
return ([fHandleWordCallbacksCheckboxButton intValue]); |
} |
/*---------------------------------------------------------------------------------------- |
shouldDisplayPhonemeCallbacks |
Returns true if user has chosen to the character animate phonemes during synthesis. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)shouldDisplayPhonemeCallbacks { |
return ([fHandlePhonemeCallbacksCheckboxButton intValue]); |
} |
/*---------------------------------------------------------------------------------------- |
shouldDisplayErrorCallbacks |
Returns true if user has chosen to have an alert appear in response to an error callback. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)shouldDisplayErrorCallbacks { |
return ([fHandleErrorCallbacksCheckboxButton intValue]); |
} |
/*---------------------------------------------------------------------------------------- |
shouldDisplaySyncCallbacks |
Returns true if user has chosen to have an alert appear in response to an sync callback. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)shouldDisplaySyncCallbacks { |
return ([fHandleSyncCallbacksCheckboxButton intValue]); |
} |
/*---------------------------------------------------------------------------------------- |
shouldDisplaySpeechDoneCallbacks |
Returns true if user has chosen to have an alert appear when synthesis is finished. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)shouldDisplaySpeechDoneCallbacks { |
return ([fHandleSpeechDoneCallbacksCheckboxButton intValue]); |
} |
/*---------------------------------------------------------------------------------------- |
shouldDisplayTextDoneCallbacks |
Returns true if user has chosen to have an alert appear when text processing is finished. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)shouldDisplayTextDoneCallbacks { |
return ([fHandleTextDoneCallbacksCheckboxButton intValue]); |
} |
/*---------------------------------------------------------------------------------------- |
updateSpeakingControlState |
This routine is called when appropriate to update the Start/Stop Speaking, |
Pause/Continue Speaking buttons. |
----------------------------------------------------------------------------------------*/ |
- (void)updateSpeakingControlState { |
// Update controls based on speaking state |
fSaveAsFileButton.enabled = !fCurrentlySpeaking; |
fPauseContinueButton.enabled = fCurrentlySpeaking; |
fStartStopButton.enabled = !fCurrentlyPaused; |
if (fCurrentlySpeaking) { |
[fStartStopButton setTitle:NSLocalizedString(@"Stop Speaking", @"Stop Speaking")]; |
[fPauseContinueButton setTitle:NSLocalizedString(@"Pause Speaking", @"Pause Speaking")]; |
} else { |
[fStartStopButton setTitle:NSLocalizedString(@"Start Speaking", @"Start Speaking")]; |
[fSpokenTextView setSelectedRange:fOrgSelectionRange]; // Set selection length to zero. |
} |
if (fCurrentlyPaused) { |
[fPauseContinueButton setTitle:NSLocalizedString(@"Continue Speaking", @"Continue Speaking")]; |
} else { |
[fPauseContinueButton setTitle:NSLocalizedString(@"Pause Speaking", @"Pause Speaking")]; |
} |
[self enableOptionsForSpeakingState:fCurrentlySpeaking]; |
// update parameter fields |
NSNumber *valueAsNSNumber; |
if (noErr == CopySpeechProperty(fCurSpeechChannel, kSpeechRateProperty, (void *)&valueAsNSNumber)) { |
[fRateCurrentStaticField setDoubleValue:[valueAsNSNumber doubleValue]]; |
} |
if (noErr == CopySpeechProperty(fCurSpeechChannel, kSpeechPitchBaseProperty, (void *)&valueAsNSNumber)) { |
[fPitchBaseCurrentStaticField setDoubleValue:[valueAsNSNumber doubleValue]]; |
} |
if (noErr == CopySpeechProperty(fCurSpeechChannel, kSpeechPitchModProperty, (void *)&valueAsNSNumber)) { |
[fPitchModCurrentStaticField setDoubleValue:[valueAsNSNumber doubleValue]]; |
} |
if (noErr == CopySpeechProperty(fCurSpeechChannel, kSpeechVolumeProperty, (void *)&valueAsNSNumber)) { |
[fVolumeCurrentStaticField setDoubleValue:[valueAsNSNumber doubleValue]]; |
} |
} // updateSpeakingControlState |
/*---------------------------------------------------------------------------------------- |
highlightWordWithParams: |
Highlights the word currently being spoken based on text position and text length |
provided in the word callback routine. |
----------------------------------------------------------------------------------------*/ |
- (void)highlightWordWithParams:(NSDictionary *)params { |
UInt32 selectionPosition = [params[kWordCallbackParamPosition] longValue] + fOffsetToSpokenText; |
UInt32 wordLength = [params[kWordCallbackParamLength] longValue]; |
[fSpokenTextView scrollRangeToVisible:NSMakeRange(selectionPosition, wordLength)]; |
[fSpokenTextView setSelectedRange:NSMakeRange(selectionPosition, wordLength)]; |
[fSpokenTextView display]; |
} // highlightWordWithParams |
/*---------------------------------------------------------------------------------------- |
displayErrorAlertWithParams: |
Displays an alert describing a text processing error provided in the error callback. |
----------------------------------------------------------------------------------------*/ |
- (void)displayErrorAlertWithParams:(NSDictionary *)params { |
UInt32 errorPosition = [params[kErrorCallbackParamPosition] longValue] + fOffsetToSpokenText; |
UInt32 errorCode = [params[kErrorCallbackParamError] longValue]; |
if (errorCode != fLastErrorCode) { |
OSErr theErr = noErr; |
NSString *theMessageStr = NULL; |
// Tell engine to pause while we display this dialog. |
theErr = PauseSpeechAt(fCurSpeechChannel, kImmediate); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"PauseSpeechAt" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
// Select offending character |
[fSpokenTextView setSelectedRange:NSMakeRange(errorPosition, 1)]; |
[fSpokenTextView display]; |
// Display error alert, and stop or continue based on user's desires |
NSString * messageFormat = NSLocalizedString(@"Error #%ld occurred at position %ld in the text.", |
@"Error #%ld occurred at position %ld in the text."); |
theMessageStr = [NSString stringWithFormat:messageFormat, |
(long) errorCode, (long) errorPosition]; |
NSModalResponse response = [self runAlertPanelWithTitle:@"Text Processing Error" |
message:theMessageStr |
buttonTitles:@[@"Stop", @"Continue"]]; |
if (NSAlertFirstButtonReturn == response) { |
[self startStopButtonPressed:fStartStopButton]; |
} else { |
theErr = ContinueSpeech(fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"ContinueSpeech" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
fLastErrorCode = errorCode; |
} |
} // displayErrorAlertWithParams |
/*---------------------------------------------------------------------------------------- |
displaySyncAlertWithMessage: |
Displays an alert with information about a sync command in response to a sync callback. |
----------------------------------------------------------------------------------------*/ |
- (void)displaySyncAlertWithMessage:(NSNumber *)messageNumber { |
OSErr theErr = noErr; |
NSString *theMessageStr = NULL; |
// Tell engine to pause while we display this dialog. |
theErr = PauseSpeechAt(fCurSpeechChannel, kImmediate); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"PauseSpeechAt" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
// Display error alert and stop or continue based on user's desires |
UInt32 theMessageValue = [messageNumber longValue]; |
NSString * messageFormat = NSLocalizedString(@"Sync embedded command was discovered containing message %ld ('%4s').", |
@"Sync embedded command was discovered containing message %ld ('%4s')."); |
theMessageStr = [NSString stringWithFormat:messageFormat, |
(long) theMessageValue, (char *) &theMessageValue]; |
NSInteger alertButtonClicked = |
[self runAlertPanelWithTitle:@"Sync Callback" message:theMessageStr buttonTitles:@[@"Stop", @"Continue"]]; |
if (alertButtonClicked == 1) { |
[self startStopButtonPressed:fStartStopButton]; |
} else { |
theErr = ContinueSpeech(fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"ContinueSpeech" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
} // displaySyncAlertWithMessage |
/*---------------------------------------------------------------------------------------- |
speechIsDone |
Updates user interface and optionally displays an alert when generation of speech is |
finish. |
----------------------------------------------------------------------------------------*/ |
- (void)speechIsDone { |
fCurrentlySpeaking = NO; |
[self updateSpeakingControlState]; |
[self enableCallbackControlsBasedOnSavingToFileFlag:NO]; |
if ([self shouldDisplaySpeechDoneCallbacks]) { |
[self runAlertPanelWithTitle:@"Speech Done" |
message:@"Generation of synthesized speech is finished." |
buttonTitles:@[@"OK"]]; |
} |
} // speechIsDone |
/*---------------------------------------------------------------------------------------- |
displayTextDoneAlert |
Displays an alert in response to a text done callback. |
----------------------------------------------------------------------------------------*/ |
- (void)displayTextDoneAlert { |
OSErr theErr = noErr; |
// Tell engine to pause while we display this dialog. |
theErr = PauseSpeechAt(fCurSpeechChannel, kImmediate); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"PauseSpeechAt" |
message:@"Generation of synthesized speech is finished." |
buttonTitles:@[@"OK"]]; |
} |
// Display error alert, and stop or continue based on user's desires |
NSModalResponse response = [self runAlertPanelWithTitle:@"Text Done Callback" |
message:@"Processing of the text has completed." |
buttonTitles:@[@"Stop", @"Continue"]]; |
if (NSAlertFirstButtonReturn == response) { |
[self startStopButtonPressed:fStartStopButton]; |
} else { |
theErr = ContinueSpeech(fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"ContinueSpeech" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
} // displayTextDoneAlert |
/*---------------------------------------------------------------------------------------- |
startStopButtonPressed: |
An action method called when the user clicks the "Start Speaking"/"Stop Speaking" |
button. We either start or stop speaking based on the current speaking state. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)startStopButtonPressed:(id)sender { |
OSErr theErr = noErr; |
if (fCurrentlySpeaking) { |
long whereToStop; |
// Grab where to stop at value from radio buttons |
if ([fAfterWordRadioButton intValue]) { |
whereToStop = kEndOfWord; |
} else if ([fAfterSentenceRadioButton intValue]) { |
whereToStop = kEndOfSentence; |
} else { |
whereToStop = kImmediate; |
} |
if (whereToStop == kImmediate) { |
// NOTE: We could just call StopSpeechAt with kImmediate, but for test purposes |
// we exercise the StopSpeech routine. |
theErr = StopSpeech(fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"StopSpeech" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} else { |
theErr = StopSpeechAt(fCurSpeechChannel, whereToStop); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"StopSpeechAt" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
fCurrentlySpeaking = NO; |
[self updateSpeakingControlState]; |
} else { |
[self startSpeakingTextViewToURL:NULL]; |
} |
} // startStopButtonPressed |
/*---------------------------------------------------------------------------------------- |
saveAsButtonPressed: |
An action method called when the user clicks the "Save As File" button. We ask user |
to specify where to save the file, then start speaking to this file. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)saveAsButtonPressed:(id)sender { |
NSURL *selectedFileURL = NULL; |
NSSavePanel *theSavePanel = [NSSavePanel savePanel]; |
[theSavePanel setPrompt:NSLocalizedString(@"Save", @"Save")]; |
[theSavePanel setNameFieldStringValue:@"Synthesized Speech.aiff"]; |
if (NSFileHandlingPanelOKButton == [theSavePanel runModal]) { |
selectedFileURL = [theSavePanel URL]; |
[self startSpeakingTextViewToURL:selectedFileURL]; |
} |
} // saveAsButtonPressed |
/*---------------------------------------------------------------------------------------- |
startSpeakingTextViewToURL: |
This method sets up the speech channel and begins the speech synthesis |
process, optionally speaking to a file instead playing through the speakers. |
----------------------------------------------------------------------------------------*/ |
- (void)startSpeakingTextViewToURL:(NSURL *)url { |
OSErr theErr = noErr; |
NSString *theViewText; |
// Grab the selection substring, or if no selection then grab entire text. |
fOrgSelectionRange = [fSpokenTextView selectedRange]; |
if (!fOrgSelectionRange.length) { |
theViewText = [fSpokenTextView string]; |
fOffsetToSpokenText = 0; |
} else { |
theViewText = [[fSpokenTextView string] substringWithRange:fOrgSelectionRange]; |
fOffsetToSpokenText = fOrgSelectionRange.location; |
} |
// Setup our callbacks |
fSavingToFile = (url != NULL); |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechErrorCFCallBack, |
(__bridge CFTypeRef)(@(fSavingToFile ? (long)NULL : (long)OurErrorCFCallBackProc))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechErrorCFCallBack)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechPhonemeCallBack, |
(__bridge CFTypeRef)(@(fSavingToFile ? (long)NULL : (long)OurPhonemeCallBackProc))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechPhonemeCallBack)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, |
kSpeechSpeechDoneCallBack, |
(__bridge CFTypeRef)(@((long)OurSpeechDoneCallBackProc))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechSpeechDoneCallBack)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechSyncCallBack, |
(__bridge CFTypeRef)(@(fSavingToFile ? (long)NULL : (long)OurSyncCallBackProc))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechSyncCallBack)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechTextDoneCallBack, |
(__bridge CFTypeRef)(@(fSavingToFile ? (long)NULL : (long)OurTextDoneCallBackProc))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechTextDoneCallBack)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechWordCFCallBack, |
(__bridge CFTypeRef)(@(fSavingToFile ? (long)NULL : (long)OurWordCFCallBackProc))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechWordCFCallBack)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
// Set URL to save file to disk |
SetSpeechProperty(fCurSpeechChannel, kSpeechOutputToFileURLProperty, (__bridge CFTypeRef)(url)); |
// Convert NSString to cString. |
// We want the text view the active view. Also saves any parameters currently being edited. |
[fWindow makeFirstResponder:fSpokenTextView]; |
theErr = SpeakCFString(fCurSpeechChannel, (__bridge CFStringRef)theViewText, NULL); |
if (noErr == theErr) { |
// Update our vars |
fLastErrorCode = 0; |
fLastSpeakingValue = NO; |
fLastPausedValue = NO; |
fCurrentlySpeaking = YES; |
fCurrentlyPaused = NO; |
[self updateSpeakingControlState]; |
} else { |
[self runAlertPanelWithTitle:@"SpeakText" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
[self enableCallbackControlsBasedOnSavingToFileFlag:fSavingToFile]; |
} // startSpeakingTextViewToURL |
/*---------------------------------------------------------------------------------------- |
pauseContinueButtonPressed: |
An action method called when the user clicks the "Pause Speaking"/"Continue Speaking" |
button. We either pause or continue speaking based on the current speaking state. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)pauseContinueButtonPressed:(id)sender { |
OSErr theErr = noErr; |
if (fCurrentlyPaused) { |
// We want the text view the active view. Also saves any parameters currently being edited. |
[fWindow makeFirstResponder:fSpokenTextView]; |
theErr = ContinueSpeech(fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"ContinueSpeech" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
fCurrentlyPaused = NO; |
[self updateSpeakingControlState]; |
} else { |
long whereToPause; |
// Figure out where to stop from radio buttons |
if ([fAfterWordRadioButton intValue]) { |
whereToPause = kEndOfWord; |
} else if ([fAfterSentenceRadioButton intValue]) { |
whereToPause = kEndOfSentence; |
} else { |
whereToPause = kImmediate; |
} |
theErr = PauseSpeechAt(fCurSpeechChannel, whereToPause); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"PauseSpeechAt" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
fCurrentlyPaused = YES; |
[self updateSpeakingControlState]; |
} |
} // pauseContinueButtonPressed |
/*---------------------------------------------------------------------------------------- |
voicePopupSelected: |
An action method called when the user selects a new voice from the Voices pop-up |
menu. We ask the speech channel to use the selected voice. If the current |
speech channel cannot use the selected voice, we close and open new speech |
channel with the selecte voice. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)voicePopupSelected:(id)sender { |
OSErr theErr = noErr; |
long theSelectedMenuIndex = [sender indexOfSelectedItem]; |
if (!theSelectedMenuIndex) { |
// Use the default voice from preferences. |
// Our only choice is to close and reopen the speech channel to get the default voice. |
fSelectedVoiceCreator = 0; |
theErr = [self createNewSpeechChannel:NULL]; |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"createNewSpeechChannel" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} else { |
// Use the voice the user selected. |
VoiceSpec theVoiceSpec; |
theErr = GetIndVoice([sender indexOfSelectedItem] - 1, &theVoiceSpec); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"GetIndVoice" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
if (noErr == theErr) { |
// Update our object fields with the selection |
fSelectedVoiceCreator = theVoiceSpec.creator; |
fSelectedVoiceID = theVoiceSpec.id; |
// Change the current voice. If it needs another engine, then dispose the current channel and open another |
NSDictionary *voiceDict = @{(__bridge NSString *)kSpeechVoiceID:@(fSelectedVoiceID), |
(__bridge NSString *)kSpeechVoiceCreator:@(fSelectedVoiceCreator)}; |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechCurrentVoiceProperty, (__bridge CFDictionaryRef) voiceDict); |
if (incompatibleVoice == theErr) { |
theErr = [self createNewSpeechChannel:&theVoiceSpec]; |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"createNewSpeechChannel" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} else if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechCurrentVoiceProperty" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
} |
// Set editable default fields |
if (fCurSpeechChannel) { |
[self fillInEditableParameterFields]; |
} |
} // voicePopupSelected |
/*---------------------------------------------------------------------------------------- |
charByCharCheckboxSelected: |
An action method called when the user checks/unchecks the Character-By-Character |
mode checkbox. We tell the speech channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)charByCharCheckboxSelected:(id)sender { |
OSErr theErr = noErr; |
if ([fCharByCharCheckboxButton intValue]) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechCharacterModeProperty, kSpeechModeLiteral); |
} else { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechCharacterModeProperty, kSpeechModeNormal); |
} |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechCharacterModeProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} // charByCharCheckboxSelected |
/*---------------------------------------------------------------------------------------- |
digitByDigitCheckboxSelected: |
An action method called when the user checks/unchecks the Digit-By-Digit |
mode checkbox. We tell the speech channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)digitByDigitCheckboxSelected:(id)sender { |
OSErr theErr = noErr; |
if ([fDigitByDigitCheckboxButton intValue]) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechNumberModeProperty, kSpeechModeLiteral); |
} else { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechNumberModeProperty, kSpeechModeNormal); |
} |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechNumberModeProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} // digitByDigitCheckboxSelected |
/*---------------------------------------------------------------------------------------- |
phonemeModeCheckboxSelected: |
An action method called when the user checks/unchecks the Phoneme input |
mode checkbox. We tell the speech channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)phonemeModeCheckboxSelected:(id)sender { |
OSErr theErr = noErr; |
if ([fPhonemeModeCheckboxButton intValue]) { |
#if 1 |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechInputModeProperty, kSpeechModePhoneme); |
#else |
OSType theMode = modePhonemes; |
theErr = SetSpeechInfo(fCurSpeechChannel, soInputMode, &theMode); |
#endif |
} else { |
#if 1 |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechInputModeProperty, kSpeechModeText); |
#else |
OSType theMode = modeText; |
theErr = SetSpeechInfo(fCurSpeechChannel, soInputMode, &theMode); |
#endif |
} |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechInputModeProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} // phonemeModeCheckboxSelected |
/*---------------------------------------------------------------------------------------- |
dumpPhonemesSelected: |
An action method called when the user clicks the Dump Phonemes button. We ask |
the speech channel for a phoneme representation of the window text then save the |
result to a text file at a location determined by the user. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)dumpPhonemesSelected:(id)sender { |
NSSavePanel *panel = [NSSavePanel savePanel]; |
if ([panel runModal] && [panel URL]) { |
// Get and speech text |
CFStringRef phonemesCFStringRef; |
OSErr theErr = CopyPhonemesFromText(fCurSpeechChannel, (__bridge CFStringRef)[fSpokenTextView string], &phonemesCFStringRef); |
if (noErr == theErr) { |
NSString *phonemesString = (__bridge NSString *)((CFStringRef) phonemesCFStringRef); |
NSError *nsError; |
if (![phonemesString writeToURL:[panel URL] atomically:YES encoding:NSUTF8StringEncoding error:&nsError]) { |
NSString * messageFormat = NSLocalizedString(@"writeToURL: '%@' error: %@", |
@"writeToURL: '%@' error: %@"); |
[self runAlertPanelWithTitle:@"CopyPhonemesFromText" |
message:[NSString stringWithFormat:messageFormat, [panel URL], nsError] |
buttonTitles:@[@"Oh?"]]; |
} |
} else { |
[self runAlertPanelWithTitle:@"CopyPhonemesFromText" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
if (phonemesCFStringRef) { |
CFRelease(phonemesCFStringRef); |
} |
} |
} // dumpPhonemesSelected |
/*---------------------------------------------------------------------------------------- |
useDictionarySelected: |
An action method called when the user clicks the "Use Dictionary…" button. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)useDictionarySelected:(id)sender { |
// Open file. |
NSOpenPanel *panel = [NSOpenPanel openPanel]; |
panel.message = @"Choose a dictionary file"; |
panel.allowedFileTypes = @[@"xml", @"plist"]; |
[panel setAllowsMultipleSelection:YES]; |
if ([panel runModal]) { |
for (NSURL *fileURL in[panel URLs]) { |
// Read dictionary file into NSData object. |
NSDictionary *speechDictionary = [NSDictionary dictionaryWithContentsOfURL:fileURL]; |
if (speechDictionary) { |
OSErr theErr = UseSpeechDictionary(fCurSpeechChannel, (__bridge CFDictionaryRef) speechDictionary); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"UseSpeechDictionary" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} else { |
NSString * messageFormat = NSLocalizedString(@"dictionaryWithContentsOfURL:'%@' returned NULL", |
@"dictionaryWithContentsOfURL:'%@' returned NULL"); |
[self runAlertPanelWithTitle:@"TextToPhonemes" |
message:[NSString stringWithFormat:messageFormat, [fileURL path]] |
buttonTitles:@[@"Oh?"]]; |
} |
} // next fileURL in [panel URLs] |
} |
} // useDictionarySelected |
/*---------------------------------------------------------------------------------------- |
rateChanged: |
An action method called when the user changes the rate field. We tell the speech |
channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)rateChanged:(id)sender { |
OSErr theErr = SetSpeechProperty(fCurSpeechChannel, |
kSpeechRateProperty, |
(__bridge CFTypeRef)(@([fRateDefaultEditableField doubleValue]))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechRate" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} else { |
[fRateCurrentStaticField setDoubleValue:[fRateDefaultEditableField doubleValue]]; |
} |
} // rateChanged |
/*---------------------------------------------------------------------------------------- |
pitchBaseChanged: |
An action method called when the user changes the pitch base field. We tell the speech |
channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)pitchBaseChanged:(id)sender { |
OSErr theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechPitchBaseProperty, |
(__bridge CFTypeRef)(@([fPitchBaseDefaultEditableField doubleValue]))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechPitch" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} else { |
[fPitchBaseCurrentStaticField setDoubleValue:[fPitchBaseDefaultEditableField doubleValue]]; |
} |
} // pitchBaseChanged |
/*---------------------------------------------------------------------------------------- |
pitchModChanged: |
An action method called when the user changes the pitch modulation field. We tell |
the speech channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)pitchModChanged:(id)sender { |
OSErr theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechPitchModProperty, |
(__bridge CFTypeRef)(@([fPitchModDefaultEditableField doubleValue]))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechPitchModProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} else { |
[fPitchModCurrentStaticField setDoubleValue:[fPitchModDefaultEditableField doubleValue]]; |
} |
} // pitchModChanged |
/*---------------------------------------------------------------------------------------- |
volumeChanged: |
An action method called when the user changes the volume field. We tell |
the speech channel to use this setting. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)volumeChanged:(id)sender { |
OSErr theErr = SetSpeechProperty(fCurSpeechChannel, |
kSpeechVolumeProperty, |
(__bridge CFTypeRef)(@([fVolumeDefaultEditableField doubleValue]))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechVolumeProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} else { |
[fVolumeCurrentStaticField setDoubleValue:[fVolumeDefaultEditableField doubleValue]]; |
} |
} // volumeChanged |
/*---------------------------------------------------------------------------------------- |
resetSelected: |
An action method called when the user clicks the Use Defaults button. We tell |
the speech channel to use this the default settings. |
----------------------------------------------------------------------------------------*/ |
- (IBAction)resetSelected:(id)sender { |
OSErr theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechResetProperty, NULL); |
[self fillInEditableParameterFields]; |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechResetProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} // resetSelected |
- (IBAction)wordCallbacksButtonPressed:(id)sender { |
if (![fHandleWordCallbacksCheckboxButton intValue]) { |
[fSpokenTextView setSelectedRange:fOrgSelectionRange]; |
} |
} // wordCallbacksButtonPressed |
- (IBAction)phonemeCallbacksButtonPressed:(id)sender { |
if ([fHandlePhonemeCallbacksCheckboxButton intValue]) { |
[fCharacterView setExpression:kCharacterExpressionIdentifierIdle]; |
} else { |
[fCharacterView setExpression:kCharacterExpressionIdentifierSleep]; |
} |
} // phonemeCallbacksButtonPressed |
/*---------------------------------------------------------------------------------------- |
enableOptionsForSpeakingState: |
Updates controls in the Option tab panel based on the passed speakingNow flag. |
----------------------------------------------------------------------------------------*/ |
- (void)enableOptionsForSpeakingState:(BOOL)speakingNow { |
fVoicesPopUpButton.enabled = !speakingNow; |
fCharByCharCheckboxButton.enabled = !speakingNow; |
fDigitByDigitCheckboxButton.enabled = !speakingNow; |
fPhonemeModeCheckboxButton.enabled = !speakingNow; |
fDumpPhonemesButton.enabled = !speakingNow; |
fUseDictionaryButton.enabled = !speakingNow; |
} // enableOptionsForSpeakingState |
/*---------------------------------------------------------------------------------------- |
enableCallbackControlsForSavingToFile: |
Updates controls in the Callback tab panel based on the passed savingToFile flag. |
----------------------------------------------------------------------------------------*/ |
- (void)enableCallbackControlsBasedOnSavingToFileFlag:(BOOL)savingToFile { |
fHandleWordCallbacksCheckboxButton.enabled = !savingToFile; |
fHandlePhonemeCallbacksCheckboxButton.enabled = !savingToFile; |
fHandleSyncCallbacksCheckboxButton.enabled = !savingToFile; |
fHandleErrorCallbacksCheckboxButton.enabled = !savingToFile; |
fHandleTextDoneCallbacksCheckboxButton.enabled = !savingToFile; |
if (savingToFile || (![fHandlePhonemeCallbacksCheckboxButton intValue])) { |
[fCharacterView setExpression:kCharacterExpressionIdentifierSleep]; |
} else { |
[fCharacterView setExpression:kCharacterExpressionIdentifierIdle]; |
} |
} // enableCallbackControlsBasedOnSavingToFileFlag |
/*---------------------------------------------------------------------------------------- |
fillInEditableParameterFields |
Updates "Current" fields in the Parameters tab panel based on the current state of the |
speech channel. |
----------------------------------------------------------------------------------------*/ |
- (void)fillInEditableParameterFields { |
double tempDoubleValue = 0.0; |
NSNumber *tempNSNumber = NULL; |
CopySpeechProperty(fCurSpeechChannel, kSpeechRateProperty, (void *)&tempNSNumber); |
tempDoubleValue = [tempNSNumber doubleValue]; |
[fRateDefaultEditableField setDoubleValue:tempDoubleValue]; |
[fRateCurrentStaticField setDoubleValue:tempDoubleValue]; |
CopySpeechProperty(fCurSpeechChannel, kSpeechPitchBaseProperty, (void *)&tempNSNumber); |
tempDoubleValue = [tempNSNumber doubleValue]; |
[fPitchBaseDefaultEditableField setDoubleValue:tempDoubleValue]; |
[fPitchBaseCurrentStaticField setDoubleValue:tempDoubleValue]; |
CopySpeechProperty(fCurSpeechChannel, kSpeechPitchModProperty, (void *)&tempNSNumber); |
tempDoubleValue = [tempNSNumber doubleValue]; |
[fPitchModDefaultEditableField setDoubleValue:tempDoubleValue]; |
[fPitchModCurrentStaticField setDoubleValue:tempDoubleValue]; |
CopySpeechProperty(fCurSpeechChannel, kSpeechVolumeProperty, (void *)&tempNSNumber); |
tempDoubleValue = [tempNSNumber doubleValue]; |
[fVolumeDefaultEditableField setDoubleValue:tempDoubleValue]; |
[fVolumeCurrentStaticField setDoubleValue:tempDoubleValue]; |
} // fillInEditableParameterFields |
/*---------------------------------------------------------------------------------------- |
createNewSpeechChannel: |
Create a new speech channel for the given voice spec. A nil voice spec pointer |
causes the speech channel to use the default voice. Any existing speech channel |
for this window is closed first. |
----------------------------------------------------------------------------------------*/ |
- (OSErr)createNewSpeechChannel:(VoiceSpec *)voiceSpec { |
OSErr theErr = noErr; |
// Dispose of the current one, if present. |
if (fCurSpeechChannel) { |
theErr = DisposeSpeechChannel(fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"DisposeSpeechChannel" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
fCurSpeechChannel = NULL; |
} |
// Create a speech channel |
if (noErr == theErr) { |
theErr = NewSpeechChannel(voiceSpec, &fCurSpeechChannel); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"NewSpeechChannel" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
// Setup our refcon to the document controller object so we have access within our Speech callbacks |
if (noErr == theErr) { |
theErr = SetSpeechProperty(fCurSpeechChannel, kSpeechRefConProperty, (__bridge CFTypeRef)(@((long)self))); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"SetSpeechProperty(kSpeechRefConProperty)" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
} |
return (theErr); |
} // createNewSpeechChannel |
#pragma mark - Window |
/*---------------------------------------------------------------------------------------- |
awakeFromNib |
This routine is call once right after our nib file is loaded. We build our voices |
pop-up menu, create a new speech channel and update our window using parameters from |
the new speech channel. |
----------------------------------------------------------------------------------------*/ |
- (void)awakeFromNib { |
OSErr theErr = noErr; |
fErrorFormatString = NSLocalizedString(@"Error #%d (0x%0X) returned.", @"Error #%d (0x%0X) returned."); |
// Build the Voices pop-up menu |
{ |
short numOfVoices; |
long voiceIndex; |
BOOL voiceFoundAndSelected = NO; |
VoiceSpec theVoiceSpec; |
// Delete the existing voices from the bottom of the menu. |
while ([fVoicesPopUpButton numberOfItems] > 2) { |
[fVoicesPopUpButton removeItemAtIndex:2]; |
} |
// Ask TTS API for each available voicez |
theErr = CountVoices(&numOfVoices); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"CountVoices" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
if (noErr == theErr) { |
for (voiceIndex = 1; voiceIndex <= numOfVoices; voiceIndex++) { |
VoiceDescription theVoiceDesc; |
theErr = GetIndVoice(voiceIndex, &theVoiceSpec); |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"GetIndVoice" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
if (noErr == theErr) { |
theErr = GetVoiceDescription(&theVoiceSpec, &theVoiceDesc, sizeof(theVoiceDesc)); |
} |
if (noErr != theErr) { |
[self runAlertPanelWithTitle:@"GetVoiceDescription" |
message:[NSString stringWithFormat:fErrorFormatString, theErr, theErr] |
buttonTitles:@[@"Oh?"]]; |
} |
if (noErr == theErr) { |
// Get voice name and add it to the menu list |
NSString *theNameString = @((char *) &(theVoiceDesc.name[1])); |
[fVoicesPopUpButton addItemWithTitle:theNameString]; |
// Selected this item if it matches our default voice spec. |
if ((theVoiceSpec.creator == fSelectedVoiceCreator) && (theVoiceSpec.id == fSelectedVoiceID)) { |
[fVoicesPopUpButton selectItemAtIndex:voiceIndex - 1]; |
voiceFoundAndSelected = YES; |
} |
} |
} |
// User preference default if problems. |
if (!voiceFoundAndSelected && (numOfVoices >= 1)) { |
// Update our object fields with the first voice |
fSelectedVoiceCreator = 0; |
fSelectedVoiceID = 0; |
[fVoicesPopUpButton selectItemAtIndex:0]; |
} |
} else { |
[fVoicesPopUpButton selectItemAtIndex:0]; |
} |
} |
// Create Speech Channel configured with our desired options and callbacks |
[self createNewSpeechChannel:NULL]; |
// Set editable default fields |
[self fillInEditableParameterFields]; |
// Enable buttons appropriatelly |
fStartStopButton.enabled = YES; |
fPauseContinueButton.enabled = NO; |
fSaveAsFileButton.enabled = YES; |
// Set starting expresison on animated character |
[self phonemeCallbacksButtonPressed:fHandlePhonemeCallbacksCheckboxButton]; |
} // awakeFromNib |
/*---------------------------------------------------------------------------------------- |
windowNibName |
Part of the NSDocument support. Called by NSDocument to return the nib file name of |
the document. |
----------------------------------------------------------------------------------------*/ |
- (NSString *)windowNibName { |
return (@"SpeakingTextWindow"); |
} |
/*---------------------------------------------------------------------------------------- |
windowControllerDidLoadNib: |
Part of the NSDocument support. Called by NSDocument after the nib has been loaded |
to udpate window as appropriate. |
----------------------------------------------------------------------------------------*/ |
- (void)windowControllerDidLoadNib:(NSWindowController *)aController { |
[super windowControllerDidLoadNib:aController]; |
// Update the window text from data |
if ([[self textDataType] isEqualToString:@"RTF Document"]) { |
[fSpokenTextView replaceCharactersInRange:NSMakeRange(0, [[fSpokenTextView string] length]) withRTF:[self textData]]; |
} else { |
[fSpokenTextView replaceCharactersInRange:NSMakeRange(0, |
[[fSpokenTextView string] length]) |
withString:[NSString stringWithUTF8String:[[self textData] bytes]]]; |
} |
} // windowControllerDidLoadNib |
#pragma mark - NSDocument |
/*---------------------------------------------------------------------------------------- |
dataRepresentationOfType: |
Part of the NSDocument support. Called by NSDocument to wrote the document. |
----------------------------------------------------------------------------------------*/ |
- (NSData *)dataRepresentationOfType:(NSString *)aType { |
// Write text to file. |
if ([aType isEqualToString:@"RTF Document"]) { |
[self setTextData:[fSpokenTextView RTFFromRange:NSMakeRange(0, [[fSpokenTextView string] length])]]; |
} else { |
[self setTextData:[NSData dataWithBytes:[[fSpokenTextView string] cString] length:[[fSpokenTextView string] cStringLength]]]; |
} |
return ([self textData]); |
} // dataRepresentationOfType |
/*---------------------------------------------------------------------------------------- |
loadDataRepresentation: ofType: |
Part of the NSDocument support. Called by NSDocument to read the document. |
----------------------------------------------------------------------------------------*/ |
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType { |
// Read the opened file. |
[self setTextData:data]; |
[self setTextDataType:aType]; |
return (YES); |
} // loadDataRepresentation |
#pragma mark - Utilities |
/*---------------------------------------------------------------------------------------- |
simple replacement method for NSRunAlertPanel |
----------------------------------------------------------------------------------------*/ |
- (NSModalResponse) runAlertPanelWithTitle:(NSString *)inTitle |
message:(NSString *)inMessage |
buttonTitles:(NSArray *)inButtonTitles |
{ |
NSAlert *alert = [[NSAlert alloc] init]; |
alert.messageText = NSLocalizedString(inTitle, inTitle); |
alert.informativeText = NSLocalizedString(inMessage, inMessage); |
for (NSString *buttonTitle in inButtonTitles) { |
[alert addButtonWithTitle:NSLocalizedString(buttonTitle, buttonTitle)]; |
} |
return ([alert runModal]); |
} // runAlertPanelWithTitle |
@end |
#pragma mark - Callback routines |
// |
// AN IMPORTANT NOTE ABOUT CALLBACKS AND THREADS |
// |
// All speech synthesis callbacks, except for the Text Done callback, call their specified routine on a |
// thread other than the main thread. Performing certain actions directly from a speech synthesis callback |
// routine may cause your program to crash without certain safe gaurds. In this example, we use the NSThread |
// method performSelectorOnMainThread:withObject:waitUntilDone: to safely update the user interface and |
// interact with our objects using only the main thread. |
// |
// Depending on your needs you may be able to specify your Cocoa application is multiple threaded |
// then preform actions directly from the speech synthesis callback routines. To indicate your Cocoa |
// application is mulitthreaded, call the following line before calling speech synthesis routines for |
// the first time: |
// |
// [NSThread detachNewThreadSelector:@selector(self) toTarget:self withObject:nil]; |
// |
/*---------------------------------------------------------------------------------------- |
OurErrorCFCallBackProc |
Called by speech channel when an error occurs during processing of text to speak. |
----------------------------------------------------------------------------------------*/ |
static void OurErrorCFCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, CFErrorRef inCFErrorRef) { |
@autoreleasepool { |
SpeakingTextWindow *stw = (__bridge SpeakingTextWindow *)inRefCon; |
if ([stw shouldDisplayErrorCallbacks]) { |
[[NSAlert alertWithError:(__bridge NSError *)inCFErrorRef] performSelectorOnMainThread:@selector(runModal) |
withObject:NULL |
waitUntilDone:YES]; |
} |
} |
} // OurErrorCFCallBackProc |
/*---------------------------------------------------------------------------------------- |
OurTextDoneCallBackProc |
Called by speech channel when all text has been processed. Additional text can be |
passed back to continue processing. |
----------------------------------------------------------------------------------------*/ |
static void OurTextDoneCallBackProc(SpeechChannel inSpeechChannel, |
SRefCon inRefCon, |
const void ** inNextBuf, |
unsigned long * inByteLen, |
long * inControlFlags) { |
@autoreleasepool { |
SpeakingTextWindow *stw = (__bridge SpeakingTextWindow *)inRefCon; |
*inNextBuf = NULL; |
if ([stw shouldDisplayTextDoneCallbacks]) { |
[stw performSelectorOnMainThread:@selector(displayTextDoneAlert) |
withObject:NULL |
waitUntilDone:NO]; |
} |
} |
} // OurTextDoneCallBackProc |
/*---------------------------------------------------------------------------------------- |
OurSpeechDoneCallBackProc |
Called by speech channel when all speech has been generated. |
----------------------------------------------------------------------------------------*/ |
static void OurSpeechDoneCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon) { |
@autoreleasepool { |
SpeakingTextWindow *stw = (__bridge SpeakingTextWindow *)inRefCon; |
[stw performSelectorOnMainThread:@selector(speechIsDone) |
withObject:NULL |
waitUntilDone:NO]; |
} |
} // OurSpeechDoneCallBackProc |
/*---------------------------------------------------------------------------------------- |
OurSyncCallBackProc |
Called by speech channel when it encouters a synchronization command within an |
embedded speech comand in text being processed. |
----------------------------------------------------------------------------------------*/ |
static void OurSyncCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, OSType inSyncMessage) { |
@autoreleasepool { |
SpeakingTextWindow *stw = (__bridge SpeakingTextWindow *)inRefCon; |
if ([stw shouldDisplaySyncCallbacks]) { |
[stw performSelectorOnMainThread:@selector(displaySyncAlertWithMessage:) |
withObject:@((long)inSyncMessage) |
waitUntilDone:NO]; |
} |
} |
} // OurSyncCallBackProc |
/*---------------------------------------------------------------------------------------- |
OurPhonemeCallBackProc |
Called by speech channel every time a phoneme is about to be generated. You might use |
this to animate a speaking character. |
----------------------------------------------------------------------------------------*/ |
static void OurPhonemeCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, short inPhonemeOpcode) { |
@autoreleasepool { |
SpeakingTextWindow *stw = (__bridge SpeakingTextWindow *)inRefCon; |
if ([stw shouldDisplayPhonemeCallbacks]) { |
[[stw characterView] performSelectorOnMainThread:@selector(setExpressionForPhoneme:) |
withObject:@(inPhonemeOpcode) |
waitUntilDone:NO]; |
} |
} |
} // OurPhonemeCallBackProc |
/*---------------------------------------------------------------------------------------- |
OurWordCallBackProc |
Called by speech channel every time a word is about to be generated. This program |
uses this callback to highlight the currently spoken word. |
----------------------------------------------------------------------------------------*/ |
static void OurWordCFCallBackProc(SpeechChannel inSpeechChannel, SRefCon inRefCon, CFStringRef inCFStringRef, CFRange inWordCFRange) { |
@autoreleasepool { |
SpeakingTextWindow *stw = (__bridge SpeakingTextWindow *)inRefCon; |
if ([stw shouldDisplayWordCallbacks]) { |
[stw performSelectorOnMainThread:@selector(highlightWordWithParams:) |
withObject:@{kWordCallbackParamPosition:@(inWordCFRange.location), kWordCallbackParamLength:@(inWordCFRange.length)} |
waitUntilDone:NO]; |
} |
} |
} // OurWordCFCallBackProc |
Copyright © 2015 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2015-07-10