added page flipping/scrolling VBE calls
[dosrtxon] / libs / mikmod / drivers / drv_oss.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 output on Linux and FreeBSD Open Sound System (OSS) (/dev/dsp)
26
27 ==============================================================================*/
28
29 /*
30
31         Written by Chris Conn <cconn@tohs.abacom.com>
32
33         Extended by Miodrag Vallat:
34         - compatible with all OSS/Voxware versions on Linux/i386, at least
35         - support for uLaw output (for sparc systems)
36
37 */
38
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42
43 #include "mikmod_internals.h"
44
45 #ifdef DRV_OSS
46
47 #ifdef HAVE_UNISTD_H
48 #include <unistd.h>
49 #endif
50 #include <errno.h>
51 #ifdef HAVE_FCNTL_H
52 #include <fcntl.h>
53 #endif
54 #include <string.h>
55 #include <stdlib.h>
56 #ifdef HAVE_SYS_IOCTL_H
57 #include <sys/ioctl.h>
58 #endif
59 #ifdef HAVE_SYS_SOUNDCARD_H
60 #include <sys/soundcard.h> /* Linux and newer BSD versions - OSS standart */
61 #elif defined(HAVE_MACHINE_SOUNDCARD_H)
62 #include <machine/soundcard.h> /*  Some old BSD versions */
63 #elif defined(HAVE_SOUNDCARD_H)
64 #include <soundcard.h> /* Some old BSD versions and also newer OpenBSD versions */
65 #endif
66
67 /* Compatibility with old versions of OSS
68    (Voxware <= 2.03, Linux kernel < 1.1.31) */
69 #ifndef AFMT_S16_LE
70 #define AFMT_S16_LE 16
71 #endif
72 #ifndef AFMT_S16_BE
73 #define AFMT_S16_BE 0x20
74 #endif
75 #ifndef AFMT_U8
76 #define AFMT_U8 8
77 #endif
78 #ifndef SNDCTL_DSP_SETFMT
79 #define SNDCTL_DSP_SETFMT SNDCTL_DSP_SAMPLESIZE
80 #endif
81
82 /* Compatibility with not-so-old versions of OSS
83    (OSS < 3.7, Linux kernel < 2.1.16) */
84 #ifndef AFMT_S16_NE
85 #if defined(_AIX) || defined(AIX) || defined(sparc) || defined(HPPA) || defined(PPC)
86 #define AFMT_S16_NE AFMT_S16_BE
87 #else
88 #define AFMT_S16_NE AFMT_S16_LE
89 #endif
90 #endif
91
92 /* Compatibility with OSS 4.x: AFMT_FLOAT is documented
93    as "not recommended" on the OSS website.  This post
94    on the OSS mailing lists says: "In general AFMT_FLOAT
95    is not supported by OSS except in some special cases."
96    (http://sf.net/p/opensound/mailman/message/28840674/) */
97 #ifndef AFMT_FLOAT
98 #define AFMT_FLOAT 0x00004000
99 #endif
100
101 static  int sndfd=-1;
102 static  SBYTE *audiobuffer=NULL;
103 static  int buffersize;
104 static  int play_precision;
105
106 #define DEFAULT_CARD 0
107 static  int card=DEFAULT_CARD;
108
109 #ifdef SNDCTL_DSP_SETFRAGMENT
110
111 #define DEFAULT_FRAGSIZE 14
112 #define DEFAULT_NUMFRAGS 16
113
114 static  int fragsize=DEFAULT_FRAGSIZE;
115 static  int numfrags=DEFAULT_NUMFRAGS;
116
117 #endif
118
119 static void OSS_CommandLine(const CHAR *cmdline)
120 {
121         CHAR *ptr;
122
123 #ifdef SNDCTL_DSP_SETFRAGMENT
124         if((ptr=MD_GetAtom("buffer",cmdline,0)) != NULL) {
125                 fragsize=atoi(ptr);
126                 if((fragsize<7)||(fragsize>17)) fragsize=DEFAULT_FRAGSIZE;
127                 MikMod_free(ptr);
128         }
129         if((ptr=MD_GetAtom("count",cmdline,0)) != NULL) {
130                 numfrags=atoi(ptr);
131                 if((numfrags<2)||(numfrags>255)) numfrags=DEFAULT_NUMFRAGS;
132                 MikMod_free(ptr);
133         }
134 #endif
135         if((ptr=MD_GetAtom("card",cmdline,0)) != NULL) {
136                 card = atoi(ptr);
137                 if((card<0)||(card>99)) card=DEFAULT_CARD;
138                 MikMod_free(ptr);
139         }
140 }
141
142 static char *OSS_GetDeviceName(void)
143 {
144         static char sounddevice[20];
145
146         /* First test for devfs enabled Linux sound devices */
147         if (card)
148                 sprintf(sounddevice,"/dev/sound/dsp%d",card);
149         else
150                 strcpy(sounddevice,"/dev/sound/dsp");
151         if(!access(sounddevice,F_OK))
152                 return sounddevice;
153
154         sprintf(sounddevice,"/dev/dsp%d",card);
155         if (!card) {
156                 /* prefer /dev/dsp0 over /dev/dsp, as /dev/dsp might be a symbolic link
157                    to something else than /dev/dsp0. Revert to /dev/dsp if /dev/dsp0
158                    does not exist. */
159                 if(access("/dev/dsp0",F_OK))
160                         strcpy(sounddevice,"/dev/dsp");
161         }
162
163         return sounddevice;
164 }
165
166 static BOOL OSS_IsThere(void)
167 {
168         /* under Linux, and perhaps other Unixes, access()ing the device is not
169            enough since it won't fail if the machine doesn't have sound support
170            in the kernel or sound hardware                                      */
171         int fd;
172
173         if((fd=open(OSS_GetDeviceName(),O_WRONLY|O_NONBLOCK))>=0) {
174                 close(fd);
175                 return 1;
176         }
177         return (errno==EACCES?1:0);
178 }
179
180 static int OSS_Init_internal(void)
181 {
182         int play_stereo,play_rate;
183         int orig_precision,orig_stereo;
184         int formats;
185 #if SOUND_VERSION >= 301
186         audio_buf_info buffinf;
187 #endif
188
189 #ifdef SNDCTL_DSP_GETFMTS
190         /* Ask device for supported formats */
191         if(ioctl(sndfd,SNDCTL_DSP_GETFMTS,&formats)<0) {
192                 _mm_errno=MMERR_OPENING_AUDIO;
193                 return 1;
194         }
195 #else
196         formats=AFMT_S16_NE|AFMT_U8;
197 #endif
198
199         orig_precision=play_precision=(md_mode&DMODE_FLOAT)? AFMT_FLOAT :
200                                         (md_mode&DMODE_16BITS)? AFMT_S16_NE : AFMT_U8;
201
202     /* Device does not support the format we would prefer... */
203     if(!(formats & play_precision)) {
204         if(play_precision==AFMT_FLOAT) {
205             _mm_errno=MMERR_NO_FLOAT32;
206             return 1;
207         }
208         /* We could try 8 bit sound if available */
209         if(play_precision==AFMT_S16_NE &&(formats&AFMT_U8)) {
210             _mm_errno=MMERR_8BIT_ONLY;
211             return 1;
212         }
213 #ifdef AFMT_MU_LAW
214         /* We could try uLaw if available */
215         if(formats&AFMT_MU_LAW) {
216             if((md_mode&DMODE_STEREO)||(md_mode&DMODE_16BITS)||
217                 md_mixfreq!=8000) {
218                 _mm_errno=MMERR_ULAW;
219                 return 1;
220             } else
221                 orig_precision=play_precision=AFMT_MU_LAW;
222         } else
223 #endif
224             /* Otherwise, just abort */
225         {
226             _mm_errno=MMERR_OSS_SETSAMPLESIZE;
227             return 1;
228         }
229     }
230
231         if((ioctl(sndfd,SNDCTL_DSP_SETFMT,&play_precision)<0)||
232            (orig_precision!=play_precision)) {
233                 _mm_errno=MMERR_OSS_SETSAMPLESIZE;
234                 return 1;
235         }
236 #ifdef SNDCTL_DSP_CHANNELS
237         orig_stereo=play_stereo=(md_mode&DMODE_STEREO)?2:1;
238         if((ioctl(sndfd,SNDCTL_DSP_CHANNELS,&play_stereo)<0)||
239            (orig_stereo!=play_stereo)) {
240                 _mm_errno=MMERR_OSS_SETSTEREO;
241                 return 1;
242         }
243 #else
244         orig_stereo=play_stereo=(md_mode&DMODE_STEREO)?1:0;
245         if((ioctl(sndfd,SNDCTL_DSP_STEREO,&play_stereo)<0)||
246            (orig_stereo!=play_stereo)) {
247                 _mm_errno=MMERR_OSS_SETSTEREO;
248                 return 1;
249         }
250 #endif
251
252         play_rate=md_mixfreq;
253         if((ioctl(sndfd,SNDCTL_DSP_SPEED,&play_rate)<0)) {
254                 _mm_errno=MMERR_OSS_SETSPEED;
255                 return 1;
256         }
257         md_mixfreq=play_rate;
258
259 #if SOUND_VERSION >= 301
260         /* This call fails on Linux/PPC */
261         if((ioctl(sndfd,SNDCTL_DSP_GETOSPACE,&buffinf)<0))
262                 ioctl(sndfd,SNDCTL_DSP_GETBLKSIZE,&buffinf.fragsize);
263         if(!(audiobuffer=(SBYTE*)MikMod_malloc(buffinf.fragsize)))
264                 return 1;
265
266         buffersize = buffinf.fragsize;
267 #else
268         ioctl(sndfd,SNDCTL_DSP_GETBLKSIZE,&buffersize);
269         if(!(audiobuffer=(SBYTE*)MikMod_malloc(buffersize)))
270                 return 1;
271 #endif
272
273         return VC_Init();
274 }
275
276 static int OSS_Init(void)
277 {
278 #ifdef SNDCTL_DSP_SETFRAGMENT
279         int fragmentsize;
280 #endif
281
282         if((sndfd=open(OSS_GetDeviceName(),O_WRONLY))<0) {
283                 _mm_errno=MMERR_OPENING_AUDIO;
284                 return 1;
285         }
286
287 #ifdef SNDCTL_DSP_SETFRAGMENT
288         if((fragsize==DEFAULT_FRAGSIZE)&&(getenv("MM_FRAGSIZE"))) {
289                 fragsize=atoi(getenv("MM_FRAGSIZE"));
290                 if((fragsize<7)||(fragsize>17)) fragsize=DEFAULT_FRAGSIZE;
291         }
292         if((numfrags==DEFAULT_NUMFRAGS)&&(getenv("MM_NUMFRAGS"))) {
293                 numfrags=atoi(getenv("MM_NUMFRAGS"));
294                 if((numfrags<2)||(numfrags>255)) numfrags=DEFAULT_NUMFRAGS;
295         }
296
297         fragmentsize=(numfrags<<16)|fragsize;
298
299         if(ioctl(sndfd,SNDCTL_DSP_SETFRAGMENT,&fragmentsize)<0) {
300                 _mm_errno=MMERR_OSS_SETFRAGMENT;
301                 return 1;
302         }
303 #endif
304
305         return OSS_Init_internal();
306 }
307
308 static void OSS_Exit_internal(void)
309 {
310         VC_Exit();
311         MikMod_free(audiobuffer);
312         audiobuffer = NULL;
313 }
314
315 static void OSS_Exit(void)
316 {
317         OSS_Exit_internal();
318
319         if (sndfd>=0) {
320                 close(sndfd);
321                 sndfd=-1;
322         }
323 }
324
325 static void OSS_PlayStop(void)
326 {
327         VC_PlayStop();
328
329         ioctl(sndfd,SNDCTL_DSP_POST,0);
330 }
331
332 static void OSS_Update(void)
333 {
334         int done;
335
336 #if SOUND_VERSION >= 301
337         audio_buf_info buffinf;
338
339         buffinf.fragments = 2;
340         for(;;) {
341                 /* This call fails on Linux/PPC */
342                 if ((ioctl(sndfd,SNDCTL_DSP_GETOSPACE,&buffinf)<0)) {
343                         buffinf.fragments--;
344                         buffinf.fragsize = buffinf.bytes = buffersize;
345                 }
346                 if(!buffinf.fragments)
347                         break;
348                 done=VC_WriteBytes(audiobuffer,buffinf.fragsize>buffinf.bytes?
349                                                    buffinf.bytes:buffinf.fragsize);
350 #ifdef AFMT_MU_LAW
351                 if (play_precision==AFMT_MU_LAW)
352                         unsignedtoulaw((char *)audiobuffer,done);
353 #endif
354                 write(sndfd,audiobuffer,done);
355         }
356 #else
357         done=VC_WriteBytes(audiobuffer,buffersize);
358 #ifdef AFMT_MU_LAW
359         if (play_precision==AFMT_MU_LAW)
360                 unsignedtoulaw(audiobuffer,done);
361 #endif
362         write(sndfd,audiobuffer,done);
363 #endif
364 }
365
366 static int OSS_Reset(void)
367 {
368         OSS_Exit_internal();
369         ioctl(sndfd,SNDCTL_DSP_RESET,0);
370         return OSS_Init_internal();
371 }
372
373 MIKMODAPI MDRIVER drv_oss={
374         NULL,
375         "Open Sound System",
376         "Open Sound System driver v1.7",
377         0,255,
378         "oss",
379 #ifdef SNDCTL_DSP_SETFRAGMENT
380         "buffer:r:7,17,14:Audio buffer log2 size\n"
381         "count:r:2,255,16:Audio buffer count\n"
382 #endif
383         "card:r:0,99,0:Sound card id\n",
384         OSS_CommandLine,
385         OSS_IsThere,
386         VC_SampleLoad,
387         VC_SampleUnload,
388         VC_SampleSpace,
389         VC_SampleLength,
390         OSS_Init,
391         OSS_Exit,
392         OSS_Reset,
393         VC_SetNumVoices,
394         VC_PlayStart,
395         OSS_PlayStop,
396         OSS_Update,
397         NULL,
398         VC_VoiceSetVolume,
399         VC_VoiceGetVolume,
400         VC_VoiceSetFrequency,
401         VC_VoiceGetFrequency,
402         VC_VoiceSetPanning,
403         VC_VoiceGetPanning,
404         VC_VoicePlay,
405         VC_VoiceStop,
406         VC_VoiceStopped,
407         VC_VoiceGetPosition,
408         VC_VoiceRealVolume
409 };
410
411 #else
412
413 MISSING(drv_oss);
414
415 #endif
416
417 /* ex:set ts=4: */