New audio output interface now works.
[2oom.git] / src / module / sdl.c
blobdbe34f7f4cff71e9a95c30aea69de01b87f67fcc
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 "../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;
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 int chanid;
54 struct audio_data *data;
55 int nloops;
56 } *channels;
57 static int nchannels;
59 static void data_delete(struct audio_data *data)
61 free(data->data);
62 free(data);
65 static int setchannels(int numchannels)
67 int i;
68 struct channel *new;
70 SDL_LockAudio();
71 if (!(new = realloc(channels, numchannels * sizeof *channels))) {
72 SDL_UnlockAudio();
73 return -1;
76 channels = new;
78 for (i = nchannels; i < numchannels; i++)
79 channels[i].data = NULL;
81 nchannels = numchannels;
83 SDL_UnlockAudio();
84 return 0;
87 /* Read a block of channel data */
88 static void stream_read(int c, int len)
90 Uint8 *stream = channels[c].data->data;
91 int rc;
93 while (len > 0) {
94 rc = av.read_audio(channels[c].data->aid, stream, len);
95 if (rc == -1)
96 break;
98 if (rc < len && channels[c].nloops != 0) {
99 if (av.rewind(channels[c].data->aid) == -1)
100 break;
101 if (channels[c].nloops > 0)
102 channels[c].nloops--;
103 } else if (rc < len)
104 break;
106 stream += rc;
107 len -= rc;
110 if (len > 0)
111 memset(stream, 0, len);
114 /* Audio callback */
115 static void render_audio(void *userdata, Uint8 *stream, int len)
117 int i;
118 Sint16 *samp, *buf;
120 /* Read data for all channels */
121 for (i = 0; i < nchannels; i++)
122 if (channels[i].data && channels[i].data->flags & AUD_OPEN_STREAM)
123 stream_read(i, len);
125 /* TO DO: Not explode with sample formats other than S16_LE */
126 for (samp = (Sint16 *) stream; (Uint8 *) samp - stream < len; samp++) {
127 double m = 0, c;
128 ptrdiff_t off = samp - (Sint16 *) stream;
130 for (i = 0; i < nchannels; i++) {
131 if (!channels[i].data)
132 continue;
134 buf = (Sint16 *) channels[i].data->data;
135 c = buf[off];
137 /* Mix sample into output */
138 if (m >= 0 && c >= 0)
139 m = (m + c) - (m * c) / 32768;
140 else if (m <= 0 && c <= 0)
141 m = (m + c) + (m * c) / 32768;
142 else
143 m = (m + c);
146 /* Render sample */
147 *samp = m;
150 /* Clean up finished channels */
151 for (i = 0; i < nchannels; i++)
152 if (!channels[i].nloops)
153 channels[i].data = NULL;
156 static struct audio_data *sdl_ao_open(const char *resource, int flags)
158 struct audio_data *new = calloc(1, sizeof *new);
160 if (!new)
161 goto err;
163 if ((new->aid = av.open(resource, AV_INPUT_AUDIO, &fmt, NULL)) == -1)
164 goto err;
166 new->flags = flags;
167 if (flags & AUD_OPEN_STREAM) {
168 new->datasz = sdlfmt.samples * 4;
170 if (!(new->data = malloc(new->datasz)))
171 goto err;
172 } else if (flags & AUD_OPEN_SAMPLE) {
173 goto err;
176 return new;
177 err:
178 free(new);
179 return NULL;
182 static void sdl_ao_close(struct audio_data *data)
184 data->delete = 1;
186 if (!data->playing)
187 data_delete(data);
190 static int sdl_ao_play(struct audio_data *data, int nloops)
192 static int chanid;
193 int i;
195 if (nloops < -1 || nloops == 0)
196 return -1;
198 SDL_LockAudio();
200 for (i = 0; i < nchannels; i++)
201 if (!channels[i].data)
202 break;
204 /* Cancelling a currently playing channel may be better */
205 if (i >= nchannels)
206 return -1;
208 data->playing++;
209 channels[i].data = data;
210 channels[i].chanid = chanid;
211 channels[i].nloops = nloops;
213 SDL_UnlockAudio();
215 chanid = (chanid + 1) % INT_MAX+1;
217 return chanid;
220 /* Open the SDL audio device. TO DO: This is ugly, fix it. */
221 static int sdl_ao_init(int flags)
223 struct SDL_AudioSpec desired;
225 if (!SDL_WasInit(SDL_INIT_AUDIO))
226 if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
227 logger.write(LOG_ERROR, SDL_MODULE,
228 "Failed to init audio: %s", SDL_GetError());
229 return -1;
232 /* TO DO: This should be configurable */
233 desired.freq = 22050;
234 desired.format = AUDIO_S16LSB;
235 desired.channels = 2;
236 desired.userdata = NULL;
237 desired.callback = render_audio;
238 desired.samples = 2048;
240 if (SDL_OpenAudio(&desired, &sdlfmt) == -1) {
241 logger.write(LOG_ERROR, SDL_MODULE,
242 "Failed to open audio device: %s", SDL_GetError());
243 return -1;
246 fmt.frequency = sdlfmt.freq;
247 fmt.channels = sdlfmt.channels;
249 switch (sdlfmt.format) {
250 case AUDIO_U8:
251 fmt.format = AFMT_U8;
252 break;
253 case AUDIO_S8:
254 fmt.format = AFMT_S8;
255 break;
256 case AUDIO_U16LSB:
257 fmt.format = AFMT_U16_LE;
258 break;
259 case AUDIO_S16LSB:
260 fmt.format = AFMT_S16_LE;
261 break;
262 case AUDIO_U16MSB:
263 fmt.format = AFMT_U16_BE;
264 break;
265 case AUDIO_S16MSB:
266 fmt.format = AFMT_S16_BE;
267 break;
268 default:
269 goto err;
272 if (setchannels(8) == -1)
273 goto err;
275 SDL_PauseAudio(0);
277 return 0;
278 err:
279 SDL_CloseAudio();
280 return -1;
283 int init(plugin_useservice_t useservice)
285 static struct audio_output ao_sdl = {
286 #undef init
287 .init = sdl_ao_init,
288 // .fini = sdl_ao_fini,
289 .open = sdl_ao_open,
290 .close = sdl_ao_close,
291 .play = sdl_ao_play,
292 // .stop = sdl_ao_stop,
295 if (useservice(LOG_SERVICE, SDL_MODULE, &logger))
296 return -1;
297 if (useservice(AV_ACCESS_SERVICE, SDL_MODULE, &av))
298 return -1;
299 if (useservice(AO_SERVICE, SDL_MODULE, &ao_sdl))
300 return -1;
302 if (SDL_Init(SDL_INIT_NOPARACHUTE) == -1) {
303 logger.write(LOG_WARNING, SDL_MODULE,
304 "Failed to initialize SDL: %s", SDL_GetError());
305 return -1;
308 logger.write(LOG_INFO, SDL_MODULE,
309 "SDL Audio/Video output driver " SDL_MODULE_VERSION);
311 return 0;