1 /* sdlsfx.c: Creating the program's sound effects.
2  *
3  * Copyright (C) 2001-2006 by Brian Raiter, under the GNU General Public
4  * License. No warranty. See COPYING for details.
5  */
6 
7 #include	<stdio.h>
8 #include	<stdlib.h>
9 #include	<string.h>
10 #include	"SDL.h"
11 #include	"sdlgen.h"
12 #include	"../err.h"
13 #include	"../state.h"
14 
15 /* Some generic default settings for the audio output.
16  */
17 #define DEFAULT_SND_FMT		AUDIO_S16SYS
18 #define DEFAULT_SND_FREQ	22050
19 #define	DEFAULT_SND_CHAN	1
20 
21 /* The data needed for each sound effect's wave.
22  */
23 typedef	struct sfxinfo {
24     Uint8	       *wave;		/* the actual wave data */
25     Uint32		len;		/* size of the wave data */
26     int			pos;		/* how much has been played already */
27     int			playing;	/* is the wave currently playing? */
28     char const	       *textsfx;	/* the onomatopoeia string */
29 } sfxinfo;
30 
31 /* The data needed to talk to the sound output device.
32  */
33 static SDL_AudioSpec	spec;
34 
35 /* All of the sound effects.
36  */
37 static sfxinfo		sounds[SND_COUNT];
38 
39 /* TRUE if sound-playing has been enabled.
40  */
41 static int		enabled = FALSE;
42 
43 /* TRUE if the program is currently talking to a sound device.
44  */
45 static int		hasaudio = FALSE;
46 
47 /* The volume level.
48  */
49 static int		volume = SDL_MIX_MAXVOLUME;
50 
51 /* The sound buffer size scaling factor.
52  */
53 static int		soundbufsize = 0;
54 
55 
56 /* Initialize the textual sound effects.
57  */
initonomatopoeia(void)58 static void initonomatopoeia(void)
59 {
60     sounds[SND_CHIP_LOSES].textsfx      = "\"Bummer\"";
61     sounds[SND_CHIP_WINS].textsfx       = "Tadaa!";
62     sounds[SND_TIME_OUT].textsfx        = "Clang!";
63     sounds[SND_TIME_LOW].textsfx        = "Ktick!";
64     sounds[SND_DEREZZ].textsfx		= "Bzont!";
65     sounds[SND_CANT_MOVE].textsfx       = "Mnphf!";
66     sounds[SND_IC_COLLECTED].textsfx    = "Chack!";
67     sounds[SND_ITEM_COLLECTED].textsfx  = "Slurp!";
68     sounds[SND_BOOTS_STOLEN].textsfx    = "Flonk!";
69     sounds[SND_TELEPORTING].textsfx     = "Bamff!";
70     sounds[SND_DOOR_OPENED].textsfx     = "Spang!";
71     sounds[SND_SOCKET_OPENED].textsfx   = "Clack!";
72     sounds[SND_BUTTON_PUSHED].textsfx   = "Click!";
73     sounds[SND_BOMB_EXPLODES].textsfx   = "Booom!";
74     sounds[SND_WATER_SPLASH].textsfx    = "Plash!";
75     sounds[SND_TILE_EMPTIED].textsfx    = "Whisk!";
76     sounds[SND_WALL_CREATED].textsfx    = "Chunk!";
77     sounds[SND_TRAP_ENTERED].textsfx    = "Shunk!";
78     sounds[SND_SKATING_TURN].textsfx    = "Whing!";
79     sounds[SND_SKATING_FORWARD].textsfx = "Whizz ...";
80     sounds[SND_SLIDING].textsfx         = "Drrrr ...";
81     sounds[SND_BLOCK_MOVING].textsfx    = "Scrrr ...";
82     sounds[SND_SLIDEWALKING].textsfx    = "slurp slurp ...";
83     sounds[SND_ICEWALKING].textsfx      = "snick snick ...";
84     sounds[SND_WATERWALKING].textsfx    = "plip plip ...";
85     sounds[SND_FIREWALKING].textsfx     = "crackle crackle ...";
86 }
87 
88 /* Display the onomatopoeia for the currently playing sound effect.
89  * Only the first sound is used, since we can't display multiple
90  * strings.
91  */
displaysoundeffects(unsigned long sfx,int display)92 static void displaysoundeffects(unsigned long sfx, int display)
93 {
94     unsigned long	flag;
95     int			i;
96 
97     if (!display) {
98 	setdisplaymsg(NULL, 0, 0);
99 	return;
100     }
101 
102     for (flag = 1, i = 0 ; flag ; flag <<= 1, ++i) {
103 	if (sfx & flag) {
104 	    setdisplaymsg(sounds[i].textsfx, 500, 10);
105 	    return;
106 	}
107     }
108 }
109 
110 /* The callback function that is called by the sound driver to supply
111  * the latest sound effects. All the sound effects are checked, and
112  * the ones that are being played get another chunk of their sound
113  * data mixed into the output buffer. When the end of a sound effect's
114  * wave data is reached, the one-shot sounds are changed to be marked
115  * as not playing, and the continuous sounds are looped.
116  */
sfxcallback(void * data,Uint8 * wave,int len)117 static void sfxcallback(void *data, Uint8 *wave, int len)
118 {
119     int	i, n;
120 
121     (void)data;
122     memset(wave, spec.silence, len);
123     for (i = 0 ; i < SND_COUNT ; ++i) {
124 	if (!sounds[i].wave)
125 	    continue;
126 	if (!sounds[i].playing)
127 	    if (!sounds[i].pos || i >= SND_ONESHOT_COUNT)
128 		continue;
129 	n = sounds[i].len - sounds[i].pos;
130 	if (n > len) {
131 	    SDL_MixAudio(wave, sounds[i].wave + sounds[i].pos, len, volume);
132 	    sounds[i].pos += len;
133 	} else {
134 	    SDL_MixAudio(wave, sounds[i].wave + sounds[i].pos, n, volume);
135 	    sounds[i].pos = 0;
136 	    if (i < SND_ONESHOT_COUNT) {
137 		sounds[i].playing = FALSE;
138 	    } else if (sounds[i].playing) {
139 		while (len - n >= (int)sounds[i].len) {
140 		    SDL_MixAudio(wave + n, sounds[i].wave, sounds[i].len,
141 				 volume);
142 		    n += sounds[i].len;
143 		}
144 		sounds[i].pos = len - n;
145 		SDL_MixAudio(wave + n, sounds[i].wave, sounds[i].pos, volume);
146 	    }
147 	}
148     }
149 }
150 
151 /*
152  * The exported functions.
153  */
154 
155 /* Activate or deactivate the sound system. When activating for the
156  * first time, the connection to the sound device is established. When
157  * deactivating, the connection is closed.
158  */
setaudiosystem(int active)159 int setaudiosystem(int active)
160 {
161     SDL_AudioSpec	des;
162     int			n;
163 
164     if (!enabled)
165 	return !active;
166 
167     if (!active) {
168 	if (hasaudio) {
169 	    SDL_PauseAudio(TRUE);
170 	    SDL_CloseAudio();
171 	    hasaudio = FALSE;
172 	}
173 	return TRUE;
174     }
175 
176     if (!SDL_WasInit(SDL_INIT_AUDIO)) {
177 	if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
178 	    warn("Cannot initialize audio output: %s", SDL_GetError());
179 	    return FALSE;
180 	}
181     }
182 
183     if (hasaudio)
184 	return TRUE;
185 
186     des.freq = DEFAULT_SND_FREQ;
187     des.format = DEFAULT_SND_FMT;
188     des.channels = DEFAULT_SND_CHAN;
189     des.callback = sfxcallback;
190     des.userdata = NULL;
191     for (n = 1 ; n <= des.freq / TICKS_PER_SECOND ; n <<= 1) ;
192     des.samples = (n << soundbufsize) >> 2;
193     if (SDL_OpenAudio(&des, &spec) < 0) {
194 	warn("can't access audio output: %s", SDL_GetError());
195 	return FALSE;
196     }
197     hasaudio = TRUE;
198     SDL_PauseAudio(FALSE);
199 
200     return TRUE;
201 }
202 
203 /* Load a single wave file into memory. The wave data is converted to
204  * the format expected by the sound device.
205  */
loadsfxfromfile(int index,char const * filename)206 int loadsfxfromfile(int index, char const *filename)
207 {
208     SDL_AudioSpec	specin;
209     SDL_AudioCVT	convert;
210     Uint8	       *wavein;
211     Uint8	       *wavecvt;
212     Uint32		lengthin;
213 
214     if (!filename) {
215 	freesfx(index);
216 	return TRUE;
217     }
218 
219     if (!enabled)
220 	return FALSE;
221     if (!hasaudio)
222 	if (!setaudiosystem(TRUE))
223 	    return FALSE;
224 
225     if (!SDL_LoadWAV(filename, &specin, &wavein, &lengthin)) {
226 	warn("can't load %s: %s", filename, SDL_GetError());
227 	return FALSE;
228     }
229 
230     if (SDL_BuildAudioCVT(&convert,
231 			  specin.format, specin.channels, specin.freq,
232 			  spec.format, spec.channels, spec.freq) < 0) {
233 	warn("can't create converter for %s: %s", filename, SDL_GetError());
234 	return FALSE;
235     }
236     if (!(wavecvt = malloc(lengthin * convert.len_mult)))
237 	memerrexit();
238     memcpy(wavecvt, wavein, lengthin);
239     SDL_FreeWAV(wavein);
240     convert.buf = wavecvt;
241     convert.len = lengthin;
242     if (SDL_ConvertAudio(&convert) < 0) {
243 	warn("can't convert %s: %s", filename, SDL_GetError());
244 	return FALSE;
245     }
246 
247     freesfx(index);
248     SDL_LockAudio();
249     sounds[index].wave = convert.buf;
250     sounds[index].len = convert.len * convert.len_ratio;
251     sounds[index].pos = 0;
252     sounds[index].playing = FALSE;
253     SDL_UnlockAudio();
254 
255     return TRUE;
256 }
257 
258 /* Select the sounds effects to be played. sfx is a bitmask of sound
259  * effect indexes. Any continuous sounds that are not included in sfx
260  * are stopped. One-shot sounds that are included in sfx are
261  * restarted.
262  */
playsoundeffects(unsigned long sfx)263 void playsoundeffects(unsigned long sfx)
264 {
265     unsigned long	flag;
266     int			i;
267 
268     if (!hasaudio || !volume) {
269 	displaysoundeffects(sfx, TRUE);
270 	return;
271     }
272 
273     SDL_LockAudio();
274     for (i = 0, flag = 1 ; i < SND_COUNT ; ++i, flag <<= 1) {
275 	if (sfx & flag) {
276 	    sounds[i].playing = TRUE;
277 	    if (sounds[i].pos && i < SND_ONESHOT_COUNT)
278 		sounds[i].pos = 0;
279 	} else {
280 	    if (i >= SND_ONESHOT_COUNT)
281 		sounds[i].playing = FALSE;
282 	}
283     }
284     SDL_UnlockAudio();
285 }
286 
287 /* If action is negative, stop playing all sounds immediately.
288  * Otherwise, just temporarily pause or unpause sound-playing.
289  */
setsoundeffects(int action)290 void setsoundeffects(int action)
291 {
292     int	i;
293 
294     if (!hasaudio || !volume)
295 	return;
296 
297     if (action < 0) {
298 	SDL_LockAudio();
299 	for (i = 0 ; i < SND_COUNT ; ++i) {
300 	    sounds[i].playing = FALSE;
301 	    sounds[i].pos = 0;
302 	}
303 	SDL_UnlockAudio();
304     } else {
305 	SDL_PauseAudio(!action);
306     }
307 }
308 
309 /* Release all memory for the given sound effect.
310  */
freesfx(int index)311 void freesfx(int index)
312 {
313     if (sounds[index].wave) {
314 	SDL_LockAudio();
315 	free(sounds[index].wave);
316 	sounds[index].wave = NULL;
317 	sounds[index].pos = 0;
318 	sounds[index].playing = FALSE;
319 	SDL_UnlockAudio();
320     }
321 }
322 
323 /* Set the current volume level to v. If display is true, the
324  * new volume level is displayed to the user.
325  */
setvolume(int v,int display)326 int setvolume(int v, int display)
327 {
328     char	buf[16];
329 
330     if (!hasaudio)
331 	return FALSE;
332     if (v < 0)
333 	v = 0;
334     else if (v > 10)
335 	v = 10;
336     if (!volume && v)
337 	displaysoundeffects(0, FALSE);
338     volume = (SDL_MIX_MAXVOLUME * v + 9) / 10;
339     if (display) {
340 	sprintf(buf, "Volume: %d", v);
341 	setdisplaymsg(buf, 1000, 1000);
342     }
343     return TRUE;
344 }
345 
346 /* Change the current volume level by delta. If display is true, the
347  * new volume level is displayed to the user.
348  */
changevolume(int delta,int display)349 int changevolume(int delta, int display)
350 {
351     return setvolume(((10 * volume) / SDL_MIX_MAXVOLUME) + delta, display);
352 }
353 
354 /* Shut down the sound system.
355  */
shutdown(void)356 static void shutdown(void)
357 {
358     setaudiosystem(FALSE);
359     if (SDL_WasInit(SDL_INIT_AUDIO))
360 	SDL_QuitSubSystem(SDL_INIT_AUDIO);
361     hasaudio = FALSE;
362 }
363 
364 /* Initialize the module. If silence is TRUE, then the program will
365  * leave sound output disabled.
366  */
_sdlsfxinitialize(int silence,int _soundbufsize)367 int _sdlsfxinitialize(int silence, int _soundbufsize)
368 {
369     atexit(shutdown);
370     enabled = !silence;
371     soundbufsize = _soundbufsize;
372     initonomatopoeia();
373     if (enabled)
374 	setaudiosystem(TRUE);
375     return TRUE;
376 }
377