build: Update for libtool-2.2 and autoconf-2.62
[2oom.git] / src / module / sdl.c
blob4b6aba05e4a39e18e26d59232dfeeea7f92ca175
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
19 #define SDL_MODULE "SDL"
20 #define SDL_MODULE_VERSION "0.1.0"
22 #include <stdlib.h>
23 #include <limits.h>
24 #include <SDL.h>
26 #include "../av/avfile.h"
27 #include "../plugin/plugin.h"
28 #include "../sys/log.h"
30 #define init sdl_LTX_init
32 static struct log_context logger;
33 static struct av_accessor av;
35 static struct SDL_AudioSpec sdlfmt;
36 static struct audio_format fmt;
38 struct audio_data {
39 int flags;
40 int playing;
41 int delete;
42 int aid;
43 Uint8 *data;
44 size_t datasz;
47 static struct channel {
48 double volume; /**< Current channel volume. */
50 double voltarget; /**< Target channel volume. */
51 double fadeamt; /**< Amount to adjust volume each sample. */
53 int chanid;
54 struct audio_data *data;
55 int nloops;
56 } *channels;
57 static int nchannels;
59 static unsigned int opensounds;
61 /* Sets a channel to fade to the target volume over the course of nsec seconds.
63 static void setfade(int c, double target, double nsec)
65 SDL_LockAudio();
67 if (target == channels[c].volume)
68 return (void) (channels[c].fadeamt = 0);
70 channels[c].voltarget = target;
71 channels[c].fadeamt = (target-channels[c].volume)/(nsec*sdlfmt.freq);
73 SDL_UnlockAudio();
76 /* Delete opened audio data */
77 static void data_delete(struct audio_data *data)
79 free(data->data);
80 free(data);
81 opensounds--;
84 /* Stop a channel */
85 static inline void stop(int c)
87 channels[c].data->playing--;
88 if (channels[c].data->delete && !channels[c].data->playing)
89 data_delete(channels[c].data);
90 channels[c].data = NULL;
93 /* Apply one sample's worth of fading to channel c. */
94 static inline void fade(int c)
96 if (channels[c].fadeamt == 0)
97 return;
98 else if (channels[c].fadeamt > 0) {
99 channels[c].volume += channels[c].fadeamt;
100 if (channels[c].volume > channels[c].voltarget) {
101 channels[c].volume = channels[c].voltarget;
102 channels[c].fadeamt = 0;
104 } else if (channels[c].fadeamt < 0) {
105 channels[c].volume += channels[c].fadeamt;
106 if (channels[c].volume < channels[c].voltarget) {
107 if (channels[c].voltarget == 0)
108 stop(c);
110 channels[c].volume = channels[c].voltarget;
111 channels[c].fadeamt = 0;
116 static int setchannels(int numchannels)
118 int i;
119 struct channel *new;
121 SDL_LockAudio();
122 if (!(new = realloc(channels, numchannels * sizeof *channels))) {
123 SDL_UnlockAudio();
124 return -1;
127 channels = new;
129 for (i = nchannels; i < numchannels; i++)
130 channels[i].data = NULL;
132 nchannels = numchannels;
134 SDL_UnlockAudio();
135 return 0;
138 /* Read a block of channel data */
139 static void stream_read(int c, int len)
141 Uint8 *stream = channels[c].data->data;
142 int rc;
144 while (len > 0) {
145 rc = av.read_audio(channels[c].data->aid, stream, len);
146 if (rc == -1)
147 break;
149 if (rc < len && channels[c].nloops != 0) {
150 if (av.rewind(channels[c].data->aid) == -1)
151 break;
152 if (channels[c].nloops > 0)
153 channels[c].nloops--;
154 } else if (rc < len)
155 break;
157 stream += rc;
158 len -= rc;
161 if (len > 0)
162 memset(stream, 0, len);
165 /* Audio callback */
166 static void render_audio(void *userdata, Uint8 *stream, int len)
168 int i;
169 Sint16 *samp, *buf;
171 /* Read data for all channels */
172 for (i = 0; i < nchannels; i++)
173 if (channels[i].data && channels[i].data->flags & AUD_OPEN_STREAM)
174 stream_read(i, len);
176 /* TO DO: Not explode with sample formats other than S16_LE */
177 for (samp = (Sint16 *) stream; (Uint8 *) samp - stream < len; samp++) {
178 double m = 0, c;
179 ptrdiff_t off = samp - (Sint16 *) stream;
181 for (i = 0; i < nchannels; i++) {
182 if (!channels[i].data)
183 continue;
185 buf = (Sint16 *) channels[i].data->data;
186 c = channels[i].volume * (double)buf[off];
188 fade(i);
190 /* Mix sample into output */
191 if (m >= 0 && c >= 0)
192 m = (m + c) - (m * c) / 32768;
193 else if (m <= 0 && c <= 0)
194 m = (m + c) + (m * c) / 32768;
195 else
196 m = (m + c);
199 /* Render sample */
200 *samp = m;
203 /* Clean up finished channels */
204 for (i = 0; i < nchannels; i++)
205 if (channels[i].data && !channels[i].nloops)
206 stop(i);
209 static struct audio_data *sdl_ao_open(const char *resource, int flags)
211 struct audio_data *new = calloc(1, sizeof *new);
213 if (!new)
214 goto err;
216 if ((new->aid = av.open(resource, AV_INPUT_AUDIO, &fmt, NULL)) == -1)
217 goto err;
219 new->flags = flags;
220 if (flags & AUD_OPEN_STREAM) {
221 new->datasz = sdlfmt.samples * 4;
223 if (!(new->data = malloc(new->datasz)))
224 goto err;
225 } else if (flags & AUD_OPEN_SAMPLE) {
226 goto err;
229 SDL_LockAudio();
230 opensounds++;
231 SDL_UnlockAudio();
233 return new;
234 err:
235 free(new);
236 return NULL;
239 static void sdl_ao_close(struct audio_data *data)
241 SDL_LockAudio();
243 data->delete = 1;
245 if (!data->playing)
246 data_delete(data);
248 SDL_UnlockAudio();
251 static int setupchannel(int c, struct audio_data *data, int nloops)
253 static unsigned int chanid;
255 data->playing++;
256 channels[c].data = data;
257 channels[c].chanid = chanid;
258 channels[c].nloops = nloops;
260 chanid = (chanid + 1) % ((unsigned int) INT_MAX + 1);
262 return channels[c].chanid;
265 static int sdl_ao_play(struct audio_data *data, int nloops, double volume)
267 int i, rc;
269 if (!data || nloops < -1 || nloops == 0)
270 return -1;
272 SDL_LockAudio();
274 for (i = 0; i < nchannels; i++)
275 if (!channels[i].data)
276 break;
278 /* Cancelling a currently playing channel may be better */
279 if (i >= nchannels)
280 return -1;
282 rc = setupchannel(i, data, nloops);
283 channels[i].volume = volume;
284 channels[i].fadeamt = 0;
286 SDL_UnlockAudio();
288 return rc;
291 static int sdl_ao_stop(int id)
293 int i;
295 SDL_LockAudio();
297 for (i = 0; i < nchannels; i++) {
298 if (channels[i].chanid == id && channels[i].data) {
299 stop(i);
300 break;
304 SDL_UnlockAudio();
306 return (i >= nchannels) ? -1 : 0;
309 static int sdl_ao_fade(int id, struct audio_data *data, int nloops, double vol)
311 int i, rc, chan = -1;
313 SDL_LockAudio();
315 /* Find some channels */
316 for (i = 0; i < nchannels; i++) {
317 if (channels[i].chanid == id)
318 setfade(i, 0, 4);
320 if (!channels[i].data)
321 chan = i;
324 if (chan == -1)
325 return -1;
327 rc = setupchannel(chan, data, nloops);
328 channels[chan].volume = 0;
329 setfade(chan, vol, 4);
331 SDL_UnlockAudio();
333 return rc;
336 /* Open the SDL audio device. TO DO: This is ugly, fix it. */
337 static int sdl_ao_init(int flags)
339 struct SDL_AudioSpec desired;
341 if (!SDL_WasInit(SDL_INIT_AUDIO))
342 if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
343 logger.write(LOG_ERROR, SDL_MODULE,
344 "Failed to init audio: %s", SDL_GetError());
345 return -1;
348 /* TO DO: This should be configurable */
349 desired.freq = 22050;
350 desired.format = AUDIO_S16LSB;
351 desired.channels = 2;
352 desired.userdata = NULL;
353 desired.callback = render_audio;
354 desired.samples = 2048;
356 if (SDL_OpenAudio(&desired, &sdlfmt) == -1) {
357 logger.write(LOG_ERROR, SDL_MODULE,
358 "Failed to open audio device: %s", SDL_GetError());
359 return -1;
362 fmt.frequency = sdlfmt.freq;
363 fmt.channels = sdlfmt.channels;
365 switch (sdlfmt.format) {
366 case AUDIO_U8:
367 fmt.format = AFMT_U8;
368 break;
369 case AUDIO_S8:
370 fmt.format = AFMT_S8;
371 break;
372 case AUDIO_U16LSB:
373 fmt.format = AFMT_U16_LE;
374 break;
375 case AUDIO_S16LSB:
376 fmt.format = AFMT_S16_LE;
377 break;
378 case AUDIO_U16MSB:
379 fmt.format = AFMT_U16_BE;
380 break;
381 case AUDIO_S16MSB:
382 fmt.format = AFMT_S16_BE;
383 break;
384 default:
385 goto err;
388 if (setchannels(8) == -1)
389 goto err;
391 SDL_PauseAudio(0);
393 logger.write(LOG_INFO, SDL_MODULE,
394 "Audio output initialized successfully.");
396 return 0;
397 err:
398 SDL_CloseAudio();
399 return -1;
402 static int sdl_ao_fini(int force)
404 int i;
406 SDL_LockAudio();
408 /* IF our refcounts are OK, opensounds > 0 means nothing is playing. */
409 if (!force && opensounds) {
410 logger.write(LOG_WARNING, SDL_MODULE,
411 "Can't shutdown while sounds still open.");
412 return -1;
415 SDL_UnlockAudio();
416 SDL_CloseAudio();
417 SDL_QuitSubSystem(SDL_INIT_AUDIO);
419 for (i = 0; i < nchannels; i++)
420 if (channels[i].data)
421 stop(i);
423 logger.write(LOG_INFO, SDL_MODULE, "%s audio shutdown successful.",
424 (force) ? "Forced" : "Normal");
426 return 0;
429 int init(plugin_useservice_t useservice)
431 static struct audio_output ao_sdl = {
432 #undef init
433 .init = sdl_ao_init,
434 .fini = sdl_ao_fini,
435 .open = sdl_ao_open,
436 .close = sdl_ao_close,
437 .play = sdl_ao_play,
438 .stop = sdl_ao_stop,
439 .fade = sdl_ao_fade,
442 if (useservice(LOG_SERVICE, SDL_MODULE, &logger))
443 return -1;
444 if (useservice(AV_ACCESS_SERVICE, SDL_MODULE, &av))
445 return -1;
446 if (useservice(AO_SERVICE, SDL_MODULE, &ao_sdl))
447 return -1;
449 if (SDL_Init(SDL_INIT_NOPARACHUTE) == -1) {
450 logger.write(LOG_WARNING, SDL_MODULE,
451 "Failed to initialize SDL: %s", SDL_GetError());
452 return -1;
455 logger.write(LOG_INFO, SDL_MODULE,
456 "SDL Audio/Video output driver " SDL_MODULE_VERSION);
458 return 0;