/* oss.c * * Open Sound System Sound Devicee * * $Id: oss.c,v 1.11 1996/11/09 21:08:04 jpaana Exp $ * * Copyright 1996 Petteri Kangaslampi and Jarno Paananen * * This file is part of the MIDAS Sound System, and may only be * used, modified and distributed under the terms of the MIDAS * Sound System license, LICENSE.TXT. By continuing to use, * modify or distribute this file you indicate that you have * read the license and understand and accept it fully. */ #include #include #include #include #include #include "lang.h" #include "mtypes.h" #include "errors.h" #include "sdevice.h" #include "mmem.h" #include "dsm.h" #include "mglobals.h" RCSID(char const *oss_rcsid = "$Id: oss.c,v 1.11 1996/11/09 21:08:04 jpaana Exp $";) #define DEVICE_NAME "/dev/dsp" #define open_mode O_WRONLY static int audio_fd; static int format_16bits = AFMT_S16_LE; static int format_8bits = AFMT_U8; static int format_stereo; static int format_mixingrate; static audio_buf_info info; static uchar *audioBuffer; static int audioBufferSize; /* Make that "e" smaller (to c or even a) if you have small DMA buffer */ static int numFragments = 0xffff000e; //#define DUMPBUFFER #define OSSVERSION 1.02 #define OSSVERSTR "1.02" /* Number of bits of accuracy in mixing for 8-bit output: */ #define MIX8BITS 12 /* Sound Device information */ /* Sound Card names: */ static char *ossCardName = "Unix Sound System output"; /* Sound Device internal static variables */ static unsigned mixRate, outputMode; static unsigned mixElemSize; static unsigned amplification; static unsigned updateMix; /* number of elements to mix between two updates */ static unsigned mixLeft; /* number of elements to mix before next update */ static unsigned bufferPos; /* mixing position inside buffer */ static uchar *ppTable; /* post-processing table for 8-bit output */ #ifdef DUMPBUFFER static FILE *buff; #endif /****************************************************************************\ * enum ossFunctIDs * ----------------- * Description: ID numbers for OSS Sound Device functions \****************************************************************************/ enum ossFunctIDs { ID_ossDetect = ID_oss, ID_ossInit, ID_ossClose, ID_ossGetMode, ID_ossOpenChannels, ID_ossSetAmplification, ID_ossGetAmplification, ID_ossSetUpdRate, ID_ossStartPlay, ID_ossPlay }; /* Local prototypes: */ int CALLING ossSetAmplification(unsigned _amplification); int CALLING ossGetAmplification(unsigned *_amplification); int CALLING ossSetUpdRate(unsigned updRate); /****************************************************************************\ * * Function: static unsigned CALLING (*postProc)(unsigned numElements, * uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, * uchar *ppTable); * * Description: Pointer to the actual post-processing routine. Takes * DSM output elements from dsmMixBuffer and writes them to * output buffer at *bufStart in a format suitable for the Sound * Device. * * Input: unsigned numElements number of elements to process * (guaranteed to be even) * uchar *bufStart pointer to start of output buffer * unsigned mixPos mixing position in output buffer * unsigned *mixBuffer source mixing buffer * uchar *ppTable pointer to post-processing table * * Returns: New mixing position in output buffer. Can not fail. * \****************************************************************************/ static unsigned CALLING (*postProc)(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp16Mono(); * * Description: 16-bit mono post-processing routine * \****************************************************************************/ unsigned CALLING pp16Mono(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp8Mono(); * * Description: 8-bit mono post-processing routine * \****************************************************************************/ unsigned CALLING pp8Mono(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp16Stereo(); * * Description: 16-bit stereo post-processing routine * \****************************************************************************/ unsigned CALLING pp16Stereo(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp8Stereo(); * * Description: 8-bit stereo post-processing routine * \****************************************************************************/ unsigned CALLING pp8Stereo(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: int ossDetect(int *result) * * Description: Detects a OSS Sound Device * * Input: int *result pointer to detection result * * Returns: MIDAS error code. Detection result (1 if detected, 0 if not) * is written to *result. * * Notes: OSS Sound Device is always detected. * \****************************************************************************/ int CALLING ossDetect(int *result) { *result = 1; return OK; } /****************************************************************************\ * * Function: int ossInit(unsigned mixRate, unsigned mode) * * Description: Initializes OSS Sound Device * * Input: unsigned mixRate mixing rate in Hz * unsigned mode output mode * * Returns: MIDAS error code * \****************************************************************************/ int CALLING ossInit(unsigned _mixRate, unsigned mode) { int error; int mixMode; int *modetag; mixRate = _mixRate; /* Determine the actual output mode: */ if ( mode & sdMono ) format_stereo = 0; else format_stereo = 1; if ( mode & sd8bit ) modetag = &format_8bits; else modetag = &format_16bits; /* Open output device using the format just set up: */ if (( audio_fd = open(DEVICE_NAME, open_mode, 0 )) == -1 ) { perror(DEVICE_NAME); return errDeviceNotAvailable; } if ( ((int)mode = ioctl(audio_fd, SNDCTL_DSP_SETFMT, modetag)) == -1) { /* Fatal error */ perror("SNDCTL_DSP_SETFMT"); return(errSDFailure); } if (ioctl(audio_fd, SNDCTL_DSP_STEREO, &format_stereo) == -1) { /* Fatal error */ perror("SNDCTL_DSP_STEREO"); return(errSDFailure); } if (format_stereo) outputMode = sdStereo; else outputMode = sdMono; if ( *modetag == AFMT_U8 ) outputMode |= sd8bit; else { if ( *modetag == AFMT_S16_LE ) outputMode |= sd16bit; else return(errSDFailure); } format_mixingrate = mixRate; if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &format_mixingrate) == -1) { /* Fatal error */ perror("SNDCTL_DSP_SPEED"); return(errSDFailure); } mixRate = format_mixingrate; /* Calculate one mixing element size: */ if ( outputMode & sd16bit ) mixElemSize = 2; else mixElemSize = 1; if ( outputMode & sdStereo ) mixElemSize <<= 1; /* Allocate memory for post-processing table if necessary: */ if ( outputMode & sd8bit ) { /* Allocate memory for 8-bit output mode post-processing table: */ if ( (error = memAlloc((1 << MIX8BITS), (void**)&ppTable)) != OK ) PASSERROR(ID_ossInit); } else ppTable = NULL; /* Check correct mixing mode: */ if ( outputMode & sdStereo ) mixMode = dsmMixStereo; else mixMode = dsmMixMono; /* Initialize Digital Sound Mixer: */ if ( outputMode & sd16bit ) { if ( (error = dsmInit(mixRate, mixMode, 16)) != OK ) PASSERROR(ID_ossInit) } else { if ( (error = dsmInit(mixRate, mixMode, MIX8BITS)) != OK ) PASSERROR(ID_ossInit) } /* Set update rate to 50Hz: */ if ( (error = ossSetUpdRate(5000)) != OK ) PASSERROR(ID_ossInit) /* Allocate memory for audiobuffer: */ if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &numFragments) == -1) { /* Fatal error */ perror("SNDCTL_DSP_SETFRAGMENT"); return(errSDFailure); } if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) { /* Fatal error */ perror("SNDCTL_DSP_GETOSPACE"); return(errSDFailure); } // printf("Fragment size: %i, total fragments: %i\n", info.fragsize, info.fragstotal); // audioBufferSize = mixRate * mixElemSize * mBufferLength / 1000; audioBufferSize = dsmMixBufferSize; // audioBufferSize = info.fragsize; if ( (error = memAlloc(audioBufferSize, (void**)&audioBuffer)) != OK ) PASSERROR(ID_ossInit); /* Point postProc() to correct post-processing routine: */ switch ( outputMode ) { case (sd16bit | sdMono): postProc = &pp16Mono; break; case (sd8bit | sdMono): postProc = &pp8Mono; break; case (sd16bit | sdStereo): postProc = &pp16Stereo; break; case (sd8bit | sdStereo): postProc = &pp8Stereo; break; default: ERROR(errInvalidArguments, ID_ossInit); return errInvalidArguments; } amplification = 64; #ifdef DUMPBUFFER buff = fopen("buffer.raw", "wb"); #endif return OK; } /****************************************************************************\ * * Function: ossClose(void) * * Description: Uninitializes OSS Sound Device * * Returns: MIDAS error code * \****************************************************************************/ int ossClose(void) { int error; #ifdef DUMPBUFFER fclose(buff); #endif /* Uninitialize Digital Sound Mixer: */ if ( (error = dsmClose()) != OK ) PASSERROR(ID_ossClose) /* Deallocate post-processing table if necessary: */ if ( outputMode & sd8bit ) { if ( (error = memFree(ppTable)) != OK ) PASSERROR(ID_ossClose); } /* Deallocate audio buffer */ if ( (error = memFree(audioBuffer)) != OK ) PASSERROR(ID_ossClose); close(audio_fd); return OK; } /****************************************************************************\ * * Function: int ossGetMode(unsigned *mode) * * Description: Reads the current output mode * * Input: unsigned *mode pointer to output mode * * Returns: MIDAS error code. Output mode is written to *mode. * \****************************************************************************/ int CALLING ossGetMode(unsigned *mode) { *mode = outputMode; return OK; } /****************************************************************************\ * * Function: int ossOpenChannels(unsigned channels) * * Description: Opens sound channels for output. Prepares post-processing * tables, takes care of default amplification and finally opens * DSM channels. Channels can be closed by simply calling * dsmCloseChannels(). * * Input: unsigned channels number of channels to open * * Returns: MIDAS error code * \****************************************************************************/ int CALLING ossOpenChannels(unsigned channels) { int error; /* Open DSM channels: */ if ( (error = dsmOpenChannels(channels)) != OK ) PASSERROR(ID_ossOpenChannels) /* Take care of default amplification and calculate new post-processing table if necessary: */ if ( channels < 5 ) ossSetAmplification(64); else ossSetAmplification(14*channels); return OK; } /****************************************************************************\ * * Function: void CalcPP8Table(void) * * Description: Calculates a new 8-bit output post-processing table using * current amplification level * \****************************************************************************/ static void CalcPP8Table(void) { uchar *tbl; int val; long temp; tbl = ppTable; /* tbl points to current table pos */ /* Calculate post-processing table for all possible DSM values: (table must be used with unsigned numbers - add (1 << MIX8BITS)/2 to DSM output values first) */ for ( val = -(1 << MIX8BITS)/2; val < (1 << MIX8BITS)/2; val++ ) { /* Calculate 8-bit unsigned output value corresponding to the current DSM output value (val), taking amplification into account: */ temp = 128 + ((((long) amplification) * ((long) val) / 64L) >> (MIX8BITS-8)); /* Clip the value to fit between 0 and 255 inclusive: */ if ( temp < 0 ) temp = 0; if ( temp > 255 ) temp = 255; /* Write the value to the post-processing table: */ *(tbl++) = (uchar) temp; } } /****************************************************************************\ * * Function: int ossSetAmplification(unsigned amplification) * * Description: Sets the amplification level. Calculates new post-processing * tables and calls dsmSetAmplification() as necessary. * * Input: unsigned amplification amplification value * * Returns: MIDAS error code * \****************************************************************************/ int CALLING ossSetAmplification(unsigned _amplification) { int error; amplification = _amplification; if ( outputMode & sd8bit ) { /* 8-bit output mode - do not set amplification level using DSM, but calculate a new post-processing table instead: */ CalcPP8Table(); } else { /* Set amplification level to DSM: */ if ( (error = dsmSetAmplification(amplification)) != OK ) PASSERROR(ID_ossSetAmplification) } return OK; } /****************************************************************************\ * * Function: int ossGetAmplification(unsigned *amplification); * * Description: Reads the current amplification level. (DSM doesn't * necessarily know the actual amplification level if * post-processing takes care of amplification) * * Input: unsigned *amplification pointer to amplification level * * Returns: MIDAS error code. Amplification level is written to * *amplification. * \****************************************************************************/ int CALLING ossGetAmplification(unsigned *_amplification) { *_amplification = amplification; return OK; } /****************************************************************************\ * * Function: int ossSetUpdRate(unsigned updRate); * * Description: Sets the channel value update rate (depends on song tempo) * * Input: unsigned updRate update rate in 100*Hz (eg. 50Hz * becomes 5000). * * Returns: MIDAS error code * \****************************************************************************/ int CALLING ossSetUpdRate(unsigned updRate) { /* Calculate number of elements to mix between two updates: (even) */ mixLeft = updateMix = ((unsigned) ((100L * (ulong) mixRate) / ((ulong) updRate)) + 1) & 0xFFFFFFFE; return OK; } /****************************************************************************\ * * Function: int ossStartPlay(void) * * Description: Prepares for playing - doesn't actually do anything here... * * Returns: MIDAS error code * \****************************************************************************/ int CALLING ossStartPlay(void) { return OK; } /****************************************************************************\ * * Function: int ossPlay(int *callMP); * * Description: Plays the sound - mixes the correct amount of data with DSM * and copies it to output buffer with post-processing. * Also takes care of sending fully mixed buffer to the * output device. * * Input: int *callMP pointer to music player calling flag * * Returns: MIDAS error code. If enough data was mixed for one updating * round and music player should be called, 1 is written to * *callMP, otherwise 0 is written there. Note that if music * player can be called, ossPlay() should be called again * with a new check for music playing to ensure the mixing buffer * gets filled with new data. * \****************************************************************************/ int CALLING ossPlay(int *callMP) { int error; unsigned bufferLeft, numElems; unsigned dsmBufSize; // uchar *temp; if (ioctl(audio_fd, SNDCTL_DSP_GETOSPACE, &info) == -1) { /* Fatal error */ perror("SNDCTL_DSP_GETOSPACE"); return(errSDFailure); } // printf("avail frags: %i\n", info.fragments); if ( info.fragments == 0 ) { *callMP = 0; return OK; } /* Calculate DSM mixing buffer size in elements: (FIXME) */ dsmBufSize = dsmMixBufferSize; dsmBufSize >>= 2; if ( outputMode & sdStereo ) dsmBufSize >>= 1; /* Calculate number of bytes of buffer left: */ bufferLeft = audioBufferSize - bufferPos; /* Calculate number of mixing elements left: */ numElems = bufferLeft / mixElemSize; /* Check that we won't mix more data than there is to the next update: */ if ( numElems > mixLeft ) numElems = mixLeft; /* Check that we won't mix more data than fits to DSM mixing buffer: */ if ( numElems > dsmBufSize ) numElems = dsmBufSize; /* Decrease number of elements before next update: */ mixLeft -= numElems; /* Mix the data to DSM mixing buffer: */ if ( (error = dsmMixData(numElems)) != OK ) PASSERROR(ID_ossPlay) /* Write the mixed data to output buffer: */ bufferPos = postProc(numElems, audioBuffer, 0, dsmMixBuffer, ppTable); #ifdef DUMPBUFFER fwrite(&audioBuffer[oldPos], numElems * mixElemSize, 1, buff); #endif /* Check if the buffer is full - if so, write it to the output device and start over: */ write(audio_fd, audioBuffer, bufferPos); /* Check if the music player should be called: */ if ( mixLeft == 0) { mixLeft = updateMix; *callMP = 1; return OK; } /* No more data fits to the mixing buffers - just return without update: */ *callMP = 0; return OK; } /* OSS Sound Device structure: */ SoundDevice OSS = { 0, /* tempoPoll = 0 */ sdUseMixRate | sdUseOutputMode | sdUseDSM, /* configBits */ 0, /* port */ 0, /* IRQ */ 0, /* DMA */ 1, /* cardType */ 1, /* numCardTypes */ sdMono | sdStereo | sd8bit | sd16bit, /* modes */ "Open Sound System Sound Device " OSSVERSTR, /* name */ &ossCardName, /* cardNames */ 0, /* numPortAddresses */ NULL, /* portAddresses */ &ossDetect, &ossInit, &ossClose, &dsmGetMixRate, &ossGetMode, &ossOpenChannels, &dsmCloseChannels, &dsmClearChannels, &dsmMute, &dsmPause, &dsmSetMasterVolume, &dsmGetMasterVolume, &ossSetAmplification, &ossGetAmplification, &dsmPlaySound, &dsmReleaseSound, &dsmStopSound, &dsmSetRate, &dsmGetRate, &dsmSetVolume, &dsmGetVolume, &dsmSetSample, &dsmGetSample, &dsmSetPosition, &dsmGetPosition, &dsmGetDirection, &dsmSetPanning, &dsmGetPanning, &dsmMuteChannel, &dsmAddSample, &dsmRemoveSample, &ossSetUpdRate, &ossStartPlay, &ossPlay }; /* * $Log: oss.c,v $ * Revision 1.11 1996/11/09 21:08:04 jpaana * Fixed some "comparison between signed and unsigned" warnings * * Revision 1.10 1996/09/22 17:11:56 jpaana * Still tweaking... * * Revision 1.9 1996/09/21 17:18:01 jpaana * Misc Fixes * * Revision 1.8 1996/09/21 16:40:26 jpaana * Fixed some typos * * Revision 1.7 1996/09/21 16:38:00 jpaana * Renamed to Open Sound System Sound Device (blah) * * Revision 1.6 1996/09/15 09:18:28 jpaana * Removed some debug texts * * Revision 1.5 1996/09/09 09:52:12 jpaana * Added some more fragments * * Revision 1.4 1996/09/08 20:32:32 jpaana * Misc. tweaking (most commented out now) * * Revision 1.3 1996/08/03 13:16:42 jpaana * Fixed to work without Pthreads ;) * * Revision 1.1 1996/06/05 19:40:35 jpaana * Initial revision * * Revision 1.2 1996/05/25 15:49:57 jpaana * Cleaned up * * Revision 1.1 1996/05/24 20:40:12 jpaana * Initial revision * * */