1 /*
2  * file x11_sound.c - sound via xbsndsrv or bell
3  *
4  * $Id: sdl_sound.c,v 1.9 2006/06/13 11:19:18 fzago Exp $
5  *
6  * Program XBLAST
7  * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published
11  * by the Free Software Foundation; either version 2; or (at your option)
12  * any later version
13  *
14  * This program is distributed in the hope that it will be entertaining,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17  * Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.
21  * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "xblast.h"
25 
26 #include "SDL_mixer.h"
27 
28 /*
29  * local constants
30  */
31 
32 /* values to be used in acknowledge pipe */
33 #define SND_ACK_OK      0
34 #define SND_ACK_ERROR   1
35 
36 typedef short s16;
37 
38 static struct _sound_name
39 {
40 	int sound_id;				/* the sound's id to refer to it */
41 	const char *name;			/* raw samples data file name */
42 	uint8_t *samples;			/* pointer to samples memory */
43 	int length;					/* length in samples of the sound */
44 	int repeat;					/* repeat flag to play sound endlessly */
45 	int mono;					/* mono flag indicating mono sounds */
46 } sound_name[] = {
47 	{
48 	SND_BAD, "xb_bad.raw", NULL, 0, XBFalse, XBFalse},	/* got a skull */
49 	{
50 	SND_DROP, "xb_drop.raw", NULL, 0, XBFalse, XBTrue},	/* dropped a bomb */
51 	{
52 	SND_NEWBOMB, "xbnbmb.raw", NULL, 0, XBFalse, XBTrue},	/* got an extra bomb */
53 	{
54 	SND_NEWKICK, "xbnkick.raw", NULL, 0, XBFalse, XBTrue},	/* got kick extra */
55 	{
56 	SND_NEWPUMP, "xbnpmp.raw", NULL, 0, XBFalse, XBTrue},	/* got pump extra */
57 	{
58 	SND_NEWRC, "xbnrc.raw", NULL, 0, XBFalse, XBTrue},	/* got rem. control */
59 	{
60 	SND_MOREFIRE, "xbfire.raw", NULL, 0, XBFalse, XBTrue},	/* got more range */
61 	{
62 	SND_DEAD, "xb_dead.raw", NULL, 0, XBFalse, XBFalse},	/* player died */
63 	{
64 	SND_EXPL, "xb_expl.raw", NULL, 0, XBFalse, XBTrue},	/* normal explosion */
65 	{
66 	SND_KICK, "xb_kick.raw", NULL, 0, XBFalse, XBTrue},	/* kick a bomb */
67 	{
68 	SND_PUMP, "xb_pump.raw", NULL, 0, XBFalse, XBTrue},	/* pump a bomb */
69 	{
70 	SND_OUCH, "xb_ouch.raw", NULL, 0, XBFalse, XBFalse},	/* player lost life */
71 	{
72 	SND_INTRO, "xb_intro.raw", NULL, 0, XBFalse, XBFalse},	/* intro fanfare */
73 	{
74 	SND_APPL, "xb_appl.raw", NULL, 0, XBFalse, XBFalse},	/* applause */
75 	{
76 	SND_APPL2, "xb_app2.raw", NULL, 0, XBFalse, XBFalse},	/* applause */
77 	{
78 	SND_BUTT, "xb_butt.raw", NULL, 0, XBFalse, XBTrue},	/* triggered button */
79 	{
80 	SND_SHOOT, "xb_shoot.raw", NULL, 0, XBFalse, XBFalse},	/* using rem. ctrl. */
81 	{
82 	SND_INVIS, "xb_nvis.raw", NULL, 0, XBFalse, XBFalse},	/* player invisible */
83 	{
84 	SND_INVINC, "xb_nvnc.raw", NULL, 0, XBFalse, XBFalse},	/* player invincible */
85 	{
86 	SND_NEWTELE, "xbntel.raw", NULL, 0, XBFalse, XBTrue},	/* player got telep. */
87 	{
88 	SND_TELE, "xbtele.raw", NULL, 0, XBFalse, XBTrue},	/* player uses tele. */
89 	{
90 	SND_INJ, "xbinj.raw", NULL, 0, XBFalse, XBFalse},	/* player got junkie */
91 	{
92 	SND_MINIBOMB, "xbmbmb.raw", NULL, 0, XBFalse, XBTrue},	/* small bomb expl. */
93 	{
94 	SND_WON, "xb_won.raw", NULL, 0, XBFalse, XBFalse},	/* player won */
95 	{
96 	SND_HAUNT, "xb_haunt.raw", NULL, 0, XBFalse, XBFalse},	/* haunting bomb */
97 	{
98 	SND_SPIRAL, "xb_spir.raw", NULL, 0, XBFalse, XBTrue},	/* spiral shrinking */
99 	{
100 	SND_SPBOMB, "xb_spbmb.raw", NULL, 0, XBFalse, XBTrue},	/* got special bomb */
101 	{
102 	SND_SLIDE, "xbslide.raw", NULL, 0, XBFalse, XBTrue},	/* bomb slide sound */
103 	{
104 	SND_FINALE, "xbfin.raw", NULL, 0, XBFalse, XBFalse},	/* final fanfare */
105 	{
106 	SND_WARN, "xb_warn.raw", NULL, 0, XBFalse, XBFalse},	/* shrink warn sound */
107 	{
108 	SND_STUN, "xb_stun.raw", NULL, 0, XBFalse, XBFalse},	/* player stun sound */
109 	{
110 	SND_WHIRL, "xb_whrl.raw", NULL, 0, XBTrue, XBFalse},	/* intro whirl */
111 	{
112 	SND_COMPOUND, "xb_cmpnd.raw", NULL, 0, XBFalse, XBFalse},	/* compound shrink */
113 	{
114 	SND_TELE1, "xbtele1.raw", NULL, 0, XBFalse, XBTrue},	/* teleport start */
115 	{
116 	SND_TELE2, "xbtele2.raw", NULL, 0, XBFalse, XBTrue},	/* teleport end */
117 	{
118 	SND_HOLY, "xbholy.raw", NULL, 0, XBFalse, XBFalse},	/* holy grail extra */
119 	{
120 	SND_ENCLOAK, "xbcloak.raw", NULL, 0, XBFalse, XBTrue},	/* encloak sound */
121 	{
122 	SND_DECLOAK, "xbdcloak.raw", NULL, 0, XBFalse, XBTrue},	/* decloak sound */
123 	{
124 	SND_FAST, "xbfast.raw", NULL, 0, XBFalse, XBTrue},	/* speed up extra */
125 	{
126 	SND_SLOW, "xbslow.raw", NULL, 0, XBFalse, XBTrue},	/* slow down extra */
127 	{
128 	SND_SLAY, "xbslay.raw", NULL, 0, XBFalse, XBTrue},	/* slay extra */
129 	{
130 	SND_LIFE, "xblife.raw", NULL, 0, XBFalse, XBTrue},	/* extra life */
131 	{
132 	SND_NEWCLOAK, "xbcloakx.raw", NULL, 0, XBFalse, XBTrue},	/* new cloak extra */
133 	{
134 	SND_BOMBMORPH, "xb_bombmorph.raw", NULL, 0, XBFalse, XBTrue},	/* bomb morph */
135 	{
136 	SND_STEP1, "xbstep1.raw", NULL, 0, XBFalse, XBTrue},	/* Backgr. song #1 */
137 	{
138 	SND_STEP2, "xbstep2.raw", NULL, 0, XBFalse, XBTrue},	/* Backgr. song #2 */
139 	{
140 	SND_STEP3, "xbstep3.raw", NULL, 0, XBFalse, XBTrue},	/* Backgr. song #3 */
141 	{
142 	SND_STEP4, "xbstep4.raw", NULL, 0, XBFalse, XBTrue},	/* Backgr. song #4 */
143 	{
144 	SND_STEP5, "xbstep5.raw", NULL, 0, XBFalse, XBTrue},	/* Backgr. song #5 */
145 	{
146 	SND_STEP6, "xbstep6.raw", NULL, 0, XBFalse, XBTrue},	/* Backgr. song #6 */
147 	{
148 	SND_SNG1, "xbsng1.raw", NULL, 0, XBTrue, XBFalse},	/* Backgr. song #1 */
149 	{
150 	SND_SNG2, "xbsng2.raw", NULL, 0, XBTrue, XBFalse},	/* Backgr. song #2 */
151 	{
152 	SND_SNG3, "xbsng3.raw", NULL, 0, XBTrue, XBFalse},	/* Backgr. song #3 */
153 	{
154 	SND_SNG4, "xbsng4.raw", NULL, 0, XBTrue, XBFalse},	/* Backgr. song #4 */
155 	{
156 	SND_SNG5, "xbsng5.raw", NULL, 0, XBTrue, XBFalse},	/* Backgr. song #5 */
157 	{
158 	SND_SNG6, "xbsng6.raw", NULL, 0, XBTrue, XBFalse},	/* Backgr. song #6 */
159 	{
160 	SND_MAX, NULL, NULL, 0}
161 };
162 
163 static Mix_Chunk *sound_chunk[SND_MAX];
164 static int sound_channel[SND_MAX];
165 
166 /* SUBSIZE small so no delay for playing...
167    maybe a better solution should be there, since it is
168    not nice sending all and dont using the buffer.
169 */
170 #define SUBSIZE          128
171 
172 #define SOUND_DEVICE "/dev/dsp"
173 #define SAMPLE_RATE     22050
174 /* SDL_Mixer doesn't allow one channel with
175 left and right, so we need 2 output channels
176 */
177 #define SAMPLE_CHANNELS     2
178 #define SAMPLE_SIZE         AUDIO_U8
179 
180 static int mono_mode = XBFalse;
181 
182 /*
183  * local variables
184  */
185 static XBBool isActive = XBFalse;
186 static XBBool boom = XBFalse;
187 
188 static XBBool soundInitialized = XBFalse;
189 
190 /*
191  * Open Audio
192  */
193 XBBool
SND_Init(const CFGSoundSetup * setup)194 SND_Init (const CFGSoundSetup * setup)
195 {
196 	int i;
197 
198 	assert (NULL != setup);
199 
200 	/* mode selection */
201 	switch (setup->mode) {
202 
203 	case XBSM_Waveout:
204 		isActive = XBTrue;
205 		break;
206 
207 	case XBSM_Beep:
208 		isActive = XBTrue;
209 		return XBTrue;
210 
211 	case XBSM_None:
212 		/* no sound at all */
213 		isActive = XBFalse;
214 		return XBTrue;
215 
216 	default:
217 		return XBFalse;
218 	}
219 
220 	/* Initialize SDL_mixer stuff */
221 
222 	/* init audio */
223 	if (Mix_OpenAudio (SAMPLE_RATE, SAMPLE_SIZE, SAMPLE_CHANNELS, SUBSIZE) != 0) {
224 		fprintf (stderr, "Error: Couldn't initializate audio\n"
225 				 "Possible reason: %s\n", Mix_GetError ());
226 		return XBFalse;
227 	}
228 
229 	/* Add more mixing channels. The default of MIX_CHANNELS (8) is
230 	 * not enough when there is many bombs. Is the new number too big
231 	 * for slow machines? */
232 	Mix_AllocateChannels(20);
233 
234 	/* reset sound stuff */
235 	for (i = 0; i < SND_MAX; i++) {
236 		sound_chunk[i] = NULL;
237 		sound_channel[i] = -1;
238 	}
239 
240 	soundInitialized = XBTrue;
241 
242 	return XBTrue;
243 
244 }								/* SND_Init */
245 
246 /*
247  * Stop Audio
248  */
249 XBBool
SND_Stop(SND_Id id)250 SND_Stop (SND_Id id)
251 {
252 	if (soundInitialized) {
253 		if (id == STOP_ALL_SOUNDS) {
254 			Mix_HaltChannel (-1);
255 		}
256 		else					// stop specific sound
257 		{
258 			if (sound_channel[id] != -1)
259 				Mix_HaltChannel (sound_channel[id]);
260 			sound_channel[id] = -1;
261 		}
262 	}
263 	return XBTrue;
264 }								/* SND_Stop */
265 
266 /*
267  * Play sound and set right position,
268  */
269 XBBool
SND_Play(SND_Id id,int position)270 SND_Play (SND_Id id, int position)
271 {
272 	if (soundInitialized) {
273 
274 		// do a stereo effect
275 		int col1 = 255 - ((position * 255) / MAX_SOUND_POSITION);
276 		int col2 = (position * 255) / MAX_SOUND_POSITION;
277 
278 		/* When there is too many bombs on the screen,
279 		 * this function will fails. */
280 		sound_channel[id] = Mix_PlayChannel (-1, sound_chunk[id], 0);
281 		if (sound_channel[id] != -1) {
282 			/* Panning must be after the channel opened,
283 			 * since we dont know the channel before */
284 			if (!Mix_SetPanning (sound_channel[id], col2, col1)) {
285 				fprintf (stderr, "Mix_SetPanning(%d, %d, %d) failed!\n", sound_channel[id], col1, col2);
286 				fprintf (stderr, "Reason: [%s].\n", Mix_GetError ());
287 			}
288 		}
289 	}
290 	else {
291 		/* beep at next frame */
292 		if (SND_MINIBOMB == id || SND_EXPL == id) {
293 			boom = XBTrue;
294 		}
295 	}
296 	return XBTrue;
297 }								/* SND_Play */
298 
299 /*
300  * beep once
301  */
302 void
SND_Beep(void)303 SND_Beep (void)
304 {
305 	/* TODO
306 	   XBell (dpy, 80);
307 	 */
308 }								/* SND_Beep */
309 
310 /*
311  * Load sound
312  */
313 XBBool
SND_Load(SND_Id id)314 SND_Load (SND_Id id)
315 {
316 	if (soundInitialized) {
317 		/* First load RAW into memory */
318 		char fname[1000];
319 		int i, f;
320 		static char *path_list[3] = {
321 			NULL,
322 			GAME_DATADIR "/",
323 			".",
324 		};
325 
326 		/* check environment for xblast search path */
327 		path_list[0] = getenv ("XBLASTDIR");
328 
329 		for (i = 0; i < 3; i++) {
330 			if (path_list[i] != NULL) {
331 				sprintf (fname, "%s/%s/%s", path_list[i], "sounds", sound_name[id].name);
332 				if ((f = open (fname, O_RDONLY)) >= 0) {
333 					int sound_size;
334 					uint8_t *sb, *sb1;
335 					struct stat snd_stat;
336 
337 #ifdef DEBUG
338 					fprintf (stderr, "Opened file \"%s\".\n", fname);
339 #endif
340 					(void)fstat (f, &snd_stat);
341 					sound_size = snd_stat.st_size / sizeof (uint8_t);
342 					if (sound_name[id].samples != NULL) {
343 						free (sound_name[id].samples);
344 						sound_name[id].samples = NULL;
345 						sound_name[id].length = 0;
346 					}
347 
348 					if ((sb = malloc (sound_size * sizeof (uint8_t))) == NULL) {
349 						close (f);
350 						return (-1);
351 					}
352 					else {
353 						read (f, sb, sound_size * sizeof (uint8_t));
354 						close (f);
355 #if defined(SERVER_STATISTICS)
356 						total_loaded++;
357 #endif
358 						/* make sound Stereo although mono... blame SDL_mixer
359 						   same problem as the 2 channels issue
360 						 */
361 						if (mono_mode != XBTrue && sound_name[id].mono == XBTrue) {
362 							if ((sb1 = malloc (2 * sound_size * sizeof (uint8_t))) == NULL) {
363 								free (sb);
364 								return (-1);
365 							}
366 							for (i = 0; i < sound_size; i++) {
367 								sb1[2*i] = sb1[2*i + 1] = sb[i];
368 
369 							}
370 							/* we free sb afterwards so we need
371 							   to pass sb1 to sb */
372 							free (sb);
373 							sb = sb1;
374 							sound_size *= 2;
375 						}
376 						sound_name[id].samples = sb;
377 						sound_name[id].length = sound_size;
378 						/*
379 						 * convert stereo samples to mono if running in mono mode
380 						 */
381 
382 						if (mono_mode == XBTrue && sound_name[id].mono == XBFalse) {
383 							int i;
384 							uint8_t *m, *s;
385 							s16 sum;
386 
387 							m = s = sound_name[id].samples;
388 
389 							sound_name[id].length >>= 1;
390 							for (i = 0; i < sound_name[id].length; i++) {
391 								sum = *s + *(s + 1);
392 								*m++ = sum >> 1;
393 								s += 2;
394 							}
395 						}
396 						if (!(sound_chunk[id] = Mix_QuickLoad_RAW (sb, sound_name[id].length)))
397 							fprintf (stderr, "Warning: Could not open RAW from memory: %s\n",
398 									 Mix_GetError ());
399 
400 						break;
401 					}
402 				}
403 				else {
404 					fprintf (stderr, "Couldnt open file \"%s\".\n", fname);
405 				}
406 			}
407 		}
408 
409 	}
410 	return XBTrue;
411 }								/* SND_Load */
412 
413 /*
414  *
415  */
416 XBBool
SND_Unload(SND_Id id)417 SND_Unload (SND_Id id)
418 {
419 	if (soundInitialized) {
420 		Mix_FreeChunk (sound_chunk[id]);
421 	}
422 	return XBTrue;
423 }								/* SND_Unload */
424 
425 /*
426  *
427  */
428 void
SND_Flush(void)429 SND_Flush (void)
430 {
431 	if (isActive && boom) {
432 		boom = XBFalse;
433 	}
434 }								/* SND_Flush */
435 
436 /*
437  *
438  */
439 void
SND_Finish(void)440 SND_Finish (void)
441 {
442 	Mix_CloseAudio ();
443 
444 	soundInitialized = XBFalse;
445 }								/* SND_Finish */
446