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