1 /*
2  * Code created by Thomas Whittaker (RT) for a FreeSpace 2 source code project
3  *
4  * You may not sell or otherwise commercially exploit the source or things you
5  * created based on the source.
6  *
7 */
8 
9 
10 
11 
12 
13 #ifndef FS2_SPEECH
14 #ifdef _WIN32
15 #if NDEBUG
16 	#pragma message( "WARNING: You have not compiled speech into this build (use FS2_SPEECH)" )
17 #endif // NDEBUG
18 #endif // _WIN32
19 #else // to end-of-file ...
20 
21 
22 #ifdef LAUNCHER
23 #include "stdafx.h"
24 #endif	//LAUNCHER
25 
26 #ifdef _WIN32
27 
28 // Since we define these ourself we need to undefine them for the sapi header
29 #pragma push_macro("strcpy_s")
30 #pragma push_macro("strcat_s")
31 #pragma push_macro("memset")
32 #pragma push_macro("memcpy")
33 #undef strcpy_s
34 #undef strcat_s
35 #undef memset
36 #undef memcpy
37 
38 	#include <windows.h>
39 	#include <sapi.h>
40 
41 	#include <sphelper.h>
42 
43 #pragma pushpop_macro("strcpy_s")
44 #pragma pushpop_macro("strcat_s")
45 #pragma pushpop_macro("memset")
46 #pragma pushpop_macro("memcpy")
47 
48 	ISpVoice *Voice_device;
49 #elif defined(SCP_UNIX)
50 	#include <fcntl.h>
51 //	#include <stdio.h>
52 
53 	int speech_dev = -1;
54 //	FILE *speech_dev = NULL;
55 #else
56 	#pragma error( "ERROR: Unknown platform, speech (FS2_SPEECH) is not supported" )
57 #endif	//_WIN32
58 
59 #pragma warning(push)
60 #pragma warning(disable: 4995)
61 // Visual Studio complains that some functions are deprecated so this fixes that
62 #include <cstring>
63 #include <cwchar>
64 #include <cstdio>
65 #pragma warning(pop)
66 
67 #include "globalincs/pstypes.h"
68 #include "utils/unicode.h"
69 #include "speech.h"
70 
71 
72 bool Speech_init = false;
73 
speech_init()74 bool speech_init()
75 {
76 #ifdef _WIN32
77     HRESULT hr = CoCreateInstance(
78 		CLSID_SpVoice,
79 		NULL,
80 		CLSCTX_ALL,
81 		IID_ISpVoice,
82 		(void **)&Voice_device);
83 
84 	Speech_init = SUCCEEDED(hr);
85 #else
86 
87 	speech_dev = open("/dev/speech", O_WRONLY | O_DIRECT);
88 //	speech_dev = fopen("/dev/speech", "w");
89 
90 	if (speech_dev == -1) {
91 //	if (speech_dev == NULL) {
92 		mprintf(("Couldn't open '/dev/speech', turning text-to-speech off...\n"));
93 		return false;
94 	}
95 
96 	Speech_init = true;
97 #endif
98 
99 	nprintf(("Speech", "Speech init %s\n", Speech_init ? "succeeded!" : "failed!"));
100 	return Speech_init;
101 }
102 
speech_deinit()103 void speech_deinit()
104 {
105 	if(Speech_init == false) return;
106 
107 #ifdef _WIN32
108 	Voice_device->Release();
109 #else
110 	close(speech_dev);
111 //	fclose(speech_dev);
112 #endif
113 }
114 
speech_play(const char * text)115 bool speech_play(const char *text)
116 {
117 	nprintf(("Speech", "Attempting to play speech string %s...\n", text));
118 
119 	if(Speech_init == false) return true;
120 	if (text == NULL) {
121 		nprintf(("Speech", "Not playing speech because passed text is null.\n"));
122 		return false;
123 	}
124 
125 #ifdef _WIN32
126 	SCP_string work_buffer;
127 
128 	bool saw_dollar = false;
129 	for (auto ch : unicode::codepoint_range(text)) {
130 		if (ch == UNICODE_CHAR('$')) {
131 			// Skip $ escape sequences which appear in briefing text
132 			saw_dollar = true;
133 			continue;
134 		} else if (saw_dollar) {
135 			saw_dollar = false;
136 			continue;
137 		}
138 
139 		unicode::encode(ch, std::back_inserter(work_buffer));
140 	}
141 
142 	// Determine the needed amount of data
143 	auto num_chars = MultiByteToWideChar(CP_UTF8, 0, work_buffer.c_str(), (int) work_buffer.size(), nullptr, 0);
144 
145 	if (num_chars <= 0) {
146 		// Error
147 		return false;
148 	}
149 
150 	std::wstring wide_string;
151 	wide_string.resize(num_chars);
152 
153 	auto err = MultiByteToWideChar(CP_UTF8, 0, work_buffer.c_str(), (int)work_buffer.size(), &wide_string[0], num_chars);
154 
155 	if (err <= 0) {
156 		return false;
157 	}
158 
159 	speech_stop();
160 	return SUCCEEDED(Voice_device->Speak(wide_string.c_str(), SPF_ASYNC, NULL));
161 #else
162 	int len = strlen(text);
163 	char Conversion_buffer[MAX_SPEECH_CHAR_LEN];
164 
165 	if(len > (MAX_SPEECH_CHAR_LEN - 1)) {
166 		len = MAX_SPEECH_CHAR_LEN - 1;
167 	}
168 
169 	int count = 0;
170 	for(int i = 0; i < len; i++) {
171 		if(text[i] == '$') {
172 			i++;
173 			continue;
174 		}
175 
176 		Conversion_buffer[count] = text[i];
177 		count++;
178 	}
179 
180 	Conversion_buffer[count] = '\0';
181 
182 	if ( write(speech_dev, Conversion_buffer, count) == -1 )
183 		return false;
184 //	if (fwrite(Conversion_buffer, count, 1, speech_dev))
185 //		fflush(speech_dev);
186 //	else
187 //		return false;
188 
189 	return true;
190 #endif	//_WIN32
191 }
192 
speech_pause()193 bool speech_pause()
194 {
195 	if(Speech_init == false) return true;
196 #ifdef _WIN32
197 	return SUCCEEDED(Voice_device->Pause());
198 #else
199 	STUB_FUNCTION;
200 
201 	return true;
202 #endif
203 }
204 
speech_resume()205 bool speech_resume()
206 {
207 	if(Speech_init == false) return true;
208 #ifdef _WIN32
209 	return SUCCEEDED(Voice_device->Resume());
210 #else
211 	STUB_FUNCTION;
212 
213 	return true;
214 #endif
215 }
216 
speech_stop()217 bool speech_stop()
218 {
219 	if(Speech_init == false) return true;
220 #ifdef _WIN32
221     return SUCCEEDED(Voice_device->Speak( NULL, SPF_PURGEBEFORESPEAK, NULL ));
222 #else
223 	STUB_FUNCTION;
224 
225 	return true;
226 #endif
227 }
228 
speech_set_volume(unsigned short volume)229 bool speech_set_volume(unsigned short volume)
230 {
231 #ifdef _WIN32
232     return SUCCEEDED(Voice_device->SetVolume(volume));
233 #else
234 	STUB_FUNCTION;
235 
236 	return true;
237 #endif
238 }
239 
speech_set_voice(int voice)240 bool speech_set_voice(int voice)
241 {
242 #ifdef _WIN32
243 	HRESULT                             hr;
244 	CComPtr<ISpObjectToken>             cpVoiceToken;
245 	CComPtr<IEnumSpObjectTokens>        cpEnum;
246 	ULONG                               num_voices = 0;
247 
248 	//Enumerate the available voices
249 	hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &cpEnum);
250 
251 	if(FAILED(hr)) return false;
252 
253     hr = cpEnum->GetCount(&num_voices);
254 
255 	if(FAILED(hr)) return false;
256 
257 	int count = 0;
258 	// Obtain a list of available voice tokens, set the voice to the token, and call Speak
259 	while (num_voices -- )
260 	{
261 		cpVoiceToken.Release();
262 
263 		hr = cpEnum->Next( 1, &cpVoiceToken, NULL );
264 
265 		if(FAILED(hr)) {
266 			return false;
267 		}
268 
269 		if(count == voice) {
270 			return SUCCEEDED(Voice_device->SetVoice(cpVoiceToken));
271 		}
272 
273 		count++;
274 	}
275 	return false;
276 #else
277 	STUB_FUNCTION;
278 
279 	return true;
280 #endif
281 }
282 
283 // Goober5000
speech_is_speaking()284 bool speech_is_speaking()
285 {
286 #ifdef _WIN32
287 	HRESULT			hr;
288 	SPVOICESTATUS	pStatus;
289 
290 	hr = Voice_device->GetStatus(&pStatus, NULL);
291 	if (FAILED(hr)) return false;
292 
293 	return (pStatus.dwRunningState != SPRS_DONE);
294 #else
295 	STUB_FUNCTION;
296 
297 	return false;
298 #endif
299 }
300 
speech_enumerate_voices()301 SCP_vector<SCP_string> speech_enumerate_voices()
302 {
303 #ifdef _WIN32
304 	HRESULT hr = CoCreateInstance(
305 		CLSID_SpVoice,
306 		NULL,
307 		CLSCTX_ALL,
308 		IID_ISpVoice,
309 		(void **)&Voice_device);
310 
311 	if (FAILED(hr)) {
312 		return SCP_vector<SCP_string>();
313 	}
314 
315 	// This code is mostly copied from wxLauncher
316 	ISpObjectTokenCategory * comTokenCategory = NULL;
317 	IEnumSpObjectTokens * comVoices = NULL;
318 	ULONG comVoicesCount = 0;
319 
320 	// Generate enumeration of voices
321 	hr = ::CoCreateInstance(CLSID_SpObjectTokenCategory, NULL,
322 		CLSCTX_INPROC_SERVER, IID_ISpObjectTokenCategory, (LPVOID*)&comTokenCategory);
323 	if (FAILED(hr)) {
324 		return SCP_vector<SCP_string>();
325 	}
326 
327 	hr = comTokenCategory->SetId(SPCAT_VOICES, false);
328 	if (FAILED(hr)) {
329 		return SCP_vector<SCP_string>();
330 	}
331 
332 	hr = comTokenCategory->EnumTokens(NULL, NULL, &comVoices);
333 	if (FAILED(hr)) {
334 		return SCP_vector<SCP_string>();
335 	}
336 
337 	hr = comVoices->GetCount(&comVoicesCount);
338 	if (FAILED(hr)) {
339 		return SCP_vector<SCP_string>();
340 	}
341 
342 	SCP_vector<SCP_string> voices;
343 	while (comVoicesCount > 0) {
344 		ISpObjectToken * comAVoice = NULL;
345 
346 		comVoices->Next(1, &comAVoice, NULL); // retrieve just one
347 
348 		LPWSTR id = NULL;
349 		comAVoice->GetStringValue(NULL, &id);
350 
351 		auto idlength = wcslen(id);
352 		auto buffer_size = WideCharToMultiByte(CP_UTF8, 0, id, (int)idlength, nullptr, 0, nullptr, nullptr);
353 
354 		if (buffer_size > 0) {
355 			SCP_string voiceName;
356 			voiceName.resize(buffer_size);
357 			buffer_size = WideCharToMultiByte(CP_UTF8, 0, id, (int)idlength, &voiceName[0], buffer_size, nullptr, nullptr);
358 
359 			voices.push_back(voiceName);
360 		}
361 
362 		CoTaskMemFree(id);
363 		comAVoice->Release();
364 		comVoicesCount--;
365 	}
366 
367 	comTokenCategory->Release();
368 
369 	Voice_device->Release();
370 
371 	return voices;
372 #else
373 	STUB_FUNCTION;
374 
375 	return SCP_vector<SCP_string>();
376 #endif
377 }
378 
379 #endif // FS2_SPEECH
380