View Issue Details

IDProjectCategoryView StatusLast Update
0002638FSSCPspeechpublic2015-04-23 17:39
Reportergereedy Assigned ToEchelon9  
PrioritynormalSeverityfeatureReproducibilityalways
Status code reviewResolutionopen 
Summary0002638: Support speech on OSX
DescriptionThis patch implements text-to-speech for mac os x.
TagsNo tags attached.

Activities

gereedy

2012-04-13 04:20

reporter  

macspeech.patch (5,716 bytes)   
Index: projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
===================================================================
--- projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(revision 8666)
+++ projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(working copy)
@@ -4169,6 +4169,7 @@
 					USE_OPENAL,
 					NO_DIRECT3D,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = ../../libjpeg;
 				LIBRARY_STYLE = STATIC;
@@ -4213,6 +4214,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
@@ -4324,6 +4326,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GENERATE_PROFILING_CODE = NO;
@@ -4360,6 +4363,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
@@ -4535,6 +4539,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GENERATE_PROFILING_CODE = NO;
@@ -4590,6 +4595,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
@@ -4626,6 +4632,7 @@
 					USE_OPENAL,
 					NO_DIRECT3D,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = ../../libjpeg;
 				LIBRARY_STYLE = STATIC;
@@ -4741,6 +4748,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
@@ -4795,6 +4803,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
Index: configure.ac
===================================================================
--- configure.ac	(revision 8666)
+++ configure.ac	(working copy)
@@ -436,7 +436,7 @@
 
 dnl extra OSX frameworks
 if test "$fs2_os_osx" = "yes" ; then
-	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation"
+	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation -framework ApplicationServices"
 fi
 
 
Index: code/sound/speech.cpp
===================================================================
--- code/sound/speech.cpp	(revision 8666)
+++ code/sound/speech.cpp	(working copy)
@@ -29,6 +29,9 @@
 	#include <sphelper.h>
 
 	ISpVoice *Voice_device;
+#elif defined(__APPLE__)
+#include <ApplicationServices/ApplicationServices.h>
+        SpeechChannel speech_channel;
 #elif defined(SCP_UNIX)
 	#include <fcntl.h>
 //	#include <stdio.h>
@@ -56,6 +59,13 @@
 		(void **)&Voice_device);
 
 	Speech_init = SUCCEEDED(hr);
+#elif defined(__APPLE__)
+        OSErr err;
+        err = NewSpeechChannel(NULL, &speech_channel);
+        if (err) {
+          return false;
+        }
+        Speech_init = true;
 #else
 
 	speech_dev = open("/dev/speech", O_WRONLY | O_DIRECT);
@@ -79,6 +89,8 @@
 
 #ifdef _WIN32
 	Voice_device->Release();
+#elif defined(__APPLE__)
+        DisposeSpeechChannel(speech_channel);
 #else
 	close(speech_dev);
 //	fclose(speech_dev);
@@ -113,6 +125,34 @@
 
 	speech_stop();
 	return SUCCEEDED(Voice_device->Speak(Conversion_buffer, SPF_ASYNC, NULL));
+#elif defined(__APPLE__)
+	int len = strlen(text);
+	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
+
+	if(len > (MAX_SPEECH_CHAR_LEN - 1)) {
+		len = MAX_SPEECH_CHAR_LEN - 1;
+	}
+
+	int count = 0;
+	for(int i = 0; i < len; i++) {
+		if(text[i] == '$') {
+			i++;
+			continue;
+		}
+        if(text[i] == '\n') {
+            Conversion_buffer[count] = '\n';
+            count++;
+        }
+
+		Conversion_buffer[count] = text[i];
+		count++;
+	}
+
+	Conversion_buffer[count] = '\0';
+
+        CFStringRef speech_string = CFStringCreateWithCString(NULL, Conversion_buffer, kCFStringEncodingASCII);
+        OSErr err = SpeakCFString(speech_channel, speech_string, NULL);
+        return !err;
 #else
 	int len = strlen(text);
 	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
@@ -150,6 +190,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Pause());
+#elif defined(__APPLE__)
+        return !PauseSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -162,6 +204,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Resume());
+#elif defined(__APPLE__)
+        return !ContinueSpeech(speech_channel);
 #else
 	STUB_FUNCTION;
 
@@ -174,6 +218,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL ));
+#elif defined(__APPLE__)
+    return !StopSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -185,6 +231,9 @@
 {
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->SetVolume(volume));
+#elif defined(__APPLE__)
+    STUB_FUNCTION;
+    return true;
 #else
 	STUB_FUNCTION;
 
@@ -228,6 +277,9 @@
 		count++;
 	}
 	return false;
