From 886bfccbabed05c978504b2b193727dba23848bc Mon Sep 17 00:00:00 2001 From: Jens Ayton Date: Sun, 15 Mar 2009 16:14:03 +0000 Subject: [PATCH] Applied dsalt's libespeak patch. It doesn't work for me, but the build is fine if not built with make libespeak=yes so it doesn't hurt anything. git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@2091 127b21dd-08f5-0310-b4b7-95ae10353056 --- GNUmakefile | 5 + Resources/Config/descriptions.plist | 2 + .../Config/speech_pronunciation_guide.plist | 60 +++++--- contributors.txt | 2 +- debian/control | 2 +- debian/rules | 2 +- src/Core/Entities/PlayerEntity.h | 2 + src/Core/Entities/PlayerEntity.m | 6 +- src/Core/Entities/PlayerEntityControls.m | 2 +- src/Core/OOCocoa.h | 10 ++ src/Core/Universe.h | 2 + src/Core/Universe.m | 128 ++++++++++++++++-- 12 files changed, 188 insertions(+), 35 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index a401b9a8..43156630 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -21,6 +21,11 @@ else ADDITIONAL_OBJCFLAGS = -std=c99 -DLOADSAVEGUI -DLINUX -DDOCKING_CLEARANCE_ENABLED -DALLOW_PROCEDURAL_PLANETS -DXP_UNIX -Wno-import `sdl-config --cflags` oolite_LIB_DIRS += -L/usr/X11R6/lib/ endif +ifeq ($(libespeak),yes) + ADDITIONAL_OBJC_LIBS += -lespeak + ADDITIONAL_OBJCFLAGS+=-DHAVE_LIBESPEAK=1 + GNUSTEP_OBJ_DIR_NAME := $(GNUSTEP_OBJ_DIR_NAME).spk +endif ifeq ($(debug),yes) ADDITIONAL_CFLAGS += -g -O0 ADDITIONAL_OBJCFLAGS += -g -O0 diff --git a/Resources/Config/descriptions.plist b/Resources/Config/descriptions.plist index 8ec7b14b..026c8b62 100644 --- a/Resources/Config/descriptions.plist +++ b/Resources/Config/descriptions.plist @@ -367,6 +367,8 @@ digrams-apostrophe = "’"; // this string represents the 4 character phonemes associated with each digram. phonograms = "AEb=UW==sEH=IHt=IHl=EHt=st==AAn=lOW=nUW=T===nOW=AEl=lEY=hEY=JEH=zEY=sEH=bIY=sOW=UHs=EHz=AEr=mAE=IHn=dIY=rEY=EH==UXr=AEt=EHn=bEH=rAX=lAX=vEH=tIY=EHd=AAr=kw==AXn=tEY=IHz=rIY=AAn="; + // this string similarly, but for espeak. (Note also Apple 'AA' is changed, else 'or' would sound like 'ar'.) + espkphonos = "ab=='u:=sE==It==Il==Et==st==0n==l'oUn'u:T===n'oUal==l'eIh'eIdZ'Ez'eIs'E=b'i:s'oU'Us='Ez='ar=m'a='In=d'i:r'eI'E=='Vr='at='En=b'E=ra2=la2=v'E=t'i:'Ed=0r==kw==a2n=t'eI'Iz=r'i:0n=="; "interstellar-space" = "Interstellar space"; "not-applicable" = "N/A"; diff --git a/Resources/Config/speech_pronunciation_guide.plist b/Resources/Config/speech_pronunciation_guide.plist index 310b49e2..8559674c 100644 --- a/Resources/Config/speech_pronunciation_guide.plist +++ b/Resources/Config/speech_pronunciation_guide.plist @@ -1,4 +1,10 @@ ( +// Format: +// ( +// [required] Original text, +// [required] replacement text (Apple), +// [optional] replacement text (espeak) or "_" (original text is unchanged) +// ) ( "\\n", " " @@ -10,7 +16,12 @@ ( // must come before ' Cr' Cruiser, - "[[inpt PHON]]kr1UWz2AXr[[inpt TEXT]]" + "[[inpt PHON]]kr1UWz2AXr[[inpt TEXT]]", + "[[kr'u:z3]]" + ), + ( + " cr.", + " credits." ), ( " Cr", @@ -24,41 +35,50 @@ Purse, ", Total " ), - ( - "Mark III", - "Mach Three" - ), - ( - "Mark II", - "Mach Two" - ), - ( - "Mark I", - "Mach One" - ), + ( "Mark V", "Mark Five" ), + ( "Mark IV", "Mark Four" ), + ( "Mark III", "Mark Three" ), + ( "Mark II", "Mark Two" ), + ( "Mark I", "Mark One" ), + ( "MkV", "Mark Five" ), + ( "MkIV", "Mark Four" ), + ( "MkIII", "Mark Three" ), + ( "MkII", "Mark Two" ), + ( "MkI", "Mark One" ), + ( "Mk V", "Mark Five" ), + ( "Mk IV", "Mark Four" ), + ( "Mk III", "Mark Three" ), + ( "Mk II", "Mark Two" ), + ( "Mk I", "Mark One" ), ( GalCop, - "[[inpt PHON]]g1AElkUXp[[inpt TEXT]]" + "[[inpt PHON]]g1AElkUXp[[inpt TEXT]]", + _ ), ( Witchspace, - "[[inpt PHON]]w1IHCsp2EYs[[inpt TEXT]]" + "[[inpt PHON]]w1IHCsp2EYs[[inpt TEXT]]", + _ ), ( "Fer-de-Lance", - "[[inpt PHON]]f1 Standards-Version: 3.7.2 Build-Depends: debhelper (>= 5), - libsdl1.2-dev, libsdl-mixer1.2-dev, + libsdl1.2-dev, libsdl-mixer1.2-dev, libespeak-dev, libgnustep-base-dev, gnustep-core-devel, libpng-dev, mesa-common-dev diff --git a/debian/rules b/debian/rules index 171ba5e3..0ed4d9bb 100755 --- a/debian/rules +++ b/debian/rules @@ -36,7 +36,7 @@ build-stamp: dh_testdir $(MAKE) -C $(JS_SRC)/fdlibm -f Makefile.ref BUILD_OPT=1 $(MAKE) -C $(JS_SRC) -f Makefile.ref BUILD_OPT=1 - $(GSMAKE) + $(GSMAKE) libespeak=yes touch $@ clean: clean-stamp unpatch diff --git a/src/Core/Entities/PlayerEntity.h b/src/Core/Entities/PlayerEntity.h index 09da1d0c..53baeb1b 100644 --- a/src/Core/Entities/PlayerEntity.h +++ b/src/Core/Entities/PlayerEntity.h @@ -67,6 +67,8 @@ enum GUI_ROW_GAMEOPTIONS_VOLUME, #if OOLITE_MAC_OS_X GUI_ROW_GAMEOPTIONS_GROWL, +#endif +#if OOLITE_SPEECH_SYNTH GUI_ROW_GAMEOPTIONS_SPEECH, #endif GUI_ROW_GAMEOPTIONS_MUSIC, diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m index 7e968bb6..1dacff82 100644 --- a/src/Core/Entities/PlayerEntity.m +++ b/src/Core/Entities/PlayerEntity.m @@ -4549,14 +4549,16 @@ static PlayerEntity *sSharedPlayer = nil; forRow:GUI_ROW_GAMEOPTIONS_GROWL align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW_GAMEOPTIONS_GROWL]; } - +#endif +#if OOLITE_SPEECH_SYNTH // Speech control if (isSpeechOn) [gui setText:DESC(@"gameoptions-spoken-messages-yes") forRow:GUI_ROW_GAMEOPTIONS_SPEECH align:GUI_ALIGN_CENTER]; else [gui setText:DESC(@"gameoptions-spoken-messages-no") forRow:GUI_ROW_GAMEOPTIONS_SPEECH align:GUI_ALIGN_CENTER]; [gui setKey:GUI_KEY_OK forRow:GUI_ROW_GAMEOPTIONS_SPEECH]; -#else +#endif +#if !OOLITE_MAC_OS_X // window/fullscreen if([gameView inFullScreenMode]) { diff --git a/src/Core/Entities/PlayerEntityControls.m b/src/Core/Entities/PlayerEntityControls.m index 0de5e3ad..2c3681f9 100644 --- a/src/Core/Entities/PlayerEntityControls.m +++ b/src/Core/Entities/PlayerEntityControls.m @@ -1946,7 +1946,7 @@ static NSTimeInterval time_last_frame; switching_resolution = NO; } -#if OOLITE_MAC_OS_X +#if OOLITE_SPEECH_SYNTH if ((guiSelectedRow == GUI_ROW_GAMEOPTIONS_SPEECH)&&(([gameView isDown:gvArrowKeyRight])||([gameView isDown:gvArrowKeyLeft]))) { if ([gameView isDown:gvArrowKeyRight] != isSpeechOn) diff --git a/src/Core/OOCocoa.h b/src/Core/OOCocoa.h index 6ac6b2cb..fb7a8225 100644 --- a/src/Core/OOCocoa.h +++ b/src/Core/OOCocoa.h @@ -330,3 +330,13 @@ enum { #else #define OOLITE_FAST_ENUMERATION 0 #endif + + +/* Speech synthesis +*/ +#if MAC_OS_X || defined(HAVE_LIBESPEAK) +#define OOLITE_SPEECH_SYNTH 1 +#else +#define OOLITE_SPEECH_SYNTH 0 +#endif + diff --git a/src/Core/Universe.h b/src/Core/Universe.h index 408b98cd..5c601a3c 100644 --- a/src/Core/Universe.h +++ b/src/Core/Universe.h @@ -246,6 +246,8 @@ enum #if OOLITE_MAC_OS_X NSSpeechSynthesizer *speechSynthesizer; // use this from OS X 10.3 onwards +#endif +#if OOLITE_SPEECH_SYNTH NSArray *speechArray; #endif } diff --git a/src/Core/Universe.m b/src/Core/Universe.m index bb62b125..3bb8d3ca 100644 --- a/src/Core/Universe.m +++ b/src/Core/Universe.m @@ -69,6 +69,9 @@ MA 02110-1301, USA. #import "OOConvertSystemDescriptions.h" #endif +#ifdef HAVE_LIBESPEAK +#include +#endif #define kOOLogUnconvertedNSLog @"unclassified.Universe" @@ -159,13 +162,15 @@ static OOComparisonResult comparePrice(id dict1, id dict2, void * context); // Set up the internal game strings descriptions = [[ResourceManager dictionaryFromFilesNamed:@"descriptions.plist" inFolder:@"Config" andMerge:YES] retain]; +#if OOLITE_SPEECH_SYNTH #if OOLITE_MAC_OS_X //// speech stuff speechSynthesizer = [[NSSpeechSynthesizer alloc] init]; - - //Jester Speech Begin +#elif defined(HAVE_LIBESPEAK) + espeak_Initialize(AUDIO_OUTPUT_PLAYBACK, 100, NULL, 0); + espeak_SetParameter(espeakPUNCTUATION, espeakPUNCT_NONE, 0); +#endif speechArray = [[ResourceManager arrayFromFilesNamed:@"speech_pronunciation_guide.plist" inFolder:@"Config" andMerge:YES] retain]; - //Jester Speech End #endif [[GameController sharedController] logProgress:DESC(@"loading-ships")]; @@ -327,9 +332,13 @@ static OOComparisonResult comparePrice(id dict1, id dict2, void * context); [[OOCacheManager sharedCache] flush]; -#ifndef OOLITE_MAC_OS_X +#if OOLITE_SPEECH_SYNTH [speechArray release]; +#if OOLITE_MAC_OS_X [speechSynthesizer release]; +#elif defined(HAVE_LIBESPEAK) + espeak_Cancel(); +#endif #endif [super dealloc]; @@ -5005,7 +5014,7 @@ OOINLINE BOOL EntityInRange(Vector p1, Entity *e2, float range) { if (![currentMessage isEqual:text]) { -#if OOLITE_MAC_OS_X +#if OOLITE_SPEECH_SYNTH PlayerEntity* player = [PlayerEntity sharedPlayer]; //speech synthesis if ([player isSpeechOn]) @@ -5023,14 +5032,17 @@ OOINLINE BOOL EntityInRange(Vector p1, Entity *e2, float range) for (speechEnumerator = [speechArray objectEnumerator]; (thePair = [speechEnumerator nextObject]); ) { NSString *original_phrase = [thePair stringAtIndex:0]; +#if OOLITE_MAC_OS_X NSString *replacement_phrase = [thePair stringAtIndex:1]; - - spoken_text = [[spoken_text componentsSeparatedByString: original_phrase] componentsJoinedByString: replacement_phrase]; +#else + NSString *replacement_phrase = [thePair stringAtIndex:([thePair count] > 2 ? 2 : 1)]; + if (![replacement_phrase isEqualToString:@"_"]) +#endif + spoken_text = [[spoken_text componentsSeparatedByString: original_phrase] componentsJoinedByString: replacement_phrase]; } spoken_text = [[spoken_text componentsSeparatedByString: systemName] componentsJoinedByString: systemSaid]; spoken_text = [[spoken_text componentsSeparatedByString: h_systemName] componentsJoinedByString: h_systemSaid]; } - if ([self isSpeaking]) [self stopSpeaking]; [self startSpeakingString:spoken_text]; @@ -5912,8 +5924,11 @@ OOINLINE BOOL EntityInRange(Vector p1, Entity *e2, float range) - (NSString *) generatePhoneticSystemName:(Random_Seed) s_seed { int i; - +#if OOLITE_MAC_OS_X NSString *phonograms = [self descriptionForKey:@"phonograms"]; +#else + NSString *phonograms = [self descriptionForKey:@"espkphonos"]; +#endif NSMutableString *name = [NSMutableString string]; int size = 4; @@ -5933,7 +5948,11 @@ OOINLINE BOOL EntityInRange(Vector p1, Entity *e2, float range) rotate_seed(&s_seed); } +#if OOLITE_MAC_OS_X return [NSString stringWithFormat:@"[[inpt PHON]]%@[[inpt TEXT]]", name]; +#else + return [NSString stringWithFormat:@"[[%@]]", name]; +#endif } @@ -7810,6 +7829,97 @@ static OOComparisonResult comparePrice(id dict1, id dict2, void * context) return [speechSynthesizer isSpeaking]; } +#elif defined(HAVE_LIBESPEAK) + +- (void) startSpeakingString:(NSString *) text +{ +#if 0 + // First, do some translation of phoneme representation from Apple to espeak. + // We recognise phonemes listed at + // http://developer.apple.com/documentation/userexperience/conceptual/speechsynthesisprogrammingguide/Phonemes/chapter_952_section_1.html + // and controls "[[inpt PHON]]" and "[[inpt TEXT]]". + NSMutableArray *frags = [NSMutableArray arrayWithCapacity:20]; + [frags setArray:[text componentsSeparatedByString:@"[[inpt PHON]]"]]; + int frag; + for (frag = 1; frag < [frags count]; ++frag) + { + NSArray *parts = [[frags objectAtIndex:frag] componentsSeparatedByString:@"[[inpt TEXT]]"]; + const char *oldp = [[parts stringAtIndex:0] cString] - 1; + char *newp = malloc (strlen (oldp) * 2); + char *ptr = newp; + while (*++oldp) + { + switch (*oldp) + { + case '%': case '@': *ptr++ = ' '; break; + case 'A': + switch (*++oldp) + { + case 'A': *ptr++ = '\''; *ptr++ = 'A'; *ptr++ = ':'; break; // AA → 'A: + case 'E': *ptr++ = '\''; *ptr++ = 'a'; break; // AE → 'a + case 'O': *ptr++ = '\''; *ptr++ = 'O'; *ptr++ = ':'; break; // AO → 'O: + case 'W': *ptr++ = '\''; *ptr++ = 'a'; *ptr++ = 'U'; break; // AW → 'aU + case 'X': *ptr++ = 'a'; *ptr++ = '2'; break; // AX → a2 + case 'Y': *ptr++ = '\''; *ptr++ = 'a'; *ptr++ = 'I'; break; // AY → 'aI + default: --oldp; *ptr++ = 'A'; break; + } + case 'C': *ptr++ = 't'; *ptr++ = 'S'; break; // C → tS + case 'E': + switch (*++oldp) + { + case 'H': *ptr++ = '\''; *ptr++ = 'E'; break; // EH → 'E + case 'Y': *ptr++ = '\''; *ptr++ = 'e'; *ptr++ = 'I'; break; // EY → 'eI + default: --oldp; *ptr++ = 'E'; break; + } + case 'I': + switch (*++oldp) + { + case 'H': *ptr++ = '\''; *ptr++ = 'I'; break; // IH → 'I + case 'X': *ptr++ = 'I'; *ptr++ = '2'; break; // IX → I2 + case 'Y': *ptr++ = '\''; *ptr++ = 'i'; *ptr++ = ':'; break; // IY → 'i: + default: --oldp; *ptr++ = 'I'; break; + } + case 'J': *ptr++ = 'd'; *ptr++ = 'Z'; break; // J → dZ + case 'O': + switch (*++oldp) + { + case 'W': *ptr++ = '\''; *ptr++ = 'o'; *ptr++ = 'U'; break; // OW → 'oU + case 'Y': *ptr++ = '\''; *ptr++ = 'O'; *ptr++ = 'I'; break; // OY → 'OI + default: --oldp; *ptr++ = 'O'; break; + } + case 'U': + switch (*++oldp) + { + case 'H': *ptr++ = '\''; *ptr++ = 'U'; break; // UH → 'U + case 'W': *ptr++ = '\''; *ptr++ = 'u'; *ptr++ = ':'; break; // UW → 'u: + case 'X': *ptr++ = '\''; *ptr++ = 'V'; break; // UX → 'V + default: --oldp; *ptr++ = 'U'; break; + } + case 'y': *ptr++ = 'j'; break; // y → j + default: *ptr++ = *oldp; break; + } + } + *ptr = 0; + [frags replaceObjectAtIndex:frag withObject:[NSString stringWithFormat:@"[[%s]]%@", newp, ([frags count] == 2 ? [parts objectAtIndex:1] : (id)@"")]]; + free (newp); + } + text = [frags componentsJoinedByString:@""]; +#endif + size_t length = [text length]; + const char *ctext = [text cString]; + espeak_Synth(ctext, length + 1 /* inc. NUL */, 0, POS_CHARACTER, length, espeakCHARS_UTF8 | espeakPHONEMES | espeakENDPAUSE, NULL, NULL); +} + +- (void) stopSpeaking +{ + espeak_Cancel(); +} + +- (BOOL) isSpeaking +{ + return espeak_IsPlaying(); +} + #else - (void) startSpeakingString:(NSString *) text {}