1 /*
2 * sound.c from GAIM.
3 *
4 * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 * File modified by Joern Thyssen <jthyssen@dk.ibm.com> for use with
21 * GNU Backgammon.
22 *
23 * $Id: sound.c,v 1.102 2018/04/10 22:11:41 plm Exp $
24 */
25
26 #include "config.h"
27 #include <string.h>
28 #if USE_GTK
29 #include "gtkgame.h"
30 #else
31 #include "backgammon.h"
32 #include <glib.h>
33 #endif
34
35 #include "sound.h"
36 #include "util.h"
37
38 #if defined(WIN32)
39 /* for PlaySound */
40 #include <windows.h>
41 #include <mmsystem.h>
42
43 #elif HAVE_APPLE_QUICKTIME
44 #include <QuickTime/QuickTime.h>
45 #include <pthread.h>
46 #include "lib/list.h"
47
48 static int fQTInitialised = FALSE;
49 static int fQTPlaying = FALSE;
50 listOLD movielist;
51 static pthread_mutex_t mutexQTAccess;
52
53
54 void *
Thread_PlaySound_QuickTime(void * data)55 Thread_PlaySound_QuickTime(void *data)
56 {
57 int done = FALSE;
58
59 fQTPlaying = TRUE;
60
61 do {
62 listOLD *pl;
63
64 pthread_mutex_lock(&mutexQTAccess);
65
66 /* give CPU time to QT to process all running movies */
67 MoviesTask(NULL, 0);
68
69 /* check if there are any running movie left */
70 pl = &movielist;
71 done = TRUE;
72 do {
73 listOLD *next = pl->plNext;
74 if (pl->p != NULL) {
75 Movie *movie = (Movie *) pl->p;
76 if (IsMovieDone(*movie)) {
77 DisposeMovie(*movie);
78 free(movie);
79 ListDelete(pl);
80 } else
81 done = FALSE;
82 }
83 pl = next;
84 } while (pl != &movielist);
85
86 pthread_mutex_unlock(&mutexQTAccess);
87 } while (!done && fQTPlaying);
88
89 fQTPlaying = FALSE;
90
91 return NULL;
92 }
93
94
95 void
PlaySound_QuickTime(const char * cSoundFilename)96 PlaySound_QuickTime(const char *cSoundFilename)
97 {
98 int err;
99 FSSpec fsSoundFile; /* movie file location descriptor */
100 short resRefNum; /* open movie file reference */
101
102 if (!fQTInitialised) {
103 pthread_mutex_init(&mutexQTAccess, NULL);
104 ListCreate(&movielist);
105 fQTInitialised = TRUE;
106 }
107
108 /* QuickTime is NOT reentrant in Mac OS (it is in MS Windows!) */
109 pthread_mutex_lock(&mutexQTAccess);
110
111 EnterMovies(); /* can be called multiple times */
112
113 err = NativePathNameToFSSpec(cSoundFilename, &fsSoundFile, 0);
114 if (err != 0) {
115 outputf(_("PlaySound_QuickTime: error #%d, can't find %s.\n"), err, cSoundFilename);
116 } else {
117 /* open movie (WAV or whatever) file */
118 err = OpenMovieFile(&fsSoundFile, &resRefNum, fsRdPerm);
119 if (err != 0) {
120 outputf(_("PlaySound_QuickTime: error #%d opening %s.\n"), err, cSoundFilename);
121 } else {
122 /* create movie from movie file */
123 Movie *movie = (Movie *) malloc(sizeof(Movie));
124 err = NewMovieFromFile(movie, resRefNum, NULL, NULL, 0, NULL);
125 CloseMovieFile(resRefNum);
126 if (err != 0) {
127 outputf(_("PlaySound_QuickTime: error #%d reading %s.\n"), err, cSoundFilename);
128 } else {
129 /* reset movie timebase */
130 TimeRecord t = { 0 };
131 t.base = GetMovieTimeBase(*movie);
132 SetMovieTime(*movie, &t);
133 /* add movie to list of running movies */
134 ListInsert(&movielist, movie);
135 /* run movie */
136 StartMovie(*movie);
137 }
138 }
139 }
140
141 pthread_mutex_unlock(&mutexQTAccess);
142
143 if (!fQTPlaying) {
144 /* launch playing thread if necessary */
145 int err;
146 pthread_t qtthread;
147 fQTPlaying = TRUE;
148 err = pthread_create(&qtthread, 0L, Thread_PlaySound_QuickTime, NULL);
149 if (err == 0)
150 pthread_detach(qtthread);
151 else
152 fQTPlaying = FALSE;
153 }
154 }
155 #elif HAVE_APPLE_COREAUDIO
156 /* Apple CoreAudio Sound support
157 * Written by Michael Petch */
158
159 #include <AudioToolbox/AudioToolbox.h>
160 #include <CoreAudio/CoreAudioTypes.h>
161 #include <ApplicationServices/ApplicationServices.h>
162
163 static pthread_mutex_t mutexCAAccess;
164 static AudioFileID audioFile;
165 static AUGraph theGraph;
166 static int fCAInitialised = FALSE;
167 static Float64 fileDuration = 0.0;
168
169 #define CoreAudioChkError(func,context,ret) \
170 { \
171 int result; \
172 if ((result = func)!=0) \
173 { \
174 outputf(_("Apple CoreAudio Error (" context "): %d\n"), result); \
175 pthread_mutex_unlock (&mutexCAAccess); \
176 return ret; \
177 } \
178 }
179 double CoreAudio_PrepareFileAU(AudioUnit * au, AudioStreamBasicDescription * fileFormat, AudioFileID audioFile);
180 void CoreAudio_MakeSimpleGraph(AUGraph * theGraph, AudioUnit * fileAU,
181 AudioStreamBasicDescription * fileFormat, AudioFileID audioFile);
182
183 void
CoreAudio_ShutDown()184 CoreAudio_ShutDown()
185 {
186 AUGraphStop(theGraph);
187 AUGraphUninitialize(theGraph);
188 AudioFileClose(audioFile);
189 AUGraphClose(theGraph);
190 }
191
192 void
CoreAudio_PlayFile_Thread(void * auGraph)193 CoreAudio_PlayFile_Thread(void *auGraph)
194 {
195 /* Start playing the sound file, and wait for it to complete */
196 AUGraphStart(theGraph);
197 usleep((int) (1000.0 * 1000.0 * fileDuration));
198
199 CoreAudio_ShutDown();
200
201 /* Shutdown the audio stream */
202 pthread_mutex_unlock(&mutexCAAccess);
203 }
204
205 void
CoreAudio_PlayFile(char * const fileName)206 CoreAudio_PlayFile(char *const fileName)
207 {
208 pthread_t CAThread;
209
210 /* first time through initialise the mutex */
211 if (!fCAInitialised) {
212 pthread_mutex_init(&mutexCAAccess, NULL);
213 fCAInitialised = TRUE;
214 }
215
216 /* Apparently CoreAudio is not fully reentrant */
217 pthread_mutex_lock(&mutexCAAccess);
218
219 /* Open the sound file */
220 CFURLRef outInputFileURL = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
221 (const UInt8 *) fileName, strlen(fileName),
222 false);
223 if (AudioFileOpenURL(outInputFileURL, kAudioFileReadPermission, 0, &audioFile)) {
224 outputf(_("Apple CoreAudio Error, can't find %s\n"), fileName);
225 return;
226 }
227
228 /* Get properties of the file */
229 AudioStreamBasicDescription fileFormat;
230 UInt32 propsize = sizeof(AudioStreamBasicDescription);
231 CoreAudioChkError(AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat,
232 &propsize, &fileFormat), "AudioFileGetProperty Dataformat",);
233
234 /* Setup sound state */
235 AudioUnit fileAU;
236 memset(&fileAU, 0, sizeof(AudioUnit));
237 memset(&theGraph, 0, sizeof(AUGraph));
238
239 /* Setup a simple output graph and AU */
240 CoreAudio_MakeSimpleGraph(&theGraph, &fileAU, &fileFormat, audioFile);
241
242 /* Load the file contents */
243 fileDuration = CoreAudio_PrepareFileAU(&fileAU, &fileFormat, audioFile);
244
245 if (pthread_create(&CAThread, 0L, (void *)CoreAudio_PlayFile_Thread, NULL) == 0)
246 pthread_detach(CAThread);
247 else {
248 CoreAudio_ShutDown();
249 pthread_mutex_unlock(&mutexCAAccess);
250 }
251 }
252
253 double
CoreAudio_PrepareFileAU(AudioUnit * au,AudioStreamBasicDescription * fileFormat,AudioFileID audioFile)254 CoreAudio_PrepareFileAU(AudioUnit * au, AudioStreamBasicDescription * fileFormat, AudioFileID audioFile)
255 {
256 UInt64 nPackets;
257 UInt32 propsize = sizeof(nPackets);
258 CoreAudioChkError(AudioFileGetProperty(audioFile, kAudioFilePropertyAudioDataPacketCount,
259 &propsize, &nPackets), "AudioFileGetProperty PacketCount", 0.0);
260
261 /* Get playing time in seconds */
262 Float64 fileDuration = (nPackets * fileFormat->mFramesPerPacket) / fileFormat->mSampleRate;
263
264 /* Initialize the region */
265 ScheduledAudioFileRegion rgn;
266 memset(&rgn, 0, sizeof(rgn));
267 rgn.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
268 rgn.mTimeStamp.mSampleTime = 0;
269 rgn.mCompletionProc = NULL;
270 rgn.mCompletionProcUserData = NULL;
271 rgn.mAudioFile = audioFile;
272 rgn.mLoopCount = 1;
273 rgn.mStartFrame = 0;
274 rgn.mFramesToPlay = (UInt32) (nPackets * fileFormat->mFramesPerPacket);
275
276 CoreAudioChkError(AudioUnitSetProperty(*au, kAudioUnitProperty_ScheduledFileRegion,
277 kAudioUnitScope_Global, 0, &rgn, sizeof(rgn)),
278 "kAudioUnitProperty_ScheduledFileRegion", 0.0);
279
280 UInt32 defaultVal = 0;
281 CoreAudioChkError(AudioUnitSetProperty(*au, kAudioUnitProperty_ScheduledFilePrime,
282 kAudioUnitScope_Global, 0, &defaultVal, sizeof(defaultVal)),
283 "kAudioUnitProperty_ScheduledFilePrime", 0.0);
284
285
286 /* Inform AU to start playing at next cycle */
287 AudioTimeStamp startTime;
288 memset(&startTime, 0, sizeof(startTime));
289 startTime.mFlags = kAudioTimeStampSampleTimeValid;
290 startTime.mSampleTime = -1;
291 CoreAudioChkError(AudioUnitSetProperty(*au, kAudioUnitProperty_ScheduleStartTimeStamp,
292 kAudioUnitScope_Global, 0, &startTime, sizeof(startTime)),
293 "AudioUnitSetproperty StartTime", 0.0);
294
295 return fileDuration;
296 }
297
298 void
CoreAudio_MakeSimpleGraph(AUGraph * theGraph,AudioUnit * fileAU,AudioStreamBasicDescription * fileFormat,AudioFileID audioFile)299 CoreAudio_MakeSimpleGraph(AUGraph * theGraph, AudioUnit * fileAU, AudioStreamBasicDescription * fileFormat,
300 AudioFileID audioFile)
301 {
302 CoreAudioChkError(NewAUGraph(theGraph), "NewAUGraph",);
303
304 AudioComponentDescription cd;
305 memset(&cd, 0, sizeof(cd));
306
307 /* Initialize and add Output Node */
308 cd.componentType = kAudioUnitType_Output;
309 cd.componentSubType = kAudioUnitSubType_DefaultOutput;
310 cd.componentManufacturer = kAudioUnitManufacturer_Apple;
311
312 AUNode outputNode;
313 CoreAudioChkError(AUGraphAddNode(*theGraph, &cd, &outputNode), "AUGraphAddNode Output",);
314
315 /* Initialize and add the AU node */
316 AUNode fileNode;
317 cd.componentType = kAudioUnitType_Generator;
318 cd.componentSubType = kAudioUnitSubType_AudioFilePlayer;
319
320 CoreAudioChkError(AUGraphAddNode(*theGraph, &cd, &fileNode), "AUGraphAddNode AU",);
321
322 /* Make connections */
323 CoreAudioChkError(AUGraphOpen(*theGraph), "AUGraphOpen",);
324
325 /* Set Schedule properties and initialize the graph with the file */
326 AudioUnit anAU;
327 memset(&anAU, 0, sizeof(anAU));
328 CoreAudioChkError(AUGraphNodeInfo(*theGraph, fileNode, NULL, &anAU), "AUGraphNodeInfo",);
329
330 *fileAU = anAU;
331
332 CoreAudioChkError(AudioUnitSetProperty(*fileAU, kAudioUnitProperty_ScheduledFileIDs,
333 kAudioUnitScope_Global, 0, &audioFile, sizeof(audioFile)),
334 "SetScheduleFile",);
335 CoreAudioChkError(AUGraphConnectNodeInput(*theGraph, fileNode, 0, outputNode, 0), "AUGraphConnectNodeInput",);
336 CoreAudioChkError(AUGraphInitialize(*theGraph), "AUGraphInitialize",);
337 }
338
339 #elif HAVE_CANBERRA
340 #include <canberra.h>
341 #include <canberra-gtk.h>
342 #endif
343
344 const char *sound_description[NUM_SOUNDS] = {
345 N_("Starting GNU Backgammon"),
346 N_("Exiting GNU Backgammon"),
347 N_("Agree"),
348 N_("Doubling"),
349 N_("Drop"),
350 N_("Chequer movement"),
351 N_("noun|Move"),
352 N_("Redouble"),
353 N_("Resign"),
354 N_("Roll"),
355 N_("Take"),
356 N_("Human fans"),
357 N_("Human wins game"),
358 N_("Human wins match"),
359 N_("Bot fans"),
360 N_("Bot wins game"),
361 N_("Bot wins match"),
362 N_("Analysis is finished")
363 };
364
365 const char *sound_command[NUM_SOUNDS] = {
366 "start",
367 "exit",
368 "agree",
369 "double",
370 "drop",
371 "chequer",
372 "move",
373 "redouble",
374 "resign",
375 "roll",
376 "take",
377 "humanfans",
378 "humanwinsgame",
379 "humanwinsmatch",
380 "botfans",
381 "botwinsgame",
382 "botwinsmatch",
383 "analysisfinished"
384 };
385
386 int fSound = TRUE;
387 int fQuiet = FALSE;
388 static char *sound_cmd = NULL;
389
390 void
playSoundFile(char * file,gboolean UNUSED (sync))391 playSoundFile(char *file, gboolean UNUSED(sync))
392 {
393 GError *error = NULL;
394
395 if (!g_file_test(file, G_FILE_TEST_EXISTS)) {
396 outputf(_("The sound file (%s) doesn't exist.\n"), file);
397 return;
398 }
399
400 if (sound_cmd && *sound_cmd) {
401 char *commandString;
402
403 commandString = g_strdup_printf("%s %s", sound_cmd, file);
404 if (!g_spawn_command_line_async(commandString, &error)) {
405 outputf(_("sound command (%s) could not be launched: %s\n"), commandString, error->message);
406 g_error_free(error);
407 }
408 return;
409 }
410 #if defined(WIN32)
411 SetLastError(0);
412 while (!PlaySound(file, NULL, SND_FILENAME | SND_ASYNC | SND_NOSTOP | SND_NODEFAULT)) {
413 static int soundDeviceAttached = -1;
414 if (soundDeviceAttached == -1) { /* Check for sound card */
415 soundDeviceAttached = (int) waveOutGetNumDevs();
416 }
417 if (!soundDeviceAttached) { /* No sound card found - disable sound */
418 g_print(_("No soundcard found - sounds disabled\n"));
419 fSound = FALSE;
420 return;
421 }
422 /* Check for errors */
423 if (GetLastError()) {
424 PrintSystemError(_("Playing sound"));
425 SetLastError(0);
426 return;
427 }
428 Sleep(1); /* Wait (1ms) for current sound to finish */
429 }
430 #elif HAVE_APPLE_QUICKTIME
431 PlaySound_QuickTime(file);
432 #elif HAVE_APPLE_COREAUDIO
433 CoreAudio_PlayFile(file);
434 #elif HAVE_CANBERRA
435 {
436 static ca_context *canberracontext = NULL;
437 if (!canberracontext) {
438 #if USE_GTK
439 if (fX)
440 canberracontext = ca_gtk_context_get();
441 else
442 #endif
443 ca_context_create(&canberracontext);
444 ca_context_change_props(canberracontext, CA_PROP_CANBERRA_ENABLE, "1", NULL);
445 }
446 ca_context_play(canberracontext, 0, CA_PROP_MEDIA_FILENAME, file, NULL);
447 }
448 #endif
449 }
450
451 extern void
playSound(const gnubgsound gs)452 playSound(const gnubgsound gs)
453 {
454 char *sound;
455
456 if (!fSound || fQuiet)
457 /* no sounds for this user */
458 return;
459
460 sound = GetSoundFile(gs);
461 if (!*sound) {
462 g_free(sound);
463 return;
464 }
465 #if USE_GTK
466 if (!fX || gs == SOUND_EXIT)
467 playSoundFile(sound, TRUE);
468 else
469 playSoundFile(sound, FALSE);
470 #else
471 playSoundFile(sound, TRUE);
472 #endif
473
474 g_free(sound);
475 }
476
477
478 extern void
SoundWait(void)479 SoundWait(void)
480 {
481 if (!fSound)
482 return;
483 #ifdef WIN32
484 /* Wait 1/10 of a second to make sure sound has started */
485 Sleep(100);
486 while (!PlaySound(NULL, NULL, SND_FILENAME | SND_ASYNC | SND_NOSTOP | SND_NODEFAULT))
487 Sleep(1); /* Wait (1ms) for previous sound to finish */
488 return;
489 #endif
490 }
491
492 static char *sound_file[NUM_SOUNDS] = { 0 };
493
494 extern char *
GetDefaultSoundFile(gnubgsound sound)495 GetDefaultSoundFile(gnubgsound sound)
496 {
497 static char aszDefaultSound[NUM_SOUNDS][80] = {
498 /* start and exit */
499 "fanfare.wav",
500 "haere-ra.wav",
501 /* commands */
502 "drop.wav",
503 "double.wav",
504 "drop.wav",
505 "chequer.wav",
506 "move.wav",
507 "double.wav",
508 "resign.wav",
509 "roll.wav",
510 "take.wav",
511 /* events */
512 "dance.wav",
513 "gameover.wav",
514 "matchover.wav",
515 "dance.wav",
516 "gameover.wav",
517 "matchover.wav",
518 "fanfare.wav"
519 };
520
521 return BuildFilename2("sounds", aszDefaultSound[sound]);
522 }
523
524 extern char *
GetSoundFile(gnubgsound sound)525 GetSoundFile(gnubgsound sound)
526 {
527 if (!sound_file[sound])
528 return GetDefaultSoundFile(sound);
529
530 if (!(*sound_file[sound]))
531 return g_strdup("");
532
533 if (g_file_test(sound_file[sound], G_FILE_TEST_EXISTS))
534 return g_strdup(sound_file[sound]);
535
536 if (g_path_is_absolute(sound_file[sound]))
537 return GetDefaultSoundFile(sound);
538
539 return BuildFilename(sound_file[sound]);
540 }
541
542 extern void
SetSoundFile(const gnubgsound sound,const char * file)543 SetSoundFile(const gnubgsound sound, const char *file)
544 {
545 char *old_file = GetSoundFile(sound);
546 const char *new_file = file ? file : "";
547 if (!strcmp(new_file, old_file)) {
548 g_free(old_file);
549 return; /* No change */
550 }
551 g_free(old_file);
552
553 if (!*new_file) {
554 outputf(_("No sound played for: %s\n"), Q_(sound_description[sound]));
555 } else {
556 outputf(_("Sound for: %s: %s\n"), Q_(sound_description[sound]), new_file);
557 }
558 g_free(sound_file[sound]);
559 sound_file[sound] = g_strdup(new_file);
560 }
561
562 extern const char *
sound_get_command(void)563 sound_get_command(void)
564 {
565 return (sound_cmd ? sound_cmd : "");
566 }
567
568 extern char *
sound_set_command(const char * sz)569 sound_set_command(const char *sz)
570 {
571 g_free(sound_cmd);
572 sound_cmd = g_strdup(sz ? sz : "");
573 return sound_cmd;
574 }
575
576 extern void
SetExitSoundOff(void)577 SetExitSoundOff(void)
578 {
579 sound_file[SOUND_EXIT] = g_strdup("");
580 }
581