+#elif defined(__APPLE__)
+        STUB_FUNCTION;
+        return true;
 #else
 	STUB_FUNCTION;
 
@@ -246,6 +298,11 @@
 	if (FAILED(hr)) return false;
 
 	return (pStatus.dwRunningState == SPRS_IS_SPEAKING);
+#elif defined(__APPLE__)
+        SpeechStatusInfo status;
+        OSErr err = GetSpeechInfo(speech_channel, soStatus, &status);
+        if (err) return false;
+        return status.outputBusy;
 #else
 	STUB_FUNCTION;
 
macspeech.patch (5,716 bytes)   

gereedy

2012-04-13 19:54

reporter   ~0013464

Oops, I forgot to release the strings. Here's an updated patch.

gereedy

2012-04-13 19:54

reporter  

macspeech2.patch (5,747 bytes)   
Index: configure.ac
===================================================================
--- configure.ac	(revision 8666)
+++ configure.ac	(working copy)
@@ -436,7 +436,7 @@
 
 dnl extra OSX frameworks
 if test "$fs2_os_osx" = "yes" ; then
-	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation"
+	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation -framework ApplicationServices"
 fi
 
 
Index: projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
===================================================================
--- projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(revision 8666)
+++ projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(working copy)
@@ -4169,6 +4169,7 @@
 					USE_OPENAL,
 					NO_DIRECT3D,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = ../../libjpeg;
 				LIBRARY_STYLE = STATIC;
@@ -4213,6 +4214,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
@@ -4324,6 +4326,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GENERATE_PROFILING_CODE = NO;
@@ -4360,6 +4363,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
@@ -4535,6 +4539,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GENERATE_PROFILING_CODE = NO;
@@ -4590,6 +4595,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
@@ -4626,6 +4632,7 @@
 					USE_OPENAL,
 					NO_DIRECT3D,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = ../../libjpeg;
 				LIBRARY_STYLE = STATIC;
@@ -4741,6 +4748,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
@@ -4795,6 +4803,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
Index: code/sound/speech.cpp
===================================================================
--- code/sound/speech.cpp	(revision 8666)
+++ code/sound/speech.cpp	(working copy)
@@ -29,6 +29,9 @@
 	#include <sphelper.h>
 
 	ISpVoice *Voice_device;
+#elif defined(__APPLE__)
+#include <ApplicationServices/ApplicationServices.h>
+        SpeechChannel speech_channel;
 #elif defined(SCP_UNIX)
 	#include <fcntl.h>
 //	#include <stdio.h>
@@ -56,6 +59,13 @@
 		(void **)&Voice_device);
 
 	Speech_init = SUCCEEDED(hr);
+#elif defined(__APPLE__)
+        OSErr err;
+        err = NewSpeechChannel(NULL, &speech_channel);
+        if (err) {
+          return false;
+        }
+        Speech_init = true;
 #else
 
 	speech_dev = open("/dev/speech", O_WRONLY | O_DIRECT);
@@ -79,6 +89,8 @@
 
 #ifdef _WIN32
 	Voice_device->Release();
+#elif defined(__APPLE__)
+        DisposeSpeechChannel(speech_channel);
 #else
 	close(speech_dev);
 //	fclose(speech_dev);
@@ -113,6 +125,35 @@
 
 	speech_stop();
 	return SUCCEEDED(Voice_device->Speak(Conversion_buffer, SPF_ASYNC, NULL));
+#elif defined(__APPLE__)
+	int len = strlen(text);
+	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
+
+	if(len > (MAX_SPEECH_CHAR_LEN - 1)) {
+		len = MAX_SPEECH_CHAR_LEN - 1;
+	}
+
+	int count = 0;
+	for(int i = 0; i < len; i++) {
+		if(text[i] == '$') {
+			i++;
+			continue;
+		}
+        if(text[i] == '\n') {
+            Conversion_buffer[count] = '\n';
+            count++;
+        }
+
+		Conversion_buffer[count] = text[i];
+		count++;
+	}
+
+	Conversion_buffer[count] = '\0';
+
+        CFStringRef speech_string = CFStringCreateWithCString(NULL, Conversion_buffer, kCFStringEncodingASCII);
+        OSErr err = SpeakCFString(speech_channel, speech_string, NULL);
+    CFRelease(speech_string);
+        return !err;
 #else
 	int len = strlen(text);
 	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
@@ -150,6 +191,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Pause());
+#elif defined(__APPLE__)
+        return !PauseSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -162,6 +205,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Resume());
+#elif defined(__APPLE__)
+        return !ContinueSpeech(speech_channel);
 #else
 	STUB_FUNCTION;
 
