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"
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
;
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. */
54 struct audio_data
*data
;
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
)
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
);
76 /* Delete opened audio data */
77 static void data_delete(struct audio_data
*data
)
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)
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)
110 channels
[c
].volume
= channels
[c
].voltarget
;
111 channels
[c
].fadeamt
= 0;
116 static int setchannels(int numchannels
)
122 if (!(new = realloc(channels
, numchannels
* sizeof *channels
))) {
129 for (i
= nchannels
; i
< numchannels
; i
++)
130 channels
[i
].data
= NULL
;
132 nchannels
= numchannels
;
138 /* Read a block of channel data */
139 static void stream_read(int c
, int len
)
141 Uint8
*stream
= channels
[c
].data
->data
;
145 rc
= av
.read_audio(channels
[c
].data
->aid
, stream
, len
);
149 if (rc
< len
&& channels
[c
].nloops
!= 0) {
150 if (av
.rewind(channels
[c
].data
->aid
) == -1)
152 if (channels
[c
].nloops
> 0)
153 channels
[c
].nloops
--;
162 memset(stream
, 0, len
);
166 static void render_audio(void *userdata
, Uint8
*stream
, int len
)
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
)
176 /* TO DO: Not explode with sample formats other than S16_LE */
177 for (samp
= (Sint16
*) stream
; (Uint8
*) samp
- stream
< len
; samp
++) {
179 ptrdiff_t off
= samp
- (Sint16
*) stream
;
181 for (i
= 0; i
< nchannels
; i
++) {
182 if (!channels
[i
].data
)
185 buf
= (Sint16
*) channels
[i
].data
->data
;
186 c
= channels
[i
].volume
* (double)buf
[off
];
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;
203 /* Clean up finished channels */
204 for (i
= 0; i
< nchannels
; i
++)
205 if (channels
[i
].data
&& !channels
[i
].nloops
)
209 static struct audio_data
*sdl_ao_open(const char *resource
, int flags
)
211 struct audio_data
*new = calloc(1, sizeof *new);
216 if ((new->aid
= av
.open(resource
, AV_INPUT_AUDIO
, &fmt
, NULL
)) == -1)
220 if (flags
& AUD_OPEN_STREAM
) {
221 new->datasz
= sdlfmt
.samples
* 4;
223 if (!(new->data
= malloc(new->datasz
)))
225 } else if (flags
& AUD_OPEN_SAMPLE
) {
239 static void sdl_ao_close(struct audio_data
*data
)
251 static int setupchannel(int c
, struct audio_data
*data
, int nloops
)
253 static unsigned int chanid
;
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
)
269 if (!data
|| nloops
< -1 || nloops
== 0)
274 for (i
= 0; i
< nchannels
; i
++)
275 if (!channels
[i
].data
)
278 /* Cancelling a currently playing channel may be better */
282 rc
= setupchannel(i
, data
, nloops
);
283 channels
[i
].volume
= volume
;
284 channels
[i
].fadeamt
= 0;
291 static int sdl_ao_stop(int id
)
297 for (i
= 0; i
< nchannels
; i
++) {
298 if (channels
[i
].chanid
== id
&& channels
[i
].data
) {
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;
315 /* Find some channels */
316 for (i
= 0; i
< nchannels
; i
++) {
317 if (channels
[i
].chanid
== id
)
320 if (!channels
[i
].data
)
327 rc
= setupchannel(chan
, data
, nloops
);
328 channels
[chan
].volume
= 0;
329 setfade(chan
, vol
, 4);
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());
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());
362 fmt
.frequency
= sdlfmt
.freq
;
363 fmt
.channels
= sdlfmt
.channels
;
365 switch (sdlfmt
.format
) {
367 fmt
.format
= AFMT_U8
;
370 fmt
.format
= AFMT_S8
;
373 fmt
.format
= AFMT_U16_LE
;
376 fmt
.format
= AFMT_S16_LE
;
379 fmt
.format
= AFMT_U16_BE
;
382 fmt
.format
= AFMT_S16_BE
;
388 if (setchannels(8) == -1)
393 logger
.write(LOG_INFO
, SDL_MODULE
,
394 "Audio output initialized successfully.");
402 static int sdl_ao_fini(int force
)
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.");
417 SDL_QuitSubSystem(SDL_INIT_AUDIO
);
419 for (i
= 0; i
< nchannels
; i
++)
420 if (channels
[i
].data
)
423 logger
.write(LOG_INFO
, SDL_MODULE
, "%s audio shutdown successful.",
424 (force
) ? "Forced" : "Normal");
429 int init(plugin_useservice_t useservice
)
431 static struct audio_output ao_sdl
= {
436 .close
= sdl_ao_close
,
442 if (useservice(LOG_SERVICE
, SDL_MODULE
, &logger
))
444 if (useservice(AV_ACCESS_SERVICE
, SDL_MODULE
, &av
))
446 if (useservice(AO_SERVICE
, SDL_MODULE
, &ao_sdl
))
449 if (SDL_Init(SDL_INIT_NOPARACHUTE
) == -1) {
450 logger
.write(LOG_WARNING
, SDL_MODULE
,
451 "Failed to initialize SDL: %s", SDL_GetError());
455 logger
.write(LOG_INFO
, SDL_MODULE
,
456 "SDL Audio/Video output driver " SDL_MODULE_VERSION
);