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