@@ -174,6 +219,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL ));
+#elif defined(__APPLE__)
+    return !StopSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -185,6 +232,9 @@
 {
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->SetVolume(volume));
+#elif defined(__APPLE__)
+    STUB_FUNCTION;
+    return true;
 #else
 	STUB_FUNCTION;
 
@@ -228,6 +278,9 @@
 		count++;
 	}
 	return false;
+#elif defined(__APPLE__)
+        STUB_FUNCTION;
+        return true;
 #else
 	STUB_FUNCTION;
 
@@ -246,6 +299,11 @@
 	if (FAILED(hr)) return false;
 
 	return (pStatus.dwRunningState == SPRS_IS_SPEAKING);
+#elif defined(__APPLE__)
+        SpeechStatusInfo status;
+        OSErr err = GetSpeechInfo(speech_channel, soStatus, &status);
+        if (err) return false;
+        return status.outputBusy;
 #else
 	STUB_FUNCTION;
 
macspeech2.patch (5,747 bytes)   

Echelon9

2012-04-23 14:08

developer   ~0013479

I've got this cleanly compiled into a build, but at a bit of a loss to actually get it working in game.

Is there a system-wide setting (i.e. in Preferences) that I need to set before this will work. Using OS X 10.7 here.

gereedy

2012-04-30 04:26

reporter   ~0013490

Oh yeah, I had to edit the fs2_open.ini file in ~/Library/FS2_Open and add the following lines:

SpeechTechroom=1
SpeechBriefings=1
SpeechIngame=1
SpeechMulti=1

I've not seen any other way (launcher or in-game) to set these.

Echelon9

2012-04-30 15:30

developer   ~0013492

Hrmm, I've tried setting those as well as setting them to 0, as per the code.

Across Debug and Retail, no luck getting this working...

gereedy

2012-05-04 03:00

reporter   ~0013515

I think the 0 in the code is the default value if none is set, they need to be 1 to activate speech.

How are you building? The only thing I can think of is that you're building without FS2_SPEECH defined.

FYI, I'm on Lion as well and used the Xcode 4 project. The patch include the changes to the project file to turn that define on.

chief1983

2012-05-09 19:07

administrator   ~0013530

Huh, and here I thought we'd need to be using Festival across the board.

Echelon9

2012-08-14 13:51

developer   ~0013905

Okay, by using this patch and forcing speech at startup with the -query_speech command line option, I hear part of the initial testing "Welcome to FS..." before Freespace crashes.

Looks like there's some memory corruption going on.

// fs2_open.log ------------------------------------------------------------

Initializing OpenAL...
  OpenAL Vendor : Apple Computer Inc.
  OpenAL Renderer : Software
  OpenAL Version : 1.1

  Found extension "AL_EXT_float32".

  Sample rate: 0 (44100)
  EFX enabled: NO
  Playback device: Built-in Output
  Capture device: Built-in Microphone
... OpenAL successfully initialized!
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]

// Crash reporter ------------------------------------------------------------
Thread 3 Crashed:
0 FS2_Open-Inferno (debug) 0x0057bb00 _vm_free(void*, char*, int) + 128 (stubs.cpp:687)
1 FS2_Open-Inferno (debug) 0x000035cb operator delete(void*) + 43 (fsmemory.cpp:19)
2 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3e4f0 MTBEDelayedNotifier::ForwardUnit() + 102
3 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3bcbe MTCBSegmentProducer::NextSegment(MTMBSegment*) + 582
4 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3b98e MTMBSmoothSegment::NextSegment(MTMBSegment*) + 90
5 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b6b488 non-virtual thunk to MTMBSmoothSegment::NextSegment(MTMBSegment*) + 27
6 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3b873 MTMBChangePitch::NextSegment(MTMBSegment*) + 145
7 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b6b2b5 non-virtual thunk to MTMBChangePitch::NextSegment(MTMBSegment*) + 27
8 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3b27a MTMBBlend::NextSegment(MTMBSegment*) + 160
9 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b6b368 non-virtual thunk to MTMBBlend::NextSegment(MTMBSegment*) + 27
10 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3b079 MTMBChangeAmplitude::NextSegment(MTMBSegment*) + 35
11 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b6b403 non-virtual thunk to MTMBChangeAmplitude::NextSegment(MTMBSegment*) + 27
12 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3ac71 MTMBSpeechRateModifier::NextSegment(MTMBSegment*) + 211
13 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b6b5fe non-virtual thunk to MTMBSpeechRateModifier::NextSegment(MTMBSegment*) + 27
14 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b3aa15 MTBEPhraseProcessor::GenerateSamples(MTBESoundOutput*, int*) + 277
15 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b2b52f MT3BEngineTask::Execute(void*) + 337
16 com.apple.speech.synthesis.MacinTalkSynthesizer 0x07b06cce MTBEWorker::ExecuteTasks() + 384

Echelon9

