dropping SDL for the cross-platform version almost done
[dosdemo] / libs / mikmod / drivers / drv_alsa.c
1 /*      MikMod sound library
2         (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for
3         complete list.
4
5         This library is free software; you can redistribute it and/or modify
6         it under the terms of the GNU Library General Public License as
7         published by the Free Software Foundation; either version 2 of
8         the License, or (at your option) any later version.
9
10         This program is distributed in the hope that it will be useful,
11         but WITHOUT ANY WARRANTY; without even the implied warranty of
12         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13         GNU Library General Public License for more details.
14
15         You should have received a copy of the GNU Library General Public
16         License along with this library; if not, write to the Free Software
17         Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18         02111-1307, USA.
19 */
20
21 /*==============================================================================
22
23   $Id$
24
25   Driver for Advanced Linux Sound Architecture (ALSA)
26
27 ==============================================================================*/
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include "mikmod_internals.h"
34
35 #ifdef DRV_ALSA
36
37 #ifdef HAVE_UNISTD_H
38 #include <unistd.h>
39 #endif
40 #ifdef HAVE_MEMORY_H
41 #include <memory.h>
42 #endif
43
44 #ifdef MIKMOD_DYNAMIC
45 #include <dlfcn.h>
46 #endif
47 #include <stdlib.h>
48 #include <string.h>
49
50 #include <alsa/asoundlib.h>
51 #if defined(SND_LIB_VERSION) && (SND_LIB_VERSION >= 0x20000)
52 #undef DRV_ALSA
53 #endif
54
55 #if defined(SND_LIB_VERSION) && (SND_LIB_VERSION < 0x600)
56 #error ALSA Version too old. Please upgrade your Linux distribution.
57 #endif
58 #endif /* DRV_ALSA */
59
60 #ifdef DRV_ALSA
61
62 #ifdef MIKMOD_DYNAMIC
63 /* runtime link with libasound */
64 #ifndef HAVE_RTLD_GLOBAL
65 #define RTLD_GLOBAL (0)
66 #endif
67 static int (*alsa_pcm_subformat_mask_malloc)(snd_pcm_subformat_mask_t **);
68 static const char * (*alsa_strerror)(int);
69 static int (*alsa_pcm_resume)(snd_pcm_t *);
70 static int (*alsa_pcm_prepare)(snd_pcm_t *);
71 static int (*alsa_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
72 static int (*alsa_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
73 static int (*alsa_pcm_hw_params_current)(snd_pcm_t *, snd_pcm_hw_params_t *);
74 static int (*alsa_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
75 static int (*alsa_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
76 static int (*alsa_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
77 static int (*alsa_pcm_hw_params_set_channels_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *);
78 static int (*alsa_pcm_hw_params_set_buffer_time_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
79 static int (*alsa_pcm_hw_params_set_period_time_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
80 static int (*alsa_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
81 static int (*alsa_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
82 static int (*alsa_pcm_sw_params_sizeof)(void);
83 static int (*alsa_pcm_hw_params_sizeof)(void);
84 static int (*alsa_pcm_open)(snd_pcm_t**, const char *, int, int);
85 static int (*alsa_pcm_close)(snd_pcm_t*);
86 static int (*alsa_pcm_drain)(snd_pcm_t*);
87 static int (*alsa_pcm_drop)(snd_pcm_t*);
88 static int (*alsa_pcm_start)(snd_pcm_t *);
89 static snd_pcm_sframes_t (*alsa_pcm_writei)(snd_pcm_t*,const void*,snd_pcm_uframes_t);
90
91 static void* libasound = NULL;
92
93 #else
94 /* compile-time link with libasound */
95 #define alsa_pcm_subformat_mask_malloc          snd_pcm_subformat_mask_malloc
96 #define alsa_strerror                           snd_strerror
97 #define alsa_pcm_hw_params_any                  snd_pcm_hw_params_any
98 #define alsa_pcm_hw_params                      snd_pcm_hw_params
99 #define alsa_pcm_hw_params_current              snd_pcm_hw_params_current
100 #define alsa_pcm_hw_params_set_access           snd_pcm_hw_params_set_access
101 #define alsa_pcm_hw_params_set_format           snd_pcm_hw_params_set_format
102 #define alsa_pcm_hw_params_set_rate_near        snd_pcm_hw_params_set_rate_near
103 #define alsa_pcm_hw_params_set_channels_near    snd_pcm_hw_params_set_channels_near
104 #define alsa_pcm_hw_params_set_buffer_time_near snd_pcm_hw_params_set_buffer_time_near
105 #define alsa_pcm_hw_params_set_period_time_near snd_pcm_hw_params_set_period_time_near
106 #define alsa_pcm_hw_params_get_buffer_size      snd_pcm_hw_params_get_buffer_size
107 #define alsa_pcm_hw_params_get_period_size      snd_pcm_hw_params_get_period_size
108 #define alsa_pcm_resume                         snd_pcm_resume
109 #define alsa_pcm_prepare                        snd_pcm_prepare
110 #define alsa_pcm_close                          snd_pcm_close
111 #define alsa_pcm_drain                          snd_pcm_drain
112 #define alsa_pcm_drop                           snd_pcm_drop
113 #define alsa_pcm_start                          snd_pcm_start
114 #define alsa_pcm_open                           snd_pcm_open
115 #define alsa_pcm_writei                         snd_pcm_writei
116 #endif /* MIKMOD_DYNAMIC */
117
118 #if defined(MIKMOD_DEBUG)
119 # define dbgprint                       fprintf
120 #elif defined (__GNUC__) && !(defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L)
121 # define dbgprint(f, fmt, args...)      do {} while (0)
122 #else
123 # define dbgprint(f, ...)               do {} while (0)
124 #endif
125 static BOOL enabled = 0;
126 static snd_pcm_t *pcm_h = NULL;
127 static SBYTE *audiobuffer = NULL;
128 static snd_pcm_sframes_t period_size;
129 static int bytes_written = 0, bytes_played = 0;
130 static int global_frame_size;
131
132 #ifdef MIKMOD_DYNAMIC
133 static int ALSA_Link(void)
134 {
135         if (libasound) return 0;
136
137         /* load libasound.so */
138         libasound = dlopen("libasound.so.2",RTLD_LAZY|RTLD_GLOBAL);
139         if (!libasound) libasound = dlopen("libasound.so",RTLD_LAZY|RTLD_GLOBAL);
140         if (!libasound) return 1;
141
142         if (!(alsa_pcm_subformat_mask_malloc = (int (*)(snd_pcm_subformat_mask_t **))
143                                                  dlsym(libasound,"snd_pcm_subformat_mask_malloc"))) return 1;
144         if (!(alsa_strerror = (const char* (*)(int))
145                                                  dlsym(libasound,"snd_strerror"))) return 1;
146         if (!(alsa_pcm_prepare = (int (*)(snd_pcm_t *))
147                                                  dlsym(libasound,"snd_pcm_prepare"))) return 1;
148         if (!(alsa_pcm_sw_params_sizeof = (int (*)(void))
149                                                  dlsym(libasound,"snd_pcm_sw_params_sizeof"))) return 1;
150         if (!(alsa_pcm_hw_params_sizeof = (int (*)(void))
151                                                  dlsym(libasound,"snd_pcm_hw_params_sizeof"))) return 1;
152         if (!(alsa_pcm_resume = (int (*)(snd_pcm_t *))
153                                                  dlsym(libasound,"snd_pcm_resume"))) return 1;
154         if (!(alsa_pcm_hw_params_any = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *))
155                                                  dlsym(libasound,"snd_pcm_hw_params_any"))) return 1;
156         if (!(alsa_pcm_hw_params = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *))
157                                                  dlsym(libasound,"snd_pcm_hw_params"))) return 1;
158         if (!(alsa_pcm_hw_params_current = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *))
159                                                  dlsym(libasound,"snd_pcm_hw_params_current"))) return 1;
160         if (!(alsa_pcm_hw_params_set_access = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t))
161                                                  dlsym(libasound,"snd_pcm_hw_params_set_access"))) return 1;
162         if (!(alsa_pcm_hw_params_set_format = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t))
163                                                  dlsym(libasound,"snd_pcm_hw_params_set_format"))) return 1;
164         if (!(alsa_pcm_hw_params_set_rate_near = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *))
165                                                  dlsym(libasound,"snd_pcm_hw_params_set_rate_near"))) return 1;
166         if (!(alsa_pcm_hw_params_set_channels_near = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *))
167                                                  dlsym(libasound,"snd_pcm_hw_params_set_channels_near"))) return 1;
168         if (!(alsa_pcm_hw_params_set_buffer_time_near = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *))
169                                                  dlsym(libasound,"snd_pcm_hw_params_set_buffer_time_near"))) return 1;
170         if (!(alsa_pcm_hw_params_set_period_time_near = (int (*)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *))
171                                                  dlsym(libasound,"snd_pcm_hw_params_set_period_time_near"))) return 1;
172         if (!(alsa_pcm_hw_params_get_buffer_size = (int (*)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *))
173                                                  dlsym(libasound,"snd_pcm_hw_params_get_buffer_size"))) return 1;
174         if (!(alsa_pcm_hw_params_get_period_size = (int (*)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *))
175                                                  dlsym(libasound,"snd_pcm_hw_params_get_period_size"))) return 1;
176         if (!(alsa_pcm_open = (int (*)(snd_pcm_t**, const char *, int, int))
177                                                  dlsym(libasound,"snd_pcm_open"))) return 1;
178         if (!(alsa_pcm_close = (int (*)(snd_pcm_t*))
179                                                  dlsym(libasound,"snd_pcm_close"))) return 1;
180         if (!(alsa_pcm_drain = (int (*)(snd_pcm_t*))
181                                                  dlsym(libasound,"snd_pcm_drain"))) return 1;
182         if (!(alsa_pcm_drop = (int (*)(snd_pcm_t*))
183                                                  dlsym(libasound,"snd_pcm_drop"))) return 1;
184         if (!(alsa_pcm_start = (int (*)(snd_pcm_t *))
185                                                  dlsym(libasound,"snd_pcm_start"))) return 1;
186         if (!(alsa_pcm_writei = (snd_pcm_sframes_t (*)(snd_pcm_t*,const void*,snd_pcm_uframes_t))
187                                                  dlsym(libasound,"snd_pcm_writei"))) return 1;
188
189         return 0;
190 }
191
192 static void ALSA_Unlink(void)
193 {
194         alsa_pcm_subformat_mask_malloc = NULL;
195         alsa_strerror = NULL;
196         alsa_pcm_resume = NULL;
197         alsa_pcm_prepare = NULL;
198         alsa_pcm_hw_params_any = NULL;
199         alsa_pcm_hw_params = NULL;
200         alsa_pcm_hw_params_current = NULL;
201         alsa_pcm_hw_params_set_access = NULL;
202         alsa_pcm_hw_params_set_format = NULL;
203         alsa_pcm_hw_params_set_rate_near = NULL;
204         alsa_pcm_hw_params_set_channels_near = NULL;
205         alsa_pcm_hw_params_set_buffer_time_near = NULL;
206         alsa_pcm_hw_params_set_period_time_near = NULL;
207         alsa_pcm_hw_params_get_buffer_size = NULL;
208         alsa_pcm_hw_params_get_period_size = NULL;
209         alsa_pcm_close = NULL;
210         alsa_pcm_drain = NULL;
211         alsa_pcm_drop = NULL;
212         alsa_pcm_start = NULL;
213         alsa_pcm_open = NULL;
214         alsa_pcm_writei = NULL;
215
216         if (libasound) {
217                 dlclose(libasound);
218                 libasound = NULL;
219         }
220 }
221
222 /* This is done to override the identifiers expanded
223  * in the macros provided by the ALSA includes which are
224  * not available.
225  * */
226 #define snd_strerror                    alsa_strerror
227 #define snd_pcm_sw_params_sizeof        alsa_pcm_sw_params_sizeof
228 #define snd_pcm_hw_params_sizeof        alsa_pcm_hw_params_sizeof
229 #endif /* MIKMOD_DYNAMIC */
230
231 static void ALSA_CommandLine(const CHAR *cmdline)
232 {
233                 /* no options */
234 }
235
236 static BOOL ALSA_IsThere(void)
237 {
238         snd_pcm_subformat_mask_t *ptr = NULL;
239         BOOL retval;
240
241 #ifdef MIKMOD_DYNAMIC
242         if (ALSA_Link()) return 0;
243 #endif
244         retval = (alsa_pcm_subformat_mask_malloc(&ptr) == 0) && (ptr != NULL);
245         free(ptr);
246 #ifdef MIKMOD_DYNAMIC
247         ALSA_Unlink();
248 #endif
249         return retval;
250 }
251
252 static int ALSA_Init_internal(void)
253 {
254         snd_pcm_format_t pformat;
255         unsigned int btime = 250000;    /* 250ms */
256         unsigned int ptime = 50000;     /* 50ms */
257         snd_pcm_uframes_t psize;
258         snd_pcm_uframes_t bsize;
259         unsigned int rate, channels;
260         snd_pcm_hw_params_t * hwparams;
261         int err;
262
263         /* setup playback format structure */
264         pformat = (md_mode&DMODE_FLOAT)? SND_PCM_FORMAT_FLOAT :
265                         (md_mode&DMODE_16BITS)? SND_PCM_FORMAT_S16 : SND_PCM_FORMAT_U8;
266         channels = (md_mode&DMODE_STEREO)?2:1;
267         rate = md_mixfreq;
268
269 #define MIKMOD_ALSA_DEVICE "default"
270         if ((err = alsa_pcm_open(&pcm_h, MIKMOD_ALSA_DEVICE, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
271                 _mm_errno = MMERR_OPENING_AUDIO;
272                 goto END;
273         }
274
275         snd_pcm_hw_params_alloca(&hwparams);
276         err = alsa_pcm_hw_params_any(pcm_h, hwparams);
277         if (err < 0) {
278                 _mm_errno = MMERR_ALSA_NOCONFIG;
279                 goto END;
280         }
281
282         err = alsa_pcm_hw_params_set_access(pcm_h, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
283         if (!err) err = alsa_pcm_hw_params_set_format(pcm_h, hwparams, pformat);
284         if (!err) err = alsa_pcm_hw_params_set_rate_near(pcm_h, hwparams, &rate, NULL);
285         if (!err) err = alsa_pcm_hw_params_set_channels_near(pcm_h, hwparams, &channels);
286         if (!err) err = alsa_pcm_hw_params_set_buffer_time_near(pcm_h, hwparams, &btime, NULL);
287         if (!err) err = alsa_pcm_hw_params_set_period_time_near(pcm_h, hwparams, &ptime, NULL);
288         if (!err) err = alsa_pcm_hw_params(pcm_h, hwparams);
289         if (err < 0) {
290                 _mm_errno = MMERR_ALSA_SETPARAMS;
291                 goto END;
292         }
293
294         if (rate != md_mixfreq) {
295                 _mm_errno = MMERR_ALSA_SETRATE;
296                 goto END;
297         }
298         if (!(md_mode&DMODE_STEREO) && channels != 1) {
299                 _mm_errno = MMERR_ALSA_SETCHANNELS;
300                 goto END;
301         }
302         if ((md_mode&DMODE_STEREO) && channels != 2) {
303                 _mm_errno = MMERR_ALSA_SETCHANNELS;
304                 goto END;
305         }
306
307         err = alsa_pcm_hw_params_current(pcm_h, hwparams);
308         if (!err) err = alsa_pcm_hw_params_get_buffer_size(hwparams, &bsize);
309         if (!err) err = alsa_pcm_hw_params_get_period_size(hwparams, &psize, NULL);
310         if (err < 0) {
311                 _mm_errno = MMERR_ALSA_BUFFERSIZE;
312                 goto END;
313         }
314
315         period_size = psize;
316         global_frame_size = channels *
317                                 ((md_mode&DMODE_FLOAT)? 4 : (md_mode&DMODE_16BITS)? 2 : 1);
318
319         if (!(audiobuffer=(SBYTE*)MikMod_malloc(period_size * global_frame_size))) {
320                 _mm_errno = MMERR_OUT_OF_MEMORY;
321                 goto END;
322         }
323
324         /* sound device is ready to work */
325         if (!VC_Init()) {
326                 enabled = 1;
327                 return 0;
328         }
329 END:
330         alsa_pcm_close(pcm_h);
331         pcm_h = NULL;
332         return 1;
333 }
334
335 static int ALSA_Init(void)
336 {
337 #ifdef HAVE_SSE2
338 /* TODO : Detect SSE2, then set  md_mode |= DMODE_SIMDMIXER;*/
339 #endif
340 #ifdef MIKMOD_DYNAMIC
341         if (ALSA_Link()) {
342                 _mm_errno=MMERR_DYNAMIC_LINKING;
343                 return 1;
344         }
345 #endif
346         return ALSA_Init_internal();
347 }
348
349 static void ALSA_Exit_internal(void)
350 {
351         enabled = 0;
352         VC_Exit();
353         if (pcm_h) {
354                 alsa_pcm_drain(pcm_h);
355                 alsa_pcm_close(pcm_h);
356                 pcm_h = NULL;
357         }
358         MikMod_free(audiobuffer);
359         audiobuffer = NULL;
360 }
361
362 static void ALSA_Exit(void)
363 {
364         ALSA_Exit_internal();
365 #ifdef MIKMOD_DYNAMIC
366         ALSA_Unlink();
367 #endif
368 }
369
370 /* Underrun and suspend recovery - from alsa-lib:test/pcm.c
371  */
372 static int xrun_recovery(snd_pcm_t *handle, int err)
373 {
374         if (err == -EPIPE) {    /* under-run */
375                 err = alsa_pcm_prepare(handle);
376                 if (err < 0)
377                         dbgprint(stderr, "Can't recover from underrun, prepare failed: %s\n", snd_strerror(err));
378                 return 0;
379         }
380         else if (err == -ESTRPIPE) {
381                 while ((err = alsa_pcm_resume(handle)) == -EAGAIN)
382                         sleep(1);       /* wait until the suspend flag is released */
383                 if (err < 0) {
384                         err = alsa_pcm_prepare(handle);
385                         if (err < 0)
386                                 dbgprint(stderr, "Can't recover from suspend, prepare failed: %s\n", snd_strerror(err));
387                 }
388                 return 0;
389         }
390         return err;
391 }
392
393 static void ALSA_Update(void)
394 {
395         int err;
396
397         if (!enabled) return;
398
399         if (bytes_written == 0 || bytes_played == bytes_written) {
400                 bytes_written = VC_WriteBytes(audiobuffer,period_size * global_frame_size);
401                 bytes_played = 0;
402         }
403
404         while (bytes_played < bytes_written)
405         {
406                 err = alsa_pcm_writei(pcm_h, &audiobuffer[bytes_played], (bytes_written - bytes_played) / global_frame_size);
407                 if (err == -EAGAIN)
408                         continue;
409                 if (err < 0) {
410                         if ((err = xrun_recovery(pcm_h, err)) < 0) {
411                                 _mm_errno = MMERR_ALSA_PCM_RECOVER;
412                                 enabled = 0;
413                                 dbgprint(stderr, "Write error: %s\n", alsa_strerror(err));
414                         }
415                         break;
416                 }
417                 bytes_played += err * global_frame_size;
418         }
419 }
420
421 static int ALSA_PlayStart(void)
422 {
423         int err;
424
425         if (pcm_h == NULL) return 1;
426         err = alsa_pcm_prepare(pcm_h);
427         if (err == 0)
428             err = alsa_pcm_start(pcm_h);
429         if (err < 0) {
430                 enabled = 0;
431                 _mm_errno = MMERR_ALSA_PCM_START;
432                 return 1;
433         }
434
435         return VC_PlayStart();
436 }
437
438 static void ALSA_PlayStop(void)
439 {
440         VC_PlayStop();
441         if (pcm_h) alsa_pcm_drop(pcm_h);
442 }
443
444 static int ALSA_Reset(void)
445 {
446         ALSA_Exit_internal();
447         return ALSA_Init_internal();
448 }
449
450 MIKMODAPI MDRIVER drv_alsa = {
451         NULL,
452         "ALSA",
453         "Advanced Linux Sound Architecture (ALSA) driver v1.11",
454         0,255,
455         "alsa",
456         NULL,
457         ALSA_CommandLine,
458         ALSA_IsThere,
459         VC_SampleLoad,
460         VC_SampleUnload,
461         VC_SampleSpace,
462         VC_SampleLength,
463         ALSA_Init,
464         ALSA_Exit,
465         ALSA_Reset,
466         VC_SetNumVoices,
467         ALSA_PlayStart,
468         ALSA_PlayStop,
469         ALSA_Update,
470         NULL,
471         VC_VoiceSetVolume,
472         VC_VoiceGetVolume,
473         VC_VoiceSetFrequency,
474         VC_VoiceGetFrequency,
475         VC_VoiceSetPanning,
476         VC_VoiceGetPanning,
477         VC_VoicePlay,
478         VC_VoiceStop,
479         VC_VoiceStopped,
480         VC_VoiceGetPosition,
481         VC_VoiceRealVolume
482 };
483
484 #else
485
486 MISSING(drv_alsa);
487
488 #endif
489
490 /* ex:set ts=8: */