// // ZoomTextToSpeech.m // ZoomCocoa // // Created by Andrew Hunter on 21/11/2004. // Copyright 2004 Andrew Hunter. All rights reserved. // #import #import "ZoomTextToSpeech.h" #undef UseCocoaSpeech #ifndef UseCocoaSpeech static SpeechChannel channel = nil; #endif @implementation ZoomTextToSpeech + (void) initialize { #ifndef UseCocoaSpeech if (channel == nil) NewSpeechChannel(NULL, &channel); #endif } - (id) init { #ifndef UseCocoaSpeech if (channel == nil) return nil; #endif self = [super init]; if (self) { text = [[NSMutableString alloc] init]; #ifdef UseCocoaSpeech synth = [[NSSpeechSynthesizer alloc] initWithVoice: [NSSpeechSynthesizer defaultVoice]]; #endif } return self; } - (void) dealloc { [text release]; [lastText release]; [skein release]; if (synth) { [synth stopSpeaking]; [synth release]; } [super dealloc]; } // = Direct output = - (void) inputCommand: (NSString*) command { [text appendString: @"\n\n"]; [text appendString: command]; [text appendString: @"\n\n"]; } - (void) inputCharacter: (NSString*) character { } - (void) outputText: (NSString*) outputText { [text appendString: outputText]; } // = Status notifications = - (void) zoomWaitingForInput { if (isImmediate) { [self speak: text]; } [lastText release]; lastText = [text copy]; [text release]; text = [[NSMutableString alloc] init]; [self resetMoves]; } - (void) zoomInterpreterRestart { [self zoomWaitingForInput]; } // = Glk automation = // Notifications about events that have occured in the view (when using this automation object for output) - (void) receivedCharacters: (NSString*) characters // Text has arrived at the specified text buffer window (from the game) window: (int) windowNumber fromView: (GlkView*) view { [text appendString: @"\n\n"]; [text appendString: characters]; } - (void) userTyped: (NSString*) userInput // The user has typed the specified string into the specified window (which is any window that is waiting for input) window: (int) windowNumber lineInput: (BOOL) isLineInput fromView: (GlkView*) view { [text appendString: @"\n\n"]; [text appendString: userInput]; [text appendString: @"...\n"]; } - (void) userClickedAtXPos: (int) xpos ypos: (int) ypos window: (int) windowNumber fromView: (GlkView*) view { } - (void) viewWaiting: (GlkView*) view { [self zoomWaitingForInput]; } - (void) setImmediate: (BOOL) immediateSpeech { isImmediate = immediateSpeech; } - (void) speakLastText { if (lastText) { [self speak: lastText]; } else { [self speak: @"No text is available for the last move"]; } } - (void) speak: (NSString*) newText { #ifndef UseCocoaSpeech char* buffer = NULL; int bufLen = 0; int x; #define WriteBuffer(x) buffer = realloc(buffer, bufLen+1); buffer[bufLen++] = x; BOOL whitespace = YES; BOOL newline = YES; BOOL punctuation = NO; for (x=0; x<[newText length]; x++) { unichar chr = [newText characterAtIndex: x]; if (chr != '\n' && chr != '\r' && (chr < 32 || chr >= 127)) chr = ' '; switch (chr) { case ' ': punctuation = NO; if (!whitespace) { whitespace = YES; WriteBuffer(' '); } break; case '\n': case '\r': if (!punctuation && !whitespace) { punctuation = YES; WriteBuffer('.'); } else { punctuation = NO; } if (!newline) { whitespace = YES; newline = YES; WriteBuffer('\n'); } break; case ',': case '.': case '?': case ';': case ':': case '!': if (!punctuation) { punctuation = YES; WriteBuffer(chr); } break; default: whitespace = newline = punctuation = NO; WriteBuffer(chr); } } WriteBuffer(0); SpeakBuffer(channel, buffer, bufLen-1, 0); free(buffer); #else [synth startSpeakingString: newText]; #endif } - (void) beQuiet { #ifndef UseCocoaSpeech StopSpeech(channel); #else [synth stopSpeaking]; #endif } - (void) setSkein: (ZoomSkein*) newSkein { [skein release]; skein = [newSkein retain]; } - (BOOL) speakBehind: (int) position { // Iterate up the skein until we get to the move we want ZoomSkeinItem* itemToSpeak = [skein activeItem]; int count = 0; while (itemToSpeak != nil && count < position) { itemToSpeak = [itemToSpeak parent]; count++; } if (itemToSpeak == nil) { // Mention if we've reached the end [self speak: @"There are no previous moves"]; return NO; } else { NSMutableString* toSpeak = [NSMutableString string]; if (position <= 0) { [toSpeak appendFormat: @"Most recent move:\n"]; } else { [toSpeak appendFormat: @"%i moves ago:\n", position+1]; } if (position <= 0 && lastText) { [toSpeak appendString: lastText]; } else { [toSpeak appendFormat: @"%@.\n%@", [itemToSpeak command], [itemToSpeak result]]; } [self speak: toSpeak]; return YES; } } - (void) speakPreviousMove { if ([self speakBehind: movesBehind]) { movesBehind++; } } - (void) speakNextMove { if (movesBehind > 1) { movesBehind--; [self speakBehind: movesBehind-1]; } else { [self speak: @"No further moves"]; } } - (void) resetMoves { movesBehind = 0; } // Using this automation object for input - (void) viewIsWaitingForInput: (GlkView*) view { [self zoomWaitingForInput]; } @end