2012-08-14 14:16

developer  

macspeech3.patch (5,999 bytes)   
Index: projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
===================================================================
--- projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(revision 9107)
+++ projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(working copy)
@@ -4162,6 +4162,7 @@
 					USE_OPENAL,
 					NO_DIRECT3D,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = ../../libjpeg;
 				LIBRARY_STYLE = STATIC;
@@ -4204,6 +4205,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
@@ -4312,6 +4314,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GENERATE_PROFILING_CODE = NO;
@@ -4345,6 +4348,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
@@ -4519,6 +4523,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GENERATE_PROFILING_CODE = NO;
@@ -4571,6 +4576,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
@@ -4606,6 +4612,7 @@
 					USE_OPENAL,
 					NO_DIRECT3D,
 					APPLE_APP,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = ../../libjpeg;
 				LIBRARY_STYLE = STATIC;
@@ -4718,6 +4725,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
@@ -4771,6 +4779,7 @@
 					USE_OPENAL,
 					APPLE_APP,
 					INF_BUILD,
+                                        FS2_SPEECH,
 				);
 				HEADER_SEARCH_PATHS = (
 					../../code,
Index: configure.ac
===================================================================
--- configure.ac	(revision 9107)
+++ configure.ac	(working copy)
@@ -436,7 +436,7 @@
 
 dnl extra OSX frameworks
 if test "$fs2_os_osx" = "yes" ; then
-	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation"
+	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation -framework ApplicationServices"
 fi
 
 
Index: code/sound/speech.cpp
===================================================================
--- code/sound/speech.cpp	(revision 9107)
+++ code/sound/speech.cpp	(working copy)
@@ -29,6 +29,9 @@
 	#include <sphelper.h>
 
 	ISpVoice *Voice_device;
+#elif defined(__APPLE__)
+#include <ApplicationServices/ApplicationServices.h>
+        SpeechChannel speech_channel;
 #elif defined(SCP_UNIX)
 	#include <fcntl.h>
 //	#include <stdio.h>
@@ -56,13 +59,18 @@
 		(void **)&Voice_device);
 
 	Speech_init = SUCCEEDED(hr);
+#elif defined(__APPLE__)
+    OSErr err;
+    err = NewSpeechChannel(NULL, &speech_channel);
+    if (err) {
+        return false;
+    }
+    Speech_init = true;
 #else
 
 	speech_dev = open("/dev/speech", O_WRONLY | O_DIRECT);
-//	speech_dev = fopen("/dev/speech", "w");
 
 	if (speech_dev == -1) {
-//	if (speech_dev == NULL) {
 		mprintf(("Couldn't open '/dev/speech', turning text-to-speech off...\n"));
 		return false;
 	}
@@ -79,9 +87,10 @@
 
 #ifdef _WIN32
 	Voice_device->Release();
+#elif defined(__APPLE__)
+    DisposeSpeechChannel(speech_channel);
 #else
 	close(speech_dev);
-//	fclose(speech_dev);
 #endif
 }
 
@@ -113,6 +122,35 @@
 
 	speech_stop();
 	return SUCCEEDED(Voice_device->Speak(Conversion_buffer, SPF_ASYNC, NULL));
+#elif defined(__APPLE__)
+	int len = strlen(text);
+	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
+
+	if(len > (MAX_SPEECH_CHAR_LEN - 1)) {
+		len = MAX_SPEECH_CHAR_LEN - 1;
+	}
+
+	int count = 0;
+	for(int i = 0; i < len; i++) {
+		if(text[i] == '$') {
+			i++;
+			continue;
+		}
+        if(text[i] == '\n') {
+            Conversion_buffer[count] = '\n';
+            count++;
+        }
+
+		Conversion_buffer[count] = text[i];
+		count++;
+	}
+
+	Conversion_buffer[count] = '\0';
+
+    CFStringRef speech_string = CFStringCreateWithCString(NULL, Conversion_buffer, kCFStringEncodingASCII);
+    OSErr err = SpeakCFString(speech_channel, speech_string, NULL);
+    CFRelease(speech_string);
+    return !err;
 #else
 	int len = strlen(text);
 	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
@@ -150,6 +188,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Pause());
+#elif defined(__APPLE__)
+    return !PauseSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -162,6 +202,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Resume());
+#elif defined(__APPLE__)
+    return !ContinueSpeech(speech_channel);
 #else
 	STUB_FUNCTION;
 
@@ -174,6 +216,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL ));
+#elif defined(__APPLE__)
+    return !StopSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -185,6 +229,10 @@
 {
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->SetVolume(volume));
+#elif defined(__APPLE__)    
+    OSErr err = SetSpeechInfo(speech_channel, soVolume, &volume);
+    if (err) return false;
+    return true;
 #else
 	STUB_FUNCTION;
 
@@ -228,9 +276,12 @@
 		count++;
 	}
 	return false;
+#elif defined(__APPLE__)
+    STUB_FUNCTION;
+    return true;
 #else
 	STUB_FUNCTION;
-
+    
 	return true;
 #endif
 }
@@ -246,6 +297,11 @@
 	if (FAILED(hr)) return false;
 
 	return (pStatus.dwRunningState != SPRS_DONE);
+#elif defined(__APPLE__)
+    SpeechStatusInfo status;
+    OSErr err = GetSpeechInfo(speech_channel, soStatus, &status);
+    if (err) return false;
+    return status.outputBusy;
 #else
 	STUB_FUNCTION;
 
macspeech3.patch (5,999 bytes)   

Echelon9

2012-08-14 14:16

developer   ~0013906

A more recent patch version is attached (macspeech3.patch)

chief1983

2014-06-30 22:18

administrator   ~0015977

Checked back up on this patch. Only takes two line additions of FS2_SPEECH to the Xcode project now I think, both added below the only two occurences of APPLE_APP. Ran it, no speech. fs2_open.ini in ~/Library/FS2_Open looks like:

[Default]
VideocardFs2open=OGL -(1024x768)x32 bit
TextureFilter=1
OGL_AnisotropicFilter=0
OGL_AntiAliasSamples=0
SoundDeviceOAL=Built-in Output
CurrentJoystick=99999
EnableJoystickFF=0
EnableHitEffect=0
NetworkConnection=LAN
ConnectionSpeed=Fast
LastPlayer=chief_work
SpeechTechroom=1
SpeechBriefings=1
SpeechIngame=1
SpeechMulti=1
SpeechGame=1

[Sound]
PlaybackDevice=Built-in Output
CaptureDevice=Built-in Microphone

[PXO]
FS2OpenPXO=1


I added SpeechGame as I saw it in my Windows registry list as well. Debug build log looks like:

Initializing OpenAL...
  OpenAL Vendor : Apple Computer Inc.
  OpenAL Renderer : Software
  OpenAL Version : 1.1

  Found extension "AL_EXT_float32".

  Sample rate: 0 (44100)
  EFX enabled: NO
  Playback device: Built-in Output
  Capture device: Built-in Microphone
... OpenAL successfully initialized!
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
STUB: speech_set_voice in /Users/cliff.gordon/fs2open/code/sound/speech.cpp at line 280, thread 63763

-query_speech doesn't seem to change anything for me.

chief1983

2014-06-30 22:20

administrator  

macspeech4.patch (4,887 bytes)   
Index: code/sound/speech.cpp
===================================================================
--- code/sound/speech.cpp	(revision 10859)
+++ code/sound/speech.cpp	(working copy)
@@ -29,6 +29,9 @@
 	#include <sphelper.h>
 
 	ISpVoice *Voice_device;
+#elif defined(__APPLE__)
+#include <ApplicationServices/ApplicationServices.h>
+        SpeechChannel speech_channel;
 #elif defined(SCP_UNIX)
 	#include <fcntl.h>
 //	#include <stdio.h>
@@ -56,13 +59,18 @@
 		(void **)&Voice_device);
 
 	Speech_init = SUCCEEDED(hr);
+#elif defined(__APPLE__)
+    OSErr err;
+    err = NewSpeechChannel(NULL, &speech_channel);
+    if (err) {
+        return false;
+    }
+    Speech_init = true;
 #else
 
 	speech_dev = open("/dev/speech", O_WRONLY | O_DIRECT);
-//	speech_dev = fopen("/dev/speech", "w");
 
 	if (speech_dev == -1) {
-//	if (speech_dev == NULL) {
 		mprintf(("Couldn't open '/dev/speech', turning text-to-speech off...\n"));
 		return false;
 	}
@@ -79,9 +87,10 @@
 
 #ifdef _WIN32
 	Voice_device->Release();
+#elif defined(__APPLE__)
+    DisposeSpeechChannel(speech_channel);
 #else
 	close(speech_dev);
-//	fclose(speech_dev);
 #endif
 }
 
@@ -113,6 +122,35 @@
 
 	speech_stop();
 	return SUCCEEDED(Voice_device->Speak(Conversion_buffer, SPF_ASYNC, NULL));
+#elif defined(__APPLE__)
+	int len = strlen(text);
+	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
+
+	if(len > (MAX_SPEECH_CHAR_LEN - 1)) {
+		len = MAX_SPEECH_CHAR_LEN - 1;
+	}
+
+	int count = 0;
+	for(int i = 0; i < len; i++) {
+		if(text[i] == '$') {
+			i++;
+			continue;
+		}
+        if(text[i] == '\n') {
+            Conversion_buffer[count] = '\n';
+            count++;
+        }
+
+		Conversion_buffer[count] = text[i];
+		count++;
+	}
+
+	Conversion_buffer[count] = '\0';
+
+    CFStringRef speech_string = CFStringCreateWithCString(NULL, Conversion_buffer, kCFStringEncodingASCII);
+    OSErr err = SpeakCFString(speech_channel, speech_string, NULL);
+    CFRelease(speech_string);
+    return !err;
 #else
 	int len = strlen(text);
 	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
@@ -150,6 +188,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Pause());
+#elif defined(__APPLE__)
+    return !PauseSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -162,6 +202,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Resume());
+#elif defined(__APPLE__)
+    return !ContinueSpeech(speech_channel);
 #else
 	STUB_FUNCTION;
 
@@ -174,6 +216,8 @@
 	if(Speech_init == false) return true;
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL ));
+#elif defined(__APPLE__)
+    return !StopSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -185,6 +229,10 @@
 {
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->SetVolume(volume));
+#elif defined(__APPLE__)    
+    OSErr err = SetSpeechInfo(speech_channel, soVolume, &volume);
+    if (err) return false;
+    return true;
 #else
 	STUB_FUNCTION;
 
@@ -228,9 +276,12 @@
 		count++;
 	}
 	return false;
+#elif defined(__APPLE__)
+    STUB_FUNCTION;
+    return true;
 #else
 	STUB_FUNCTION;
-
+    
 	return true;
 #endif
 }
@@ -246,6 +297,11 @@
 	if (FAILED(hr)) return false;
 
 	return (pStatus.dwRunningState != SPRS_DONE);
+#elif defined(__APPLE__)
+    SpeechStatusInfo status;
+    OSErr err = GetSpeechInfo(speech_channel, soStatus, &status);
+    if (err) return false;
+    return status.outputBusy;
 #else
 	STUB_FUNCTION;
 
Index: configure.ac
===================================================================
--- configure.ac	(revision 10859)
+++ configure.ac	(working copy)
@@ -395,6 +395,7 @@
 	FS2_CXXFLAGS="$FS2_CXXFLAGS -I/System/Library/Frameworks/OpenGL.framework/Headers"
 	FS2_LDFLAGS="$FS2_LDFLAGS -framework OpenGL"
 	AC_DEFINE([SCP_UNIX])
+	AC_DEFINE([FS2_SPEECH])
 	AC_DEFINE([NO_DIRECT3D])
 	## don't need the CFLAGS here if recent SDL is used
 elif test "$fs2_os_solaris" = "yes" ; then
@@ -473,7 +474,7 @@
 
 dnl extra OSX frameworks
 if test "$fs2_os_osx" = "yes" ; then
-	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation"
+	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation -framework ApplicationServices"
 fi
 
 
Index: projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
===================================================================
--- projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(revision 10859)
+++ projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj	(working copy)
@@ -4439,6 +4439,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+					FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -4486,6 +4487,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+					FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
macspeech4.patch (4,887 bytes)   

chief1983

2014-06-30 22:21

administrator   ~0015978

Last edited: 2014-06-30 22:22

I've uploaded a new patch version with tweaked project files. I also added the FS2_SPEECH define to configure.ac, however that file doesn't affect Xcode to my knowledge. That also made me wonder how ApplicationServices is being loaded into the Xcode builds then, or if it was needed at all. This doesn't fix it, it's just a more up to date starting point for anyone looking at it.

Echelon9

2014-07-01 00:26

developer   ~0015981

Thanks, I'll take another look at the memory corruption here. Looks like another double free.

Echelon9

2014-07-01 12:08

developer   ~0016000

You shouldn't need to set SpeechGame=1, see further the table of speech types in the array FSSpeech_play_id[] in fsspeech.cpp

chief1983

2014-07-01 13:57

administrator   ~0016002

Well, I wasn't sure, but removing it doesn't change anything. I don't get any crashes, I just enter the tech room, and I don't hear any TTS.

Echelon9

2014-07-01 14:11

developer   ~0016003

Same here. I spent some time testing and debugging this evening.

Whilst no crashes, also no spoken words on 10.9. I can confirm the text is being passed to the SpeakCFString() API call, just nothing happens after that.

chief1983

2014-07-01 14:36

administrator   ~0016004

Last edited: 2014-07-01 14:36

Like I said, I wonder if that's because the ApplicationServices framework isn't actually being added as an App resource in the Xcode4 project. It was added to configure.ac for some reason but that has nothing to do with Xcode builds. But I would probably expect more warnings/errors if that were actually the problem.

Echelon9

2014-07-01 15:09

developer   ~0016005

I did a quick test with otool -L on the resultant binary, and it looks like ApplicationServices.framework was linked in.

chief1983

2015-04-23 15:16

administrator   ~0016654

So any other thoughts on this? If the framework is linked in, why is there no speech? Something needs to be configured in System Preferences or something?

chief1983

2015-04-23 17:06

administrator   ~0016655

Last edited: 2015-04-23 17:09

I applied the macspeech4.patch to current trunk and tried again, after adjusting some settings in the VoiceOver configuration. Also tried the -query_speech flag. No voice in tech room, nothing from query_speech either.

Edit: Attached an updated version of the patch with the chunks in the right places and some whitespace goofs fixed.

chief1983

2015-04-23 17:09

administrator  

macspeech5.patch (5,020 bytes)   
diff --git code/sound/speech.cpp code/sound/speech.cpp
index 14327a9..a152b57 100644
--- code/sound/speech.cpp
+++ code/sound/speech.cpp
@@ -29,6 +29,9 @@
 	#include <sphelper.h>
 
 	ISpVoice *Voice_device;
+#elif defined(__APPLE__)
+#include <ApplicationServices/ApplicationServices.h>
+        SpeechChannel speech_channel;
 #elif defined(SCP_UNIX)
 	#include <fcntl.h>
 //	#include <stdio.h>
@@ -56,13 +59,18 @@ bool speech_init()
 		(void **)&Voice_device);
 
 	Speech_init = SUCCEEDED(hr);
+#elif defined(__APPLE__)
+    OSErr err;
+    err = NewSpeechChannel(NULL, &speech_channel);
+    if (err) {
+        return false;
+    }
+    Speech_init = true;
 #else
 
 	speech_dev = open("/dev/speech", O_WRONLY | O_DIRECT);
-//	speech_dev = fopen("/dev/speech", "w");
 
 	if (speech_dev == -1) {
-//	if (speech_dev == NULL) {
 		mprintf(("Couldn't open '/dev/speech', turning text-to-speech off...\n"));
 		return false;
 	}
@@ -79,9 +87,10 @@ void speech_deinit()
 
 #ifdef _WIN32
 	Voice_device->Release();
+#elif defined(__APPLE__)
+    DisposeSpeechChannel(speech_channel);
 #else
 	close(speech_dev);
-//	fclose(speech_dev);
 #endif
 }
 
@@ -113,6 +122,35 @@ bool speech_play(const char *text)
 
 	speech_stop();
 	return SUCCEEDED(Voice_device->Speak(Conversion_buffer, SPF_ASYNC, NULL));
+#elif defined(__APPLE__)
+	int len = strlen(text);
+	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
+
+	if(len > (MAX_SPEECH_CHAR_LEN - 1)) {
+		len = MAX_SPEECH_CHAR_LEN - 1;
+	}
+
+	int count = 0;
+	for(int i = 0; i < len; i++) {
+		if(text[i] == '$') {
+			i++;
+			continue;
+		}
+        if(text[i] == '\n') {
+            Conversion_buffer[count] = '\n';
+            count++;
+        }
+
+		Conversion_buffer[count] = text[i];
+		count++;
+	}
+
+	Conversion_buffer[count] = '\0';
+
+    CFStringRef speech_string = CFStringCreateWithCString(NULL, Conversion_buffer, kCFStringEncodingASCII);
+    OSErr err = SpeakCFString(speech_channel, speech_string, NULL);
+    CFRelease(speech_string);
+    return !err;
 #else
 	int len = strlen(text);
 	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
@@ -150,6 +188,8 @@ bool speech_pause()
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Pause());
+#elif defined(__APPLE__)
+    return !PauseSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -162,6 +202,8 @@ bool speech_resume()
 	if(Speech_init == false) return true;
 #ifdef _WIN32
 	return SUCCEEDED(Voice_device->Resume());
+#elif defined(__APPLE__)
+    return !ContinueSpeech(speech_channel);
 #else
 	STUB_FUNCTION;
 
@@ -174,6 +216,8 @@ bool speech_stop()
 	if(Speech_init == false) return true;
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL ));
+#elif defined(__APPLE__)
+    return !StopSpeechAt(speech_channel, kEndOfWord);
 #else
 	STUB_FUNCTION;
 
@@ -185,6 +229,10 @@ bool speech_set_volume(unsigned short volume)
 {
 #ifdef _WIN32
     return SUCCEEDED(Voice_device->SetVolume(volume));
+#elif defined(__APPLE__)
+    OSErr err = SetSpeechInfo(speech_channel, soVolume, &volume);
+    if (err) return false;
+    return true;
 #else
 	STUB_FUNCTION;
 
@@ -228,6 +276,9 @@ bool speech_set_voice(int voice)
 		count++;
 	}
 	return false;
+#elif defined(__APPLE__)
+    STUB_FUNCTION;
+    return true;
 #else
 	STUB_FUNCTION;
 
@@ -246,6 +297,11 @@ bool speech_is_speaking()
 	if (FAILED(hr)) return false;
 
 	return (pStatus.dwRunningState != SPRS_DONE);
+#elif defined(__APPLE__)
+    SpeechStatusInfo status;
+    OSErr err = GetSpeechInfo(speech_channel, soStatus, &status);
+    if (err) return false;
+    return status.outputBusy;
 #else
 	STUB_FUNCTION;
 
diff --git configure.ac configure.ac
index 88127a9..0dc4429 100644
--- configure.ac
+++ configure.ac
@@ -396,6 +396,7 @@ elif test "$fs2_os_osx" = "yes" ; then
 	FS2_CXXFLAGS="$FS2_CXXFLAGS -I/System/Library/Frameworks/OpenGL.framework/Headers"
 	FS2_LDFLAGS="$FS2_LDFLAGS -framework OpenGL"
 	AC_DEFINE([SCP_UNIX])
+	AC_DEFINE([FS2_SPEECH])
 	AC_DEFINE([NO_DIRECT3D])
 	## don't need the CFLAGS here if recent SDL is used
 elif test "$fs2_os_solaris" = "yes" ; then
@@ -474,7 +475,7 @@ fi
 
 dnl extra OSX frameworks
 if test "$fs2_os_osx" = "yes" ; then
-	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation"
+	FS2_LDFLAGS="$FS2_LDFLAGS -framework AppKit -framework Foundation -framework ApplicationServices"
 fi
 
 
diff --git projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
index 06a4269..fc79fd3 100755
--- projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
+++ projects/Xcode4/FS2_Open.xcodeproj/project.pbxproj
@@ -4447,6 +4447,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+					FS2_SPEECH,
 				);
 				GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -4494,6 +4495,7 @@
 					SCP_UNIX,
 					USE_OPENAL,
 					APPLE_APP,
+					FS2_SPEECH,
 				);
 				GCC_STRICT_ALIASING = YES;
 				GCC_UNROLL_LOOPS = YES;
macspeech5.patch (5,020 bytes)   

chief1983

2015-04-23 17:39

administrator   ~0016657

Log excerpt:

... OpenAL successfully initialized!
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
Why are you trying to free a NULL pointer? [fsmemory.cpp(19)]
STUB: speech_set_voice in /Users/cliff.gordon/fs2_open_git/code/sound/speech.cpp at line 280, thread 6729
Initializing OpenGL graphics device at 640x480 with 32-bit color...

Issue History

Date Modified Username Field Change
2012-04-13 04:20 gereedy New Issue
2012-04-13 04:20 gereedy File Added: macspeech.patch
2012-04-13 06:16 niffiwan Severity minor => feature
2012-04-13 19:54 gereedy Note Added: 0013464
2012-04-13 19:54 gereedy File Added: macspeech2.patch
2012-04-23 13:53 Echelon9 Assigned To => Echelon9
2012-04-23 13:53 Echelon9 Status new => code review
2012-04-23 14:08 Echelon9 Note Added: 0013479
2012-04-30 04:26 gereedy Note Added: 0013490
2012-04-30 15:30 Echelon9 Note Added: 0013492
2012-05-04 03:00 gereedy Note Added: 0013515
2012-05-09 19:07 chief1983 Note Added: 0013530
2012-08-14 13:51 Echelon9 Note Added: 0013905
2012-08-14 14:16 Echelon9 File Added: macspeech3.patch
2012-08-14 14:16 Echelon9 Note Added: 0013906
2014-06-30 22:18 chief1983 Note Added: 0015977
2014-06-30 22:20 chief1983 File Added: macspeech4.patch
2014-06-30 22:21 chief1983 Note Added: 0015978
2014-06-30 22:22 chief1983 Note Edited: 0015978
2014-07-01 00:26 Echelon9 Note Added: 0015981
2014-07-01 12:08 Echelon9 Note Added: 0016000
2014-07-01 13:57 chief1983 Note Added: 0016002
2014-07-01 14:11 Echelon9 Note Added: 0016003
2014-07-01 14:36 chief1983 Note Added: 0016004
2014-07-01 14:36 chief1983 Note Edited: 0016004
2014-07-01 15:09 Echelon9 Note Added: 0016005
2015-04-23 15:16 chief1983 Note Added: 0016654
2015-04-23 17:06 chief1983 Note Added: 0016655
2015-04-23 17:09 chief1983 File Added: macspeech5.patch
2015-04-23 17:09 chief1983 Note Edited: 0016655
2015-04-23 17:39 chief1983 Note Added: 0016657