Applied proposed audio patch
[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
27 /*---------------------------------------------------------------------------*/
28
29 #define AUDIO_RATE 44100
30 #define AUDIO_CHAN 2
31
32 struct voice
33 {
34     OggVorbis_File  vf;
35     float          amp;
36     float         damp;
37     int           chan;
38     int           play;
39     int           loop;
40     char         *name;
41     struct voice *next;
42 };
43
44 static float sound_vol   = 1.0f;
45 static float music_vol   = 1.0f;
46
47 static SDL_AudioSpec spec;
48
49 static struct voice *music  = NULL;
50 static struct voice *queue  = NULL;
51 static struct voice *voices = NULL;
52 static short        *buffer = NULL;
53
54 /*---------------------------------------------------------------------------*/
55
56 #define MIX(d, s) {                           \
57         int n = (int) (d) + (int) (s);        \
58         if      (n >  32767) (d) =  32767;    \
59         else if (n < -32768) (d) = -32768;    \
60         else                 (d) = (short) n; \
61     }
62
63 static int voice_step(struct voice *V, float volume, Uint8 *stream, int length)
64 {
65 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
66     int order = 1;
67 #else
68     int order = 0;
69 #endif
70
71     short *obuf = (short *) stream;
72     char  *ibuf = (char  *) buffer;
73
74     int i, b = 0, n = 1, c = 0, r = 0;
75
76     /* Compute the total request size for the current stream. */
77
78     if (V->chan == 1) r = length / 2;
79     if (V->chan == 2) r = length    ;
80
81     /* While data is coming in and data is still needed... */
82
83     while (n > 0 && r > 0)
84     {
85         /* Read audio from the stream. */
86
87         if ((n = (int) ov_read(&V->vf, ibuf, r, order, 2, 1, &b)) > 0)
88         {
89             /* Mix mono audio. */
90
91             if (V->chan == 1)
92                 for (i = 0; i < n / 2; i += 1)
93                 {
94                     short M = (short) (V->amp * volume * buffer[i]);
95
96                     MIX(obuf[c], M); c++;
97                     MIX(obuf[c], M); c++;
98
99                     V->amp += V->damp;
100
101                     if (V->amp < 0.0) V->amp = 0.0;
102                     if (V->amp > 1.0) V->amp = 1.0;
103                 }
104
105             /* Mix stereo audio. */
106
107             if (V->chan == 2)
108                 for (i = 0; i < n / 2; i += 2)
109                 {
110                     short L = (short) (V->amp * volume * buffer[i + 0]);
111                     short R = (short) (V->amp * volume * buffer[i + 1]);
112
113                     MIX(obuf[c], L); c++;
114                     MIX(obuf[c], R); c++;
115
116                     V->amp += V->damp;
117
118                     if (V->amp < 0.0) V->amp = 0.0;
119                     if (V->amp > 1.0) V->amp = 1.0;
120                 }
121
122             r -= n;
123         }
124         else
125         {
126             /* We're at EOF.  Loop or end the voice. */
127
128             if (V->loop)
129             {
130                 ov_raw_seek(&V->vf, 0);
131                 n = 1;
132             }
133             else return 1;
134         }
135     }
136     return 0;
137 }
138
139 static struct voice *voice_init(const char *filename, float a)
140 {
141     struct voice *V;
142     FILE        *fp;
143
144     /* Allocate and initialize a new voice structure. */
145
146     if ((V = (struct voice *) calloc(1, sizeof (struct voice))))
147     {
148         /* Note the name. */
149
150         V->name = (char *) malloc(strlen(filename) + 1);
151
152         strcpy(V->name, 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
250     /* Configure the audio. */
251
252     spec.format   = AUDIO_S16SYS;
253     spec.channels = AUDIO_CHAN;
254     spec.samples  = config_get_d(CONFIG_AUDIO_BUFF);
255     spec.freq     = AUDIO_RATE;
256     spec.callback = audio_step;
257
258     /* Allocate an input buffer. */
259
260     if ((buffer = (short *) malloc(spec.samples * 4)))
261     {
262         /* Start the audio thread. */
263
264         if (SDL_OpenAudio(&spec, NULL) == 0)
265         {
266             SDL_PauseAudio(0);
267         }
268         else fprintf(stderr, "%s\n", SDL_GetError());
269     }
270
271     /* Set the initial volumes. */
272
273     audio_volume(config_get_d(CONFIG_SOUND_VOLUME),
274                  config_get_d(CONFIG_MUSIC_VOLUME));
275 }
276
277 void audio_free(void)
278 {
279     /* Halt the audio thread. */
280
281     SDL_CloseAudio();
282
283     /* Release the input buffer. */
284
285     free(buffer);
286
287     /* Ogg streams and voice structure remain open to allow quality setting. */
288 }
289
290 void audio_play(const char *filename, float a)
291 {
292     struct voice *V;
293
294     /* If we're already playing this sound, preempt the running copy. */
295
296     for (V = voices; V; V = V->next)
297         if (strcmp(V->name, filename) == 0)
298         {
299             ov_raw_seek(&V->vf, 0);
300
301             V->amp = a;
302
303             if (V->amp > 1.0) V->amp = 1.0;
304             if (V->amp < 0.0) V->amp = 0.0;
305
306              return;
307         }
308
309     /* Create a new voice structure. */
310
311     V = voice_init(filename, a);
312
313     /* Add it to the list of sounding voices. */
314
315     SDL_LockAudio();
316     {
317         V->next = voices;
318         voices  = V;
319     }
320     SDL_UnlockAudio();
321 }
322
323 /*---------------------------------------------------------------------------*/
324
325 void audio_music_play(const char *filename)
326 {
327     audio_music_stop();
328
329     SDL_LockAudio();
330     {
331         if ((music = voice_init(filename, 0.0f)))
332         {
333             music->loop = 1;
334         }
335     }
336     SDL_UnlockAudio();
337 }
338
339 void audio_music_queue(const char *filename)
340 {
341     SDL_LockAudio();
342     {
343         if ((queue = voice_init(filename, 0.0f)))
344         {
345             queue->loop = 1;
346         }
347     }
348     SDL_UnlockAudio();
349 }
350
351 void audio_music_stop(void)
352 {
353     SDL_LockAudio();
354     {
355         if (music)
356         {
357             voice_free(music);
358         }
359         music = NULL;
360     }
361     SDL_UnlockAudio();
362 }
363
364 /*---------------------------------------------------------------------------*/
365
366 void audio_music_fade_out(float t)
367 {
368     SDL_LockAudio();
369     {
370         if (music) music->damp = -1.0f / (AUDIO_RATE * t);
371     }
372     SDL_UnlockAudio();
373 }
374
375 void audio_music_fade_in(float t)
376 {
377     SDL_LockAudio();
378     {
379         if (music) music->damp = +1.0f / (AUDIO_RATE * t);
380     }
381     SDL_UnlockAudio();
382 }
383
384 void audio_music_fade_to(float t, const char *filename)
385 {
386     if (music)
387     {
388         if (strcmp(filename, music->name))
389         {
390             audio_music_queue(filename);
391             audio_music_fade_out(t);
392
393             if (queue) queue->damp = +1.0f / (AUDIO_RATE * t);
394         }
395         else audio_music_fade_in(t);
396     }
397     else
398     {
399         audio_music_play(filename);
400         audio_music_fade_in(t);
401     }
402 }
403
404 void audio_volume(int s, int m)
405 {
406     sound_vol = (float) s / 10.0f;
407     music_vol = (float) m / 10.0f;
408 }
409
410 /*---------------------------------------------------------------------------*/