e4340ef0d6a0e0bab68f8e9384c459db13a91caa
[neverball] / share / audio.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14
15 #include <SDL.h>
16
17 #include <vorbis/codec.h>
18 #include <vorbis/vorbisfile.h>
19
20 #include <string.h>
21 #include <stdlib.h>
22
23 #include "text.h"
24 #include "config.h"
25 #include "audio.h"
26 #include "common.h"
27
28 /*---------------------------------------------------------------------------*/
29
30 #define AUDIO_RATE 44100
31 #define AUDIO_CHAN 2
32
33 struct voice
34 {
35     OggVorbis_File  vf;
36     float          amp;
37     float         damp;
38     int           chan;
39     int           play;
40     int           loop;
41     char         *name;
42     struct voice *next;
43 };
44
45 static int   audio_state = 0;
46 static float sound_vol   = 1.0f;
47 static float music_vol   = 1.0f;
48
49 static SDL_AudioSpec spec;
50
51 static struct voice *music  = NULL;
52 static struct voice *queue  = NULL;
53 static struct voice *voices = NULL;
54 static short        *buffer = NULL;
55
56 /*---------------------------------------------------------------------------*/
57
58 #define MIX(d, s) {                           \
59         int n = (int) (d) + (int) (s);        \
60         if      (n >  32767) (d) =  32767;    \
61         else if (n < -32768) (d) = -32768;    \
62         else                 (d) = (short) n; \
63     }
64
65 static int voice_step(struct voice *V, float volume, Uint8 *stream, int length)
66 {
67 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
68     int order = 1;
69 #else
70     int order = 0;
71 #endif
72
73     short *obuf = (short *) stream;
74     char  *ibuf = (char  *) buffer;
75
76     int i, b = 0, n = 1, c = 0, r = 0;
77
78     /* Compute the total request size for the current stream. */
79
80     if (V->chan == 1) r = length / 2;
81     if (V->chan == 2) r = length    ;
82
83     /* While data is coming in and data is still needed... */
84
85     while (n > 0 && r > 0)
86     {
87         /* Read audio from the stream. */
88
89         if ((n = (int) ov_read(&V->vf, ibuf, r, order, 2, 1, &b)) > 0)
90         {
91             /* Mix mono audio. */
92
93             if (V->chan == 1)
94                 for (i = 0; i < n / 2; i += 1)
95                 {
96                     short M = (short) (V->amp * volume * buffer[i]);
97
98                     MIX(obuf[c], M); c++;
99                     MIX(obuf[c], M); c++;
100
101                     V->amp += V->damp;
102
103                     if (V->amp < 0.0) V->amp = 0.0;
104                     if (V->amp > 1.0) V->amp = 1.0;
105                 }
106
107             /* Mix stereo audio. */
108
109             if (V->chan == 2)
110                 for (i = 0; i < n / 2; i += 2)
111                 {
112                     short L = (short) (V->amp * volume * buffer[i + 0]);
113                     short R = (short) (V->amp * volume * buffer[i + 1]);
114
115                     MIX(obuf[c], L); c++;
116                     MIX(obuf[c], R); c++;
117
118                     V->amp += V->damp;
119
120                     if (V->amp < 0.0) V->amp = 0.0;
121                     if (V->amp > 1.0) V->amp = 1.0;
122                 }
123
124             r -= n;
125         }
126         else
127         {
128             /* We're at EOF.  Loop or end the voice. */
129
130             if (V->loop)
131             {
132                 ov_raw_seek(&V->vf, 0);
133                 n = 1;
134             }
135             else return 1;
136         }
137     }
138     return 0;
139 }
140
141 static struct voice *voice_init(const char *filename, float a)
142 {
143     struct voice *V;
144     FILE        *fp;
145
146     /* Allocate and initialize a new voice structure. */
147
148     if ((V = (struct voice *) calloc(1, sizeof (struct voice))))
149     {
150         /* Note the name. */
151
152         V->name = strdup(filename);
153
154         /* Attempt to open the named Ogg stream. */
155
156         if ((fp = fopen(config_data(filename), FMODE_RB)))
157         {
158             if (ov_open(fp, &V->vf, NULL, 0) == 0)
159             {
160                 vorbis_info *info = ov_info(&V->vf, -1);
161
162                 /* On success, configure the voice. */
163
164                 V->amp  = a;
165                 V->damp = 0;
166                 V->chan = info->channels;
167                 V->play = 1;
168                 V->loop = 0;
169
170                 if (V->amp > 1.0) V->amp = 1.0;
171                 if (V->amp < 0.0) V->amp = 0.0;
172
173                 /* The file will be closed when the Ogg is cleared. */
174             }
175             else fclose(fp);
176         }
177     }
178     return V;
179 }
180
181 static void voice_free(struct voice *V)
182 {
183     ov_clear(&V->vf);
184
185     free(V->name);
186     free(V);
187 }
188
189 /*---------------------------------------------------------------------------*/
190
191 static void audio_step(void *data, Uint8 *stream, int length)
192 {
193     struct voice *V = voices;
194     struct voice *P = NULL;
195
196     /* Zero the output buffer. */
197
198     memset(stream, 0, length);
199
200     /* Mix the background music. */
201
202     if (music)
203     {
204         voice_step(music, music_vol, stream, length);
205
206         /* If the track has faded out, move to a queued track. */
207
208         if (music->amp <= 0.0f && music->damp < 0.0f && queue)
209         {
210             voice_free(music);
211             music = queue;
212             queue = NULL;
213         }
214     }
215
216     /* Iterate over all active voices. */
217
218     while (V)
219     {
220         /* Mix this voice. */
221
222         if (V->play && voice_step(V, sound_vol, stream, length))
223         {
224             /* Delete a finished voice... */
225
226             struct voice *T = V;
227
228             if (P)
229                 V = P->next = V->next;
230             else
231                 V = voices  = V->next;
232
233             voice_free(T);
234         }
235         else
236         {
237             /* ... or continue to the next. */
238
239             P = V;
240             V = V->next;
241         }
242     }
243 }
244
245 /*---------------------------------------------------------------------------*/
246
247 void audio_init(void)
248 {
249     audio_state = 0;
250
251     /* Configure the audio. */
252
253     spec.format   = AUDIO_S16SYS;
254     spec.channels = AUDIO_CHAN;
255     spec.samples  = config_get_d(CONFIG_AUDIO_BUFF);
256     spec.freq     = AUDIO_RATE;
257     spec.callback = audio_step;
258
259     /* Allocate an input buffer. */
260
261     if ((buffer = (short *) malloc(spec.samples * 4)))
262     {
263         /* Start the audio thread. */
264
265         if (SDL_OpenAudio(&spec, NULL) == 0)
266         {
267             audio_state = 1;
268             SDL_PauseAudio(0);
269         }
270         else fprintf(stderr, "%s\n", SDL_GetError());
271     }
272
273     /* Set the initial volumes. */
274
275     audio_volume(config_get_d(CONFIG_SOUND_VOLUME),
276                  config_get_d(CONFIG_MUSIC_VOLUME));
277 }
278
279 void audio_free(void)
280 {
281     /* Halt the audio thread. */
282
283     SDL_CloseAudio();
284
285     /* Release the input buffer. */
286
287     free(buffer);
288
289     /* Ogg streams and voice structure remain open to allow quality setting. */
290 }
291
292 void audio_play(const char *filename, float a)
293 {
294     if (audio_state)
295     {
296         struct voice *V;
297
298         /* If we're already playing this sound, preempt the running copy. */
299
300         for (V = voices; V; V = V->next)
301             if (strcmp(V->name, filename) == 0)
302             {
303                 ov_raw_seek(&V->vf, 0);
304
305                 V->amp = a;
306
307                 if (V->amp > 1.0) V->amp = 1.0;
308                 if (V->amp < 0.0) V->amp = 0.0;
309
310                 return;
311             }
312
313         /* Create a new voice structure. */
314
315         V = voice_init(filename, a);
316
317         /* Add it to the list of sounding voices. */
318
319         SDL_LockAudio();
320         {
321             V->next = voices;
322             voices  = V;
323         }
324         SDL_UnlockAudio();
325     }
326 }
327
328 /*---------------------------------------------------------------------------*/
329
330 void audio_music_play(const char *filename)
331 {
332     if (audio_state)
333     {
334         audio_music_stop();
335
336         SDL_LockAudio();
337         {
338             if ((music = voice_init(filename, 0.0f)))
339             {
340                 music->loop = 1;
341             }
342         }
343         SDL_UnlockAudio();
344     }
345 }
346
347 void audio_music_queue(const char *filename, float t)
348 {
349     if (audio_state)
350     {
351         SDL_LockAudio();
352         {
353             if ((queue = voice_init(filename, 0.0f)))
354             {
355                 queue->loop = 1;
356
357                 if (t > 0.0f)
358                     queue->damp = +1.0f / (AUDIO_RATE * t);
359             }
360         }
361         SDL_UnlockAudio();
362     }
363 }
364
365 void audio_music_stop(void)
366 {
367     if (audio_state)
368     {
369         SDL_LockAudio();
370         {
371             if (music)
372             {
373                 voice_free(music);
374             }
375             music = NULL;
376         }
377         SDL_UnlockAudio();
378     }
379 }
380
381 /*---------------------------------------------------------------------------*/
382
383 void audio_music_fade_out(float t)
384 {
385     SDL_LockAudio();
386     {
387         if (music) music->damp = -1.0f / (AUDIO_RATE * t);
388     }
389     SDL_UnlockAudio();
390 }
391
392 void audio_music_fade_in(float t)
393 {
394     SDL_LockAudio();
395     {
396         if (music) music->damp = +1.0f / (AUDIO_RATE * t);
397     }
398     SDL_UnlockAudio();
399 }
400
401 void audio_music_fade_to(float t, const char *filename)
402 {
403     if (music)
404     {
405         if (strcmp(filename, music->name) != 0)
406         {
407             audio_music_fade_out(t);
408             audio_music_queue(filename, t);
409         }
410         else
411         {
412             /*
413              * We're fading to the current track.  Chances are,
414              * whatever track is still in the queue, we don't want to
415              * hear it anymore.
416              */
417
418             if (queue)
419             {
420                 voice_free(queue);
421                 queue = NULL;
422             }
423
424             audio_music_fade_in(t);
425         }
426     }
427     else
428     {
429         audio_music_play(filename);
430         audio_music_fade_in(t);
431     }
432 }
433
434 void audio_volume(int s, int m)
435 {
436     sound_vol = (float) s / 10.0f;
437     music_vol = (float) m / 10.0f;
438 }
439
440 /*---------------------------------------------------------------------------*/