New audio output interface
[2oom.git] / src / module / sdl.c
blob3fdc808527bea612313fc918a82fdb7fb4f9b7db
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 <SDL.h>
29 #include "../audio/avfile.h"
30 #include "../plugin/plugin.h"
31 #include "../sys/log.h"
33 #define init sdl_LTX_init
35 static struct log_context logger;
36 static struct av_accessor av;
38 static struct SDL_AudioSpec sdlfmt;
39 static struct audio_format fmt;
41 struct audio_data {
42 int flags;
43 int playing;
44 int delete;
45 int aid;
46 Uint8 *data;
47 size_t datasz;
50 static struct channel {
51 struct audio_data *data;
52 int nloops;
53 } *channels;
54 static int nchannels;
56 static void data_delete(struct audio_data *data)
58 free(data->data);
59 free(data);
62 static int setchannels(int numchannels)
64 int i;
65 struct channel *new;
67 SDL_LockAudio();
68 if (!(new = realloc(channels, numchannels * sizeof *channels))) {
69 SDL_UnlockAudio();
70 return -1;
73 channels = new;
75 for (i = nchannels; i < numchannels; i++)
76 channels[i].data = NULL;
78 nchannels = numchannels;
80 SDL_UnlockAudio();
81 return 0;
84 /* Read a block of channel data */
85 static void read_channel(int c, int len)
87 Uint8 *stream = channels[c].data->data;
88 int rc;
90 while (len > 0) {
91 rc = av.read_audio(channels[c].data->aid, stream, len);
92 if (rc == -1)
93 break;
95 if (rc < len && channels[c].nloops) {
96 if (av.rewind(channels[c].data->aid) == -1) break;
97 } else if (rc < len)
98 break;
100 stream += rc;
101 len -= rc;
104 if (len > 0)
105 memset(stream, 0, len);
108 /* Audio callback */
109 static void render_audio(void *userdata, Uint8 *stream, int len)
111 int i;
112 Sint16 *samp, *buf;
114 /* Read data for all channels */
115 for (i = 0; i < nchannels; i++)
116 if (channels[i].aid >= 0)
117 read_channel(i, len);
119 /* TO DO: Not explode with sample formats other than S16_LE */
120 for (samp = (Sint16 *) stream; (Uint8 *) samp - stream < len; samp++) {
121 double m = 0;
122 ptrdiff_t off = samp - (Sint16 *) stream;
124 for (i = 0; i < nchannels; i++) {
125 if (channels[i].aid == -1)
126 continue;
128 buf = (Sint16 *) channels[i].buf;
130 /* Mix sample into output */
131 if (m >= 0 && buf[off] >= 0)
132 m = (m + (double)buf[off]) -
133 (m * (double)buf[off]) / 32768;
134 else if (m <= 0 && buf[off] <= 0)
135 m = (m + (double)buf[off]) +
136 (m * (double)buf[off]) / 32768;
137 else
138 m = (m + (double)buf[off]);
141 /* Render sample */
142 *samp = m;
146 static struct audio_data *sdl_ao_open(const char *resource, int flags)
148 struct audio_data *new = calloc(1, sizeof *new);
150 if (!new)
151 goto err;
153 if (!(new->aid = av.open(resource, AV_INPUT_AUDIO, fmt, NULL)))
154 goto err;
156 new->flags = flags;
157 if (flags & AUD_OPEN_STREAM) {
158 new->datasz = sdlfmt.samples * 4;
160 if (!(new->data = malloc(new->datasz)))
161 goto err;
162 } else if (flags & AUD_OPEN_SAMPLE) {
166 return new;
167 err:
168 free(new);
169 return NULL;
172 static void sdl_ao_close(struct audio_data *data)
174 data->delete = 1;
176 if (!data->playing)
177 data_delete(data);
180 /* Play the audio data from the given AV id, according to flags. */
181 static int sdl_ao_play(int channel, int id, int flags)
183 int i = channel;
184 if (channel == -1)
185 for (i = 0; i < nchannels; i++)
186 if (channels[i].aid == -1) break;
188 if (i < 0 || i >= nchannels)
189 return -1;
191 SDL_LockAudio();
193 /* TO DO: Something more intelligent. */
194 if (!(channels[i].buf = malloc(sdlfmt.format * 4)))
195 return -1;
197 channels[i].aid = id;
198 channels[i].flags = flags;
200 SDL_UnlockAudio();
202 return i;
205 /* Open the SDL audio device */
206 static int sdl_ao_init(int flags)
208 struct SDL_AudioSpec desired;
210 if (!SDL_WasInit(SDL_INIT_AUDIO))
211 if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
212 logger.write(LOG_ERROR, SDL_MODULE,
213 "Failed to init audio: %s", SDL_GetError());
214 return NULL;
217 /* TO DO: This should be configurable */
218 desired.freq = 22050;
219 desired.format = AUDIO_S16LSB;
220 desired.channels = 2;
221 desired.userdata = NULL;
222 desired.callback = render_audio;
223 desired.samples = 2048;
225 if (SDL_OpenAudio(&desired, &sdlfmt) == -1) {
226 logger.write(LOG_ERROR, SDL_MODULE,
227 "Failed to open audio device: %s", SDL_GetError());
228 return NULL;
231 fmt.frequency = sdlfmt.freq;
232 fmt.channels = sdlfmt.channels;
234 switch (sdlfmt.format) {
235 case AUDIO_U8:
236 fmt.format = AFMT_U8;
237 break;
238 case AUDIO_S8:
239 fmt.format = AFMT_S8;
240 break;
241 case AUDIO_U16LSB:
242 fmt.format = AFMT_U16_LE;
243 break;
244 case AUDIO_S16LSB:
245 fmt.format = AFMT_S16_LE;
246 break;
247 case AUDIO_U16MSB:
248 fmt.format = AFMT_U16_BE;
249 break;
250 case AUDIO_S16MSB:
251 fmt.format = AFMT_S16_BE;
252 break;
253 default:
254 goto err;
257 if (setchannels(8) == -1)
258 goto err;
260 SDL_PauseAudio(0);
262 return &fmt;
263 err:
264 SDL_CloseAudio();
265 return NULL;
268 int init(plugin_useservice_t useservice)
270 static struct audio_output ao_sdl = {
271 #undef init
272 .init = sdl_ao_init,
273 .play = sdl_ao_play,
276 if (useservice(LOG_SERVICE, SDL_MODULE, &logger))
277 return -1;
278 if (useservice(AV_ACCESS_SERVICE, SDL_MODULE, &av))
279 return -1;
280 if (useservice(AO_SERVICE, SDL_MODULE, &ao_sdl))
281 return -1;
283 if (SDL_Init(SDL_INIT_NOPARACHUTE) == -1) {
284 logger.write(LOG_WARNING, SDL_MODULE,
285 "Failed to initialize SDL: %s", SDL_GetError());
286 return -1;
289 logger.write(LOG_INFO, SDL_MODULE,
290 "SDL Audio/Video output driver " SDL_MODULE_VERSION);
292 return 0;