1 /*
2 * nazghul - an old-school RPG engine
3 * Copyright (C) 2002, 2003, 2004 Gordon McNutt
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 * Suite 330, Boston, MA 02111-1307 USA
18 *
19 *----------------------------------------------------------------------------
20 *
21 * This file implements the sound-playing library for the nazghul engine. It
22 * wraps the SDL sound library and provides a simple mixer. The code which
23 * interacts with the SDL sound library is borrowed straight from the SDL
24 * examples, which are in the public domain (see www.libsdl.org).
25 *
26 * Gordon McNutt
27 * gmcnutt@users.sourceforge.net
28 */
29
30 #include "sound.h"
31 #include "debug.h"
32 #include "file.h"
33 #include "cfg.h"
34
35 #include <assert.h>
36 #include <SDL.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <SDL_mixer.h>
40
41 #define NUM_SOUNDS 64
42 #define SOUND_MAGIC 50043
43
44 #define IS_SOUND(ptr) ((ptr)->magic == SOUND_MAGIC)
45
46
47 struct sound {
48 int magic;
49 char *tag;
50 Mix_Chunk *data;
51 int channel;
52 //char *fname;
53 int refcount;
54 bool ambient;
55 int volume;
56 int nextVolume;
57 };
58
59 sound_t *sound_reverse_lookup[NUM_SOUNDS];
60
61 int SOUND_MAX_VOLUME = SDL_MIX_MAXVOLUME;
62
63 /* This indicates whether or not the player has turned sound on or off. */
64 static int sound_enabled = 1;
65
66 /* This is 1 iff SDL_Audio() is initialized and ready for use. */
67 static int sound_activated = 0;
68
config_to_soundvolume(const char * config)69 static int config_to_soundvolume(const char* config)
70 {
71 //our input percentages have nicely unique initial characters
72 const char* comp="O2571";
73 int i;
74 for (i=0;i<5;i++)
75 {
76 if (config[0] == comp[i])
77 {
78 break;
79 }
80 }
81 if (i>4)
82 {
83 i=0;
84 }
85 return i;
86 }
87
sound_unref(sound_t * sound)88 static void sound_unref(sound_t *sound)
89 {
90 assert(sound->refcount > 0);
91
92 sound->refcount--;
93 if (! sound->refcount)
94 {
95 // make sure it isnt being played
96 if (sound->channel >= 0)
97 {
98 // it *shouldnt* be being played, so lets die if it is.
99 assert(false);
100 Mix_HaltChannel(sound->channel);
101 }
102 free(sound->tag);
103 Mix_FreeChunk(sound->data);
104 free(sound);
105 }
106
107 }
108
sound_play(sound_t * sound,int volume,bool ambient)109 void sound_play(sound_t *sound, int volume, bool ambient)
110 {
111
112
113 if (!sound_enabled || !sound_activated) {
114 return;
115 }
116
117 if (NULL_SOUND == sound)
118 {
119 return;
120 }
121 assert(IS_SOUND(sound));
122
123 if (sound->channel < 0)
124 {
125 if (ambient)
126 {
127 sound->channel = Mix_PlayChannel(-1, sound->data,-1);
128 Mix_VolumeChunk(sound->data,volume);
129 sound->volume = volume;
130 sound->ambient = true;
131 sound->nextVolume = volume;
132 }
133 else
134 {
135 sound->channel = Mix_PlayChannel(-1, sound->data,0);
136 Mix_VolumeChunk(sound->data,volume);
137 sound->volume = volume;
138 sound->ambient = false;
139 sound->nextVolume = 0;
140 }
141 sound->refcount++;
142 sound_reverse_lookup[sound->channel] = sound;
143 }
144 else if (ambient)
145 {
146 if (sound->volume < volume)
147 {
148 Mix_VolumeChunk(sound->data,volume);
149 sound->volume = volume;
150 }
151 if (sound->nextVolume < volume)
152 {
153 sound->nextVolume = volume;
154 }
155 }
156 else
157 {
158 if (sound->volume < volume)
159 {
160 sound->volume = volume;
161 Mix_VolumeChunk(sound->data,volume);
162 }
163 }
164 }
165
sound_played(int channel)166 void sound_played(int channel)
167 {
168 sound_t *sound = sound_reverse_lookup[channel];
169 sound->channel=-1;
170 sound_reverse_lookup[channel] = NULL;
171 sound->volume=0;
172 sound_unref(sound);
173 }
174
sound_flush_ambient()175 void sound_flush_ambient()
176 {
177 unsigned int i;
178 sound_t *active;
179
180 for (i = 0; i < NUM_SOUNDS; ++i)
181 {
182 active = sound_reverse_lookup[i];
183
184 // Skip idle or oneoff entries
185 if (! active || !active->ambient)
186 continue;
187
188 active->volume = active->nextVolume;
189 active->nextVolume = 0;
190 Mix_VolumeChunk(active->data,active->volume);
191 }
192 }
193
sound_del(sound_t * sound)194 void sound_del(sound_t *sound)
195 {
196 if (sound != NULL_SOUND)
197 sound_unref(sound);
198 }
199
sound_new(const char * tag,const char * file)200 sound_t *sound_new(const char *tag, const char *file)
201 {
202 sound_t *sound;
203 char *fn;
204 Mix_Chunk *wave = NULL;
205
206 if (!sound_activated) {
207 return NULL_SOUND;
208 }
209
210 if (file == NULL)
211 return NULL_SOUND;
212
213 fn = file_mkpath(cfg_get("include-dirname"), file);
214 /* Load the sound file and convert it to 16-bit stereo at 22kHz */
215
216 wave = Mix_LoadWAV(fn?fn:file);
217 if (!wave)
218 {
219 warn("Mix_LoadWav:%s:%s", fn?fn:file, SDL_GetError());
220 free(fn);
221 return NULL_SOUND;
222 }
223 free(fn);
224
225 /* Allocate the sound structure */
226 sound = (sound_t *)calloc(1, sizeof(*sound));
227 assert(sound);
228
229 /* Copy the tag */
230 sound->tag = strdup(tag);
231 assert(sound->tag);
232
233 /* Initialized defaults */
234 sound->magic = SOUND_MAGIC;
235 sound->refcount = 1;
236 sound->channel = -1;
237 sound->data = wave;
238
239 return sound;
240 }
241
sound_init(void)242 int sound_init(void)
243 {
244
245 if (sound_activated)
246 return 0;
247
248 /* Init the active sound list */
249
250 /* Set 16-bit stereo audio at 22Khz */
251 /* Open the audio device and start playing sound! */
252 if (Mix_OpenAudio(22050,AUDIO_S16,2,1024) < 0) {
253 warn("Mix_OpenAudio: %s", SDL_GetError());
254 return -1;
255 }
256
257 /* Create the mutex */
258
259
260 atexit(Mix_CloseAudio);
261
262 sound_activated = 1;
263
264 for (int i=0;i<NUM_SOUNDS;i++)
265 {
266 sound_reverse_lookup[i]=NULL;
267 }
268 Mix_AllocateChannels(NUM_SOUNDS);
269 Mix_ChannelFinished(sound_played);
270
271 return 0;
272 }
273
sound_exit(void)274 void sound_exit(void)
275 {
276 if (! sound_activated)
277 return;
278
279 sound_activated = 0;
280
281 /* Does this invoke the mixer callback on all active sounds? If not
282 * then how will I unref active sounds? */
283 Mix_CloseAudio();
284 }
285
sound_haltall()286 void sound_haltall()
287 {
288 //Mix_HaltChannel(-1);
289 }
290
sound_get_tag(sound_t * sound)291 const char *sound_get_tag(sound_t *sound)
292 {
293 return sound->tag;
294 }
295
sound_on(void)296 void sound_on(void)
297 {
298 sound_enabled = 1;
299 }
300
sound_off(void)301 void sound_off(void)
302 {
303 sound_enabled = 0;
304 }
305
sound_is_activated(void)306 int sound_is_activated(void)
307 {
308 return sound_activated;
309 }
310
311 //////////////////////////////////////////////////////////////////////////////
312 // Music API
313
314 Mix_Music *music_track;
315 Mix_Music *prev_track = NULL;
316
317 int music_volume=0;
318 bool music_needtrack=false;
319
320 //setting must be one of Off 25% 50% 75% 100%
set_music_volume(const char * setting)321 void set_music_volume(const char *setting)
322 {
323 music_volume = config_to_soundvolume(setting);
324 fprintf(stderr, "vol: %s\n", setting);
325 fprintf(stderr, "vol: %d\n", music_volume);
326 int mvm = MIX_MAX_VOLUME * music_volume / 4;
327 fprintf(stderr, "vol: %d\n", mvm);
328 Mix_VolumeMusic(mvm);
329 fprintf(stderr, "vol?: %d\n", Mix_VolumeMusic(-1));
330 }
331
music_load_track(const char * file)332 void music_load_track(const char *file)
333 {
334 // nasty hack:
335 // SDL mixer seems to have some rather odd timing requirements.
336 // For example, I cant stop a track and immediately dispose of it
337 // so instead I will stop this track, and dispose of one I stopped earlier
338 if (prev_track)
339 {
340 Mix_FreeMusic(prev_track);
341 prev_track = NULL;
342 }
343 if (music_volume==0)
344 {
345 music_needtrack=false;
346 return;
347 }
348 if (Mix_PlayingMusic())
349 {
350 Mix_HaltMusic();
351 prev_track = music_track;
352 }
353 char *fn = NULL;
354 if (!file_exists(file))
355 {
356 fn = file_mkpath(cfg_get("include-dirname"), file);
357 }
358 music_track=Mix_LoadMUS(fn?fn:file);
359 if (music_track)
360 {
361 Mix_PlayMusic(music_track,1);
362 }
363 else
364 {
365 warn("Mix_LoadMusic:%s:%s\n", fn?fn:file, SDL_GetError());
366 }
367 music_needtrack=false;
368 free(fn);
369 }
370
music_finished()371 void music_finished()
372 {
373 music_needtrack=true;
374 }
375
376 // SDL_Mixer has a music-has-finished hook, but you arent allowed to do anything
377 // involving SDL_Mixer in that thread, so I am reduced to polling...
music_need_track()378 bool music_need_track()
379 {
380 return music_needtrack;
381 }
382
music_init()383 void music_init()
384 {
385 Mix_HookMusicFinished(music_finished);
386 }
387
sound_enable(int enable)388 void sound_enable(int enable)
389 {
390 if (enable) {
391 sound_on();
392 } else {
393 sound_off();
394 }
395 }
396