Moved src/audio to more appropriately named src/av
[2oom.git] / src / module / sdl.c
blob270a8e912b7f42245ce0ecb55bdc1627bb7a919d
1 /* 2ooM: The Master of Orion II Reverse Engineering Project
2 * Copyright (C) 2006 Nick Bowler
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * $HeadURL$
19 * $Date$
20 * $Revision$
21 * $Author$
24 #define SDL_MODULE "SDL"
25 #define SDL_MODULE_VERSION "0.1.0"
27 #include <stdlib.h>
28 #include <limits.h>
29 #include <SDL.h>
31 #include "../av/avfile.h"
32 #include "../plugin/plugin.h"
33 #include "../sys/log.h"
35 #define init sdl_LTX_init
37 static struct log_context logger;
38 static struct av_accessor av;
40 static struct SDL_AudioSpec sdlfmt;
41 static struct audio_format fmt;
43 struct audio_data {
44 int flags;
45 int playing;
46 int delete;
47 int aid;
48 Uint8 *data;
49 size_t datasz;
52 static struct channel {
53 double volume; /**< Current channel volume. */
55 double voltarget; /**< Target channel volume. */
56 double fadeamt; /**< Amount to adjust volume each sample. */
58 int chanid;
59 struct audio_data *data;
60 int nloops;
61 } *channels;
62 static int nchannels;
64 static unsigned int opensounds;
66 /* Sets a channel to fade to the target volume over the course of nsec seconds.
68 static void setfade(int c, double target, double nsec)
70 SDL_LockAudio();
72 if (target == channels[c].volume)
73 return (void) (channels[c].fadeamt = 0);
75 channels[c].voltarget = target;
76 channels[c].fadeamt = (target-channels[c].volume)/(nsec*sdlfmt.freq);
78 SDL_UnlockAudio();
81 /* Delete opened audio data */
82 static void data_delete(struct audio_data *data)
84 free(data->data);
85 free(data);
86 opensounds--;
89 /* Stop a channel */
90 static inline void stop(int c)
92 channels[c].data->playing--;
93 if (channels[c].data->delete && !channels[c].data->playing)
94 data_delete(channels[c].data);
95 channels[c].data = NULL;
98 /* Apply one sample's worth of fading to channel c. */
99 static inline void fade(int c)
101 if (channels[c].fadeamt == 0)
102 return;
103 else if (channels[c].fadeamt > 0) {
104 channels[c].volume += channels[c].fadeamt;
105 if (channels[c].volume > channels[c].voltarget) {
106 channels[c].volume = channels[c].voltarget;
107 channels[c].fadeamt = 0;
109 } else if (channels[c].fadeamt < 0) {
110 channels[c].volume += channels[c].fadeamt;
111 if (channels[c].volume < channels[c].voltarget) {
112 if (channels[c].voltarget == 0)
113 stop(c);
115 channels[c].volume = channels[c].voltarget;
116 channels[c].fadeamt = 0;
121 static int setchannels(int numchannels)
123 int i;
124 struct channel *new;
126 SDL_LockAudio();
127 if (!(new = realloc(channels, numchannels * sizeof *channels))) {
128 SDL_UnlockAudio();
129 return -1;
132 channels = new;
134 for (i = nchannels; i < numchannels; i++)
135 channels[i].data = NULL;
137 nchannels = numchannels;
139 SDL_UnlockAudio();
140 return 0;
143 /* Read a block of channel data */
144 static void stream_read(int c, int len)
146 Uint8 *stream = channels[c].data->data;
147 int rc;
149 while (len > 0) {
150 rc = av.read_audio(channels[c].data->aid, stream, len);
151 if (rc == -1)
152 break;
154 if (rc < len && channels[c].nloops != 0) {
155 if (av.rewind(channels[c].data->aid) == -1)
156 break;
157 if (channels[c].nloops > 0)
158 channels[c].nloops--;
159 } else if (rc < len)
160 break;
162 stream += rc;
163 len -= rc;
166 if (len > 0)
167 memset(stream, 0, len);
170 /* Audio callback */
171 static void render_audio(void *userdata, Uint8 *stream, int len)
173 int i;
174 Sint16 *samp, *buf;
176 /* Read data for all channels */
177 for (i = 0; i < nchannels; i++)
178 if (channels[i].data && channels[i].data->flags & AUD_OPEN_STREAM)
179 stream_read(i, len);
181 /* TO DO: Not explode with sample formats other than S16_LE */
182 for (samp = (Sint16 *) stream; (Uint8 *) samp - stream < len; samp++) {
183 double m = 0, c;
184 ptrdiff_t off = samp - (Sint16 *) stream;
186 for (i = 0; i < nchannels; i++) {
187 if (!channels[i].data)
188 continue;
190 buf = (Sint16 *) channels[i].data->data;
191 c = channels[i].volume * (double)buf[off];
193 fade(i);
195 /* Mix sample into output */
196 if (m >= 0 && c >= 0)
197 m = (m + c) - (m * c) / 32768;
198 else if (m <= 0 && c <= 0)
199 m = (m + c) + (m * c) / 32768;
200 else
201 m = (m + c);
204 /* Render sample */
205 *samp = m;
208 /* Clean up finished channels */
209 for (i = 0; i < nchannels; i++)
210 if (channels[i].data && !channels[i].nloops)
211 stop(i);
214 static struct audio_data *sdl_ao_open(const char *resource, int flags)
216 struct audio_data *new = calloc(1, sizeof *new);
218 if (!new)
219 goto err;
221 if ((new->aid = av.open(resource, AV_INPUT_AUDIO, &fmt, NULL)) == -1)
222 goto err;
224 new->flags = flags;
225 if (flags & AUD_OPEN_STREAM) {
226 new->datasz = sdlfmt.samples * 4;
228 if (!(new->data = malloc(new->datasz)))
229 goto err;
230 } else if (flags & AUD_OPEN_SAMPLE) {
231 goto err;
234 SDL_LockAudio();
235 opensounds++;
236 SDL_UnlockAudio();
238 return new;
239 err:
240 free(new);
241 return NULL;
244 static void sdl_ao_close(struct audio_data *data)
246 SDL_LockAudio();
248 data->delete = 1;
250 if (!data->playing)
251 data_delete(data);
253 SDL_UnlockAudio();
256 static int setupchannel(int c, struct audio_data *data, int nloops)
258 static unsigned int chanid;
260 data->playing++;
261 channels[c].data = data;
262 channels[c].chanid = chanid;
263 channels[c].nloops = nloops;
265 chanid = (chanid + 1) % ((unsigned int) INT_MAX + 1);
267 return channels[c].chanid;
270 static int sdl_ao_play(struct audio_data *data, int nloops, double volume)
272 int i, rc;
274 if (!data || nloops < -1 || nloops == 0)
275 return -1;
277 SDL_LockAudio();
279 for (i = 0; i < nchannels; i++)
280 if (!channels[i].data)
281 break;
283 /* Cancelling a currently playing channel may be better */
284 if (i >= nchannels)
285 return -1;
287 rc = setupchannel(i, data, nloops);
288 channels[i].volume = volume;
289 channels[i].fadeamt = 0;
291 SDL_UnlockAudio();
293 return rc;
296 static int sdl_ao_stop(int id)
298 int i;
300 SDL_LockAudio();
302 for (i = 0; i < nchannels; i++) {
303 if (channels[i].chanid == id && channels[i].data) {
304 stop(i);
305 break;
309 SDL_UnlockAudio();
311 return (i >= nchannels) ? -1 : 0;
314 static int sdl_ao_fade(int id, struct audio_data *data, int nloops, double vol)
316 int i, rc, chan = -1;
318 SDL_LockAudio();
320 /* Find some channels */
321 for (i = 0; i < nchannels; i++) {
322 if (channels[i].chanid == id)
323 setfade(i, 0, 4);
325 if (!channels[i].data)
326 chan = i;
329 if (chan == -1)
330 return -1;
332 rc = setupchannel(chan, data, nloops);
333 channels[chan].volume = 0;
334 setfade(chan, vol, 4);
336 SDL_UnlockAudio();
338 return rc;
341 /* Open the SDL audio device. TO DO: This is ugly, fix it. */
342 static int sdl_ao_init(int flags)
344 struct SDL_AudioSpec desired;
346 if (!SDL_WasInit(SDL_INIT_AUDIO))
347 if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
348 logger.write(LOG_ERROR, SDL_MODULE,
349 "Failed to init audio: %s", SDL_GetError());
350 return -1;
353 /* TO DO: This should be configurable */
354 desired.freq = 22050;
355 desired.format = AUDIO_S16LSB;
356 desired.channels = 2;
357 desired.userdata = NULL;
358 desired.callback = render_audio;
359 desired.samples = 2048;
361 if (SDL_OpenAudio(&desired, &sdlfmt) == -1) {
362 logger.write(LOG_ERROR, SDL_MODULE,
363 "Failed to open audio device: %s", SDL_GetError());
364 return -1;
367 fmt.frequency = sdlfmt.freq;
368 fmt.channels = sdlfmt.channels;
370 switch (sdlfmt.format) {
371 case AUDIO_U8:
372 fmt.format = AFMT_U8;
373 break;
374 case AUDIO_S8:
375 fmt.format = AFMT_S8;
376 break;
377 case AUDIO_U16LSB:
378 fmt.format = AFMT_U16_LE;
379 break;
380 case AUDIO_S16LSB:
381 fmt.format = AFMT_S16_LE;
382 break;
383 case AUDIO_U16MSB:
384 fmt.format = AFMT_U16_BE;
385 break;
386 case AUDIO_S16MSB:
387 fmt.format = AFMT_S16_BE;
388 break;
389 default:
390 goto err;
393 if (setchannels(8) == -1)
394 goto err;
396 SDL_PauseAudio(0);
398 logger.write(LOG_INFO, SDL_MODULE,
399 "Audio output initialized successfully.");
401 return 0;
402 err:
403 SDL_CloseAudio();
404 return -1;
407 static int sdl_ao_fini(int force)
409 int i;
411 SDL_LockAudio();
413 /* IF our refcounts are OK, opensounds > 0 means nothing is playing. */
414 if (!force && opensounds) {
415 logger.write(LOG_WARNING, SDL_MODULE,
416 "Can't shutdown while sounds still open.");
417 return -1;
420 SDL_UnlockAudio();
421 SDL_CloseAudio();
422 SDL_QuitSubSystem(SDL_INIT_AUDIO);
424 for (i = 0; i < nchannels; i++)
425 if (channels[i].data)
426 stop(i);
428 logger.write(LOG_INFO, SDL_MODULE, "%s audio shutdown successful.",
429 (force) ? "Forced" : "Normal");
431 return 0;
434 int init(plugin_useservice_t useservice)
436 static struct audio_output ao_sdl = {
437 #undef init
438 .init = sdl_ao_init,
439 .fini = sdl_ao_fini,
440 .open = sdl_ao_open,
441 .close = sdl_ao_close,
442 .play = sdl_ao_play,
443 .stop = sdl_ao_stop,
444 .fade = sdl_ao_fade,
447 if (useservice(LOG_SERVICE, SDL_MODULE, &logger))
448 return -1;
449 if (useservice(AV_ACCESS_SERVICE, SDL_MODULE, &av))
450 return -1;
451 if (useservice(AO_SERVICE, SDL_MODULE, &ao_sdl))
452 return -1;
454 if (SDL_Init(SDL_INIT_NOPARACHUTE) == -1) {
455 logger.write(LOG_WARNING, SDL_MODULE,
456 "Failed to initialize SDL: %s", SDL_GetError());
457 return -1;
460 logger.write(LOG_INFO, SDL_MODULE,
461 "SDL Audio/Video output driver " SDL_MODULE_VERSION);
463 return 0;