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