/* 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 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 #endif #include #ifdef HAVE_FCNTL_H #include #endif #include #include #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_SYS_SOUNDCARD_H #include /* Linux and newer BSD versions - OSS standart */ #elif defined(HAVE_MACHINE_SOUNDCARD_H) #include /* Some old BSD versions */ #elif defined(HAVE_SOUNDCARD_H) #include /* 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: */