/* 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 <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/soundcard.h>
#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
*
*
*/