add mikmod source
[dosdemo] / libs / mikmod / drivers / drv_oss.c
diff --git a/libs/mikmod/drivers/drv_oss.c b/libs/mikmod/drivers/drv_oss.c
new file mode 100644 (file)
index 0000000..64957f7
--- /dev/null
@@ -0,0 +1,417 @@
+/*     MikMod sound library
+       (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for
+       complete list.
+
+       This library is free software; you can redistribute it and/or modify
+       it under the terms of the GNU Library General Public License as
+       published by the Free Software Foundation; either version 2 of
+       the License, or (at your option) any later version.
+
+       This program is distributed in the hope that it will be useful,
+       but WITHOUT ANY WARRANTY; without even the implied warranty of
+       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+       GNU Library General Public License for more details.
+
+       You should have received a copy of the GNU Library General Public
+       License along with this library; if not, write to the Free Software
+       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+       02111-1307, USA.
+*/
+
+/*==============================================================================
+
+  $Id$
+
+  Driver for output on Linux and FreeBSD Open Sound System (OSS) (/dev/dsp)
+
+==============================================================================*/
+
+/*
+
+       Written by Chris Conn <cconn@tohs.abacom.com>
+
+       Extended by Miodrag Vallat:
+       - compatible with all OSS/Voxware versions on Linux/i386, at least
+       - support for uLaw output (for sparc systems)
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "mikmod_internals.h"
+
+#ifdef DRV_OSS
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_SOUNDCARD_H
+#include <sys/soundcard.h> /* Linux and newer BSD versions - OSS standart */
+#elif defined(HAVE_MACHINE_SOUNDCARD_H)
+#include <machine/soundcard.h> /*  Some old BSD versions */
+#elif defined(HAVE_SOUNDCARD_H)
+#include <soundcard.h> /* Some old BSD versions and also newer OpenBSD versions */
+#endif
+
+/* Compatibility with old versions of OSS
+   (Voxware <= 2.03, Linux kernel < 1.1.31) */
+#ifndef AFMT_S16_LE
+#define AFMT_S16_LE 16
+#endif
+#ifndef AFMT_S16_BE
+#define AFMT_S16_BE 0x20
+#endif
+#ifndef AFMT_U8
+#define AFMT_U8 8
+#endif
+#ifndef SNDCTL_DSP_SETFMT
+#define SNDCTL_DSP_SETFMT SNDCTL_DSP_SAMPLESIZE
+#endif
+
+/* Compatibility with not-so-old versions of OSS
+   (OSS < 3.7, Linux kernel < 2.1.16) */
+#ifndef AFMT_S16_NE
+#if defined(_AIX) || defined(AIX) || defined(sparc) || defined(HPPA) || defined(PPC)
+#define AFMT_S16_NE AFMT_S16_BE
+#else
+#define AFMT_S16_NE AFMT_S16_LE
+#endif
+#endif
+
+/* Compatibility with OSS 4.x: AFMT_FLOAT is documented
+   as "not recommended" on the OSS website.  This post
+   on the OSS mailing lists says: "In general AFMT_FLOAT
+   is not supported by OSS except in some special cases."
+   (http://sf.net/p/opensound/mailman/message/28840674/) */
+#ifndef AFMT_FLOAT
+#define AFMT_FLOAT 0x00004000
+#endif
+
+static int sndfd=-1;
+static SBYTE *audiobuffer=NULL;
+static int buffersize;
+static int play_precision;
+
+#define DEFAULT_CARD 0
+static int card=DEFAULT_CARD;
+
+#ifdef SNDCTL_DSP_SETFRAGMENT
+
+#define DEFAULT_FRAGSIZE 14
+#define DEFAULT_NUMFRAGS 16
+
+static int fragsize=DEFAULT_FRAGSIZE;
+static int numfrags=DEFAULT_NUMFRAGS;
+
+#endif
+
+static void OSS_CommandLine(const CHAR *cmdline)
+{
+       CHAR *ptr;
+
+#ifdef SNDCTL_DSP_SETFRAGMENT
+       if((ptr=MD_GetAtom("buffer",cmdline,0)) != NULL) {
+               fragsize=atoi(ptr);
+               if((fragsize<7)||(fragsize>17)) fragsize=DEFAULT_FRAGSIZE;
+               MikMod_free(ptr);
+       }
+       if((ptr=MD_GetAtom("count",cmdline,0)) != NULL) {
+               numfrags=atoi(ptr);
+               if((numfrags<2)||(numfrags>255)) numfrags=DEFAULT_NUMFRAGS;
+               MikMod_free(ptr);
+       }
+#endif
+       if((ptr=MD_GetAtom("card",cmdline,0)) != NULL) {
+               card = atoi(ptr);
+               if((card<0)||(card>99)) card=DEFAULT_CARD;
+               MikMod_free(ptr);
+       }
+}
+
+static char *OSS_GetDeviceName(void)
+{
+       static char sounddevice[20];
+
+       /* First test for devfs enabled Linux sound devices */
+       if (card)
+               sprintf(sounddevice,"/dev/sound/dsp%d",card);
+       else
+               strcpy(sounddevice,"/dev/sound/dsp");
+       if(!access(sounddevice,F_OK))
+               return sounddevice;
+
+       sprintf(sounddevice,"/dev/dsp%d",card);
+       if (!card) {
+               /* prefer /dev/dsp0 over /dev/dsp, as /dev/dsp might be a symbolic link
+                  to something else than /dev/dsp0. Revert to /dev/dsp if /dev/dsp0
+                  does not exist. */
+               if(access("/dev/dsp0",F_OK))
+                       strcpy(sounddevice,"/dev/dsp");
+       }
+
+       return sounddevice;
+}
+
+static BOOL OSS_IsThere(void)
+{
+       /* under Linux, and perhaps other Unixes, access()ing the device is not
+          enough since it won't fail if the machine doesn't have sound support
+          in the kernel or sound hardware                                      */
+       int fd;
+
+       if((fd=open(OSS_GetDeviceName(),O_WRONLY|O_NONBLOCK))>=0) {
+               close(fd);
+               return 1;
+       }
+       return (errno==EACCES?1:0);
+}
+
+static int OSS_Init_internal(void)
+{
+       int play_stereo,play_rate;
+       int orig_precision,orig_stereo;
+       int formats;
+#if SOUND_VERSION >= 301
+       audio_buf_info buffinf;
+#endif
+
+#ifdef SNDCTL_DSP_GETFMTS
+       /* Ask device for supported formats */
+       if(ioctl(sndfd,SNDCTL_DSP_GETFMTS,&formats)<0) {
+               _mm_errno=MMERR_OPENING_AUDIO;
+               return 1;
+       }
+#else
+       formats=AFMT_S16_NE|AFMT_U8;
+#endif
+
+       orig_precision=play_precision=(md_mode&DMODE_FLOAT)? AFMT_FLOAT :
+                                       (md_mode&DMODE_16BITS)? AFMT_S16_NE : AFMT_U8;
+
+    /* Device does not support the format we would prefer... */
+    if(!(formats & play_precision)) {
+        if(play_precision==AFMT_FLOAT) {
+            _mm_errno=MMERR_NO_FLOAT32;
+            return 1;
+        }
+        /* We could try 8 bit sound if available */
+        if(play_precision==AFMT_S16_NE &&(formats&AFMT_U8)) {
+            _mm_errno=MMERR_8BIT_ONLY;
+            return 1;
+        }
+#ifdef AFMT_MU_LAW
+        /* We could try uLaw if available */
+        if(formats&AFMT_MU_LAW) {
+            if((md_mode&DMODE_STEREO)||(md_mode&DMODE_16BITS)||
+                md_mixfreq!=8000) {
+                _mm_errno=MMERR_ULAW;
+                return 1;
+            } else
+                orig_precision=play_precision=AFMT_MU_LAW;
+        } else
+#endif
+            /* Otherwise, just abort */
+        {
+            _mm_errno=MMERR_OSS_SETSAMPLESIZE;
+            return 1;
+        }
+    }
+
+       if((ioctl(sndfd,SNDCTL_DSP_SETFMT,&play_precision)<0)||
+          (orig_precision!=play_precision)) {
+               _mm_errno=MMERR_OSS_SETSAMPLESIZE;
+               return 1;
+       }
+#ifdef SNDCTL_DSP_CHANNELS
+       orig_stereo=play_stereo=(md_mode&DMODE_STEREO)?2:1;
+       if((ioctl(sndfd,SNDCTL_DSP_CHANNELS,&play_stereo)<0)||
+          (orig_stereo!=play_stereo)) {
+               _mm_errno=MMERR_OSS_SETSTEREO;
+               return 1;
+       }
+#else
+       orig_stereo=play_stereo=(md_mode&DMODE_STEREO)?1:0;
+       if((ioctl(sndfd,SNDCTL_DSP_STEREO,&play_stereo)<0)||
+          (orig_stereo!=play_stereo)) {
+               _mm_errno=MMERR_OSS_SETSTEREO;
+               return 1;
+       }
+#endif
+
+       play_rate=md_mixfreq;
+       if((ioctl(sndfd,SNDCTL_DSP_SPEED,&play_rate)<0)) {
+               _mm_errno=MMERR_OSS_SETSPEED;
+               return 1;
+       }
+       md_mixfreq=play_rate;
+
+#if SOUND_VERSION >= 301
+       /* This call fails on Linux/PPC */
+       if((ioctl(sndfd,SNDCTL_DSP_GETOSPACE,&buffinf)<0))
+               ioctl(sndfd,SNDCTL_DSP_GETBLKSIZE,&buffinf.fragsize);
+       if(!(audiobuffer=(SBYTE*)MikMod_malloc(buffinf.fragsize)))
+               return 1;
+
+       buffersize = buffinf.fragsize;
+#else
+       ioctl(sndfd,SNDCTL_DSP_GETBLKSIZE,&buffersize);
+       if(!(audiobuffer=(SBYTE*)MikMod_malloc(buffersize)))
+               return 1;
+#endif
+
+       return VC_Init();
+}
+
+static int OSS_Init(void)
+{
+#ifdef SNDCTL_DSP_SETFRAGMENT
+       int fragmentsize;
+#endif
+
+       if((sndfd=open(OSS_GetDeviceName(),O_WRONLY))<0) {
+               _mm_errno=MMERR_OPENING_AUDIO;
+               return 1;
+       }
+
+#ifdef SNDCTL_DSP_SETFRAGMENT
+       if((fragsize==DEFAULT_FRAGSIZE)&&(getenv("MM_FRAGSIZE"))) {
+               fragsize=atoi(getenv("MM_FRAGSIZE"));
+               if((fragsize<7)||(fragsize>17)) fragsize=DEFAULT_FRAGSIZE;
+       }
+       if((numfrags==DEFAULT_NUMFRAGS)&&(getenv("MM_NUMFRAGS"))) {
+               numfrags=atoi(getenv("MM_NUMFRAGS"));
+               if((numfrags<2)||(numfrags>255)) numfrags=DEFAULT_NUMFRAGS;
+       }
+
+       fragmentsize=(numfrags<<16)|fragsize;
+
+       if(ioctl(sndfd,SNDCTL_DSP_SETFRAGMENT,&fragmentsize)<0) {
+               _mm_errno=MMERR_OSS_SETFRAGMENT;
+               return 1;
+       }
+#endif
+
+       return OSS_Init_internal();
+}
+
+static void OSS_Exit_internal(void)
+{
+       VC_Exit();
+       MikMod_free(audiobuffer);
+       audiobuffer = NULL;
+}
+
+static void OSS_Exit(void)
+{
+       OSS_Exit_internal();
+
+       if (sndfd>=0) {
+               close(sndfd);
+               sndfd=-1;
+       }
+}
+
+static void OSS_PlayStop(void)
+{
+       VC_PlayStop();
+
+       ioctl(sndfd,SNDCTL_DSP_POST,0);
+}
+
+static void OSS_Update(void)
+{
+       int done;
+
+#if SOUND_VERSION >= 301
+       audio_buf_info buffinf;
+
+       buffinf.fragments = 2;
+       for(;;) {
+               /* This call fails on Linux/PPC */
+               if ((ioctl(sndfd,SNDCTL_DSP_GETOSPACE,&buffinf)<0)) {
+                       buffinf.fragments--;
+                       buffinf.fragsize = buffinf.bytes = buffersize;
+               }
+               if(!buffinf.fragments)
+                       break;
+               done=VC_WriteBytes(audiobuffer,buffinf.fragsize>buffinf.bytes?
+                                                  buffinf.bytes:buffinf.fragsize);
+#ifdef AFMT_MU_LAW
+               if (play_precision==AFMT_MU_LAW)
+                       unsignedtoulaw((char *)audiobuffer,done);
+#endif
+               write(sndfd,audiobuffer,done);
+       }
+#else
+       done=VC_WriteBytes(audiobuffer,buffersize);
+#ifdef AFMT_MU_LAW
+       if (play_precision==AFMT_MU_LAW)
+               unsignedtoulaw(audiobuffer,done);
+#endif
+       write(sndfd,audiobuffer,done);
+#endif
+}
+
+static int OSS_Reset(void)
+{
+       OSS_Exit_internal();
+       ioctl(sndfd,SNDCTL_DSP_RESET,0);
+       return OSS_Init_internal();
+}
+
+MIKMODAPI MDRIVER drv_oss={
+       NULL,
+       "Open Sound System",
+       "Open Sound System driver v1.7",
+       0,255,
+       "oss",
+#ifdef SNDCTL_DSP_SETFRAGMENT
+       "buffer:r:7,17,14:Audio buffer log2 size\n"
+       "count:r:2,255,16:Audio buffer count\n"
+#endif
+       "card:r:0,99,0:Sound card id\n",
+       OSS_CommandLine,
+       OSS_IsThere,
+       VC_SampleLoad,
+       VC_SampleUnload,
+       VC_SampleSpace,
+       VC_SampleLength,
+       OSS_Init,
+       OSS_Exit,
+       OSS_Reset,
+       VC_SetNumVoices,
+       VC_PlayStart,
+       OSS_PlayStop,
+       OSS_Update,
+       NULL,
+       VC_VoiceSetVolume,
+       VC_VoiceGetVolume,
+       VC_VoiceSetFrequency,
+       VC_VoiceGetFrequency,
+       VC_VoiceSetPanning,
+       VC_VoiceGetPanning,
+       VC_VoicePlay,
+       VC_VoiceStop,
+       VC_VoiceStopped,
+       VC_VoiceGetPosition,
+       VC_VoiceRealVolume
+};
+
+#else
+
+MISSING(drv_oss);
+
+#endif
+
+/* ex:set ts=4: */