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
24 #define SDL_MODULE "SDL"
25 #define SDL_MODULE_VERSION "0.1.0"
31 #include "../audio/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
;
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. */
59 struct audio_data
*data
;
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
)
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
);
81 /* Delete opened audio data */
82 static void data_delete(struct audio_data
*data
)
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)
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)
115 channels
[c
].volume
= channels
[c
].voltarget
;
116 channels
[c
].fadeamt
= 0;
121 static int setchannels(int numchannels
)
127 if (!(new = realloc(channels
, numchannels
* sizeof *channels
))) {
134 for (i
= nchannels
; i
< numchannels
; i
++)
135 channels
[i
].data
= NULL
;
137 nchannels
= numchannels
;
143 /* Read a block of channel data */
144 static void stream_read(int c
, int len
)
146 Uint8
*stream
= channels
[c
].data
->data
;
150 rc
= av
.read_audio(channels
[c
].data
->aid
, stream
, len
);
154 if (rc
< len
&& channels
[c
].nloops
!= 0) {
155 if (av
.rewind(channels
[c
].data
->aid
) == -1)
157 if (channels
[c
].nloops
> 0)
158 channels
[c
].nloops
--;
167 memset(stream
, 0, len
);
171 static void render_audio(void *userdata
, Uint8
*stream
, int len
)
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
)
181 /* TO DO: Not explode with sample formats other than S16_LE */
182 for (samp
= (Sint16
*) stream
; (Uint8
*) samp
- stream
< len
; samp
++) {
184 ptrdiff_t off
= samp
- (Sint16
*) stream
;
186 for (i
= 0; i
< nchannels
; i
++) {
187 if (!channels
[i
].data
)
190 buf
= (Sint16
*) channels
[i
].data
->data
;
191 c
= channels
[i
].volume
* (double)buf
[off
];
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;
208 /* Clean up finished channels */
209 for (i
= 0; i
< nchannels
; i
++)
210 if (channels
[i
].data
&& !channels
[i
].nloops
)
214 static struct audio_data
*sdl_ao_open(const char *resource
, int flags
)
216 struct audio_data
*new = calloc(1, sizeof *new);
221 if ((new->aid
= av
.open(resource
, AV_INPUT_AUDIO
, &fmt
, NULL
)) == -1)
225 if (flags
& AUD_OPEN_STREAM
) {
226 new->datasz
= sdlfmt
.samples
* 4;
228 if (!(new->data
= malloc(new->datasz
)))
230 } else if (flags
& AUD_OPEN_SAMPLE
) {
244 static void sdl_ao_close(struct audio_data
*data
)
256 static int setupchannel(int c
, struct audio_data
*data
, int nloops
)
258 static unsigned int chanid
;
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
)
274 if (nloops
< -1 || nloops
== 0)
279 for (i
= 0; i
< nchannels
; i
++)
280 if (!channels
[i
].data
)
283 /* Cancelling a currently playing channel may be better */
287 rc
= setupchannel(i
, data
, nloops
);
288 channels
[i
].volume
= volume
;
289 channels
[i
].fadeamt
= 0;
296 static int sdl_ao_stop(int id
)
302 for (i
= 0; i
< nchannels
; i
++) {
303 if (channels
[i
].chanid
== id
&& channels
[i
].data
) {
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;
320 /* Find some channels */
321 for (i
= 0; i
< nchannels
; i
++) {
322 if (channels
[i
].chanid
== id
)
325 if (!channels
[i
].data
)
332 rc
= setupchannel(chan
, data
, nloops
);
333 channels
[chan
].volume
= 0;
334 setfade(chan
, vol
, 4);
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());
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());
367 fmt
.frequency
= sdlfmt
.freq
;
368 fmt
.channels
= sdlfmt
.channels
;
370 switch (sdlfmt
.format
) {
372 fmt
.format
= AFMT_U8
;
375 fmt
.format
= AFMT_S8
;
378 fmt
.format
= AFMT_U16_LE
;
381 fmt
.format
= AFMT_S16_LE
;
384 fmt
.format
= AFMT_U16_BE
;
387 fmt
.format
= AFMT_S16_BE
;
393 if (setchannels(8) == -1)
398 logger
.write(LOG_INFO
, SDL_MODULE
,
399 "Audio output initialized successfully.");
407 static int sdl_ao_fini(int force
)
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.");
422 SDL_QuitSubSystem(SDL_INIT_AUDIO
);
424 for (i
= 0; i
< nchannels
; i
++)
425 if (channels
[i
].data
)
428 logger
.write(LOG_INFO
, SDL_MODULE
, "%s audio shutdown successful.",
429 (force
) ? "Forced" : "Normal");
434 int init(plugin_useservice_t useservice
)
436 static struct audio_output ao_sdl
= {
441 .close
= sdl_ao_close
,
447 if (useservice(LOG_SERVICE
, SDL_MODULE
, &logger
))
449 if (useservice(AV_ACCESS_SERVICE
, SDL_MODULE
, &av
))
451 if (useservice(AO_SERVICE
, SDL_MODULE
, &ao_sdl
))
454 if (SDL_Init(SDL_INIT_NOPARACHUTE
) == -1) {
455 logger
.write(LOG_WARNING
, SDL_MODULE
,
456 "Failed to initialize SDL: %s", SDL_GetError());
460 logger
.write(LOG_INFO
, SDL_MODULE
,
461 "SDL Audio/Video output driver " SDL_MODULE_VERSION
);