home *** CD-ROM | disk | FTP | other *** search
- /* Emacs style mode select -*- C++ -*- */
- /* ----------------------------------------------------------------------------- */
- /* */
- /* $Id:$ */
- /* */
- /* Copyright (C) 1993-1996 by id Software, Inc. */
- /* */
- /* This source is available for distribution and/or modification */
- /* only under the terms of the DOOM Source Code License as */
- /* published by id Software. All rights reserved. */
- /* */
- /* The source is distributed in the hope that it will be useful, */
- /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
- /* FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License */
- /* for more details. */
- /* */
- /* $Log:$ */
- /* */
- /* DESCRIPTION: */
- /* System interface for sound. */
- /* */
- /* ----------------------------------------------------------------------------- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdarg.h>
-
- #include <math.h>
-
- #include <sys/time.h>
- #include <sys/types.h>
-
- #ifndef LINUX
- #ifndef ATARI
- #include <sys/filio.h>
- #endif
- #endif
-
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
-
- /* Linux voxware output. */
- #ifdef LINUX
- #include <linux/soundcard.h>
- #endif
-
- /* Timer stuff. Experimental. */
- #include <time.h>
- #include <signal.h>
-
- #include "z_zone.h"
-
- #include "i_system.h"
- #include "i_sound.h"
- #include "m_argv.h"
- #include "m_misc.h"
- #include "w_wad.h"
-
- #include "doomdef.h"
-
- /* Use timer interrupt for audio device ? */
- #define SNDINTR 1
-
-
-
- #ifdef SNDINTR
-
- /* Update all 30 millisecs, approx. 30fps synchronized. */
- /* Linux resolution is allegedly 10 millisecs, */
- /* scale is microseconds. */
- /*#define SOUND_INTERVAL 500*/
- #define SOUND_INTERVAL (((512/4)*1000000)/11025)
-
- /* Get the interrupt. Set duration in millisecs. */
- int I_SoundSetTimer( int duration_of_tick );
- void I_SoundDelTimer( void );
-
- /* A quick hack to establish a protocol between */
- /* synchronous mix buffer updates and asynchronous */
- /* audio writes. Probably redundant with gametic. */
- static int flag = 0;
-
- #endif
-
- /* The number of internal mixing channels, */
- /* the samples calculated for each mixing step, */
- /* the size of the 16bit, 2 hardware channel (stereo) */
- /* mixing buffer, and the samplerate of the raw data. */
-
-
- /* Needed for calling the actual sound output. */
- #define SAMPLECOUNT 512
- #define NUM_CHANNELS 8
- /* It is 2 for 16bit, and 2 for two channels. */
- #define BUFMUL 4
- #define MIXBUFFERSIZE (SAMPLECOUNT*BUFMUL)
-
- #define SAMPLERATE 11025 /* Hz */
- #define SAMPLESIZE 2 /* 16bit */
-
- /* The actual lengths of all sound effects. */
- int lengths[NUMSFX];
-
- /* The actual output device. */
- int audio_fd;
-
- /* The global mixing buffer. */
- /* Basically, samples from all active internal channels */
- /* are modifed and added, and stored in the buffer */
- /* that is submitted to the audio device. */
- signed short mixbuffer[MIXBUFFERSIZE];
-
-
- /* The channel step amount... */
- unsigned int channelstep[NUM_CHANNELS];
- /* ... and a 0.16 bit remainder of last step. */
- unsigned int channelstepremainder[NUM_CHANNELS];
-
-
- /* The channel data pointers, start and end. */
- unsigned char* channels[NUM_CHANNELS];
- unsigned char* channelsend[NUM_CHANNELS];
-
-
- /* Time/gametic that the channel started playing, */
- /* used to determine oldest, which automatically */
- /* has lowest priority. */
- /* In case number of active sounds exceeds */
- /* available channels. */
- int channelstart[NUM_CHANNELS];
-
- /* The sound in channel handles, */
- /* determined on registration, */
- /* might be used to unregister/stop/modify, */
- /* currently unused. */
- int channelhandles[NUM_CHANNELS];
-
- /* SFX id of the playing sound effect. */
- /* Used to catch duplicates (like chainsaw). */
- int channelids[NUM_CHANNELS];
-
- /* Pitch to stepping lookup, unused. */
- int steptable[256];
-
- /* Volume lookups. */
- int vol_lookup[128*256];
-
- /* Hardware left and right channel volume lookup. */
- int* channelleftvol_lookup[NUM_CHANNELS];
- int* channelrightvol_lookup[NUM_CHANNELS];
-
-
-
-
- /* */
- /* Safe ioctl, convenience. */
- /* */
- void
- myioctl
- ( int fd,
- int command,
- int* arg )
- {
- int rc;
- extern int errno;
-
- rc = ioctl(fd, command, arg);
- if (rc < 0)
- {
- fprintf(stderr, "ioctl(dsp,%d,arg) failed\n", command);
- fprintf(stderr, "errno=%d\n", errno);
- exit(-1);
- }
- }
-
- /* */
- /* Starting a sound means adding it */
- /* to the current list of active sounds */
- /* in the internal channels. */
- /* As the SFX info struct contains */
- /* e.g. a pointer to the raw data, */
- /* it is ignored. */
- /* As our sound handling does not handle */
- /* priority, it is ignored. */
- /* Pitching (that is, increased speed of playback) */
- /* is set, but currently not used by mixing. */
- /* */
- int
- I_StartSound_linux
- ( int id,
- int vol,
- int sep,
- int pitch,
- int priority )
- {
- /* Debug. */
- /* fprintf( stderr, "starting sound %d", id ); */
-
- /* Returns a handle (not used). */
- id = addsfx( id, vol, steptable[pitch], sep );
-
- /* fprintf( stderr, "/handle is %d\n", id ); */
-
- return id;
- }
-
- /* */
- /* This function loops all active (internal) sound */
- /* channels, retrieves a given number of samples */
- /* from the raw sound data, modifies it according */
- /* to the current (internal) channel parameters, */
- /* mixes the per channel samples into the global */
- /* mixbuffer, clamping it to the allowed range, */
- /* and sets up everything for transferring the */
- /* contents of the mixbuffer to the (two) */
- /* hardware channels (left and right, that is). */
- /* */
- /* This function currently supports only 16bit. */
- /* */
- void I_UpdateSound( void )
- {
- #ifdef SNDINTR
- /* Debug. Count buffer misses with interrupt. */
- static int misses = 0;
- #endif
-
-
- /* Mix current sound data. */
- /* Data, from raw sound, for right and left. */
- register unsigned int sample;
- register int dl;
- register int dr;
-
- /* Pointers in global mixbuffer, left, right, end. */
- signed short* leftout;
- signed short* rightout;
- signed short* leftend;
- /* Step in mixbuffer, left and right, thus two. */
- int step;
-
- /* Mixing channel index. */
- int chan;
-
- /* Left and right channel */
- /* are in global mixbuffer, alternating. */
- leftout = mixbuffer;
- rightout = mixbuffer+1;
- step = 2;
-
- /* Determine end, for left channel only */
- /* (right channel is implicit). */
- leftend = mixbuffer + SAMPLECOUNT*step;
-
- /* Mix sounds into the mixing buffer. */
- /* Loop over step*SAMPLECOUNT, */
- /* that is 512 values for two channels. */
- while (leftout != leftend)
- {
- /* Reset left/right value. */
- dl = 0;
- dr = 0;
-
- /* Love thy L2 chache - made this a loop. */
- /* Now more channels could be set at compile time */
- /* as well. Thus loop those channels. */
- for ( chan = 0; chan < NUM_CHANNELS; chan++ )
- {
- /* Check channel, if active. */
- if (channels[ chan ])
- {
- /* Get the raw data from the channel. */
- sample = *channels[ chan ];
- /* Add left and right part */
- /* for this channel (sound) */
- /* to the current data. */
- /* Adjust volume accordingly. */
- dl += channelleftvol_lookup[ chan ][sample];
- dr += channelrightvol_lookup[ chan ][sample];
- /* Increment index ??? */
- channelstepremainder[ chan ] += channelstep[ chan ];
- /* MSB is next sample??? */
- channels[ chan ] += channelstepremainder[ chan ] >> 16;
- /* Limit to LSB??? */
- channelstepremainder[ chan ] &= 65536-1;
-
- /* Check whether we are done. */
- if (channels[ chan ] >= channelsend[ chan ])
- channels[ chan ] = 0;
- }
- }
-
- /* Clamp to range. Left hardware channel. */
- /* Has been char instead of short. */
- /* if (dl > 127) *leftout = 127; */
- /* else if (dl < -128) *leftout = -128; */
- /* else *leftout = dl; */
-
- if (dl > 0x7fff)
- *leftout = 0x7fff;
- else if (dl < -0x8000)
- *leftout = -0x8000;
- else
- *leftout = dl;
-
- /* Same for right hardware channel. */
- if (dr > 0x7fff)
- *rightout = 0x7fff;
- else if (dr < -0x8000)
- *rightout = -0x8000;
- else
- *rightout = dr;
-
- /* Increment current pointers in mixbuffer. */
- leftout += step;
- rightout += step;
- }
-
- #ifdef SNDINTR
- /* Debug check. */
- if ( flag )
- {
- misses += flag;
- flag = 0;
- }
-
- if ( misses > 10 )
- {
- /*
- fprintf( stderr, "I_SoundUpdate: missed 10 buffer writes\n");
- */
- misses = 0;
- }
-
- /* Increment flag for update. */
- flag++;
- #endif
- }
-
- /* */
- /* This would be used to write out the mixbuffer */
- /* during each game loop update. */
- /* Updates sound buffer and audio device at runtime. */
- /* It is called during Timer interrupt with SNDINTR. */
- /* Mixing now done synchronous, and */
- /* only output be done asynchronous? */
- /* */
- void I_SubmitSound(void)
- {
- /* Write it to DSP device. */
- write(audio_fd, mixbuffer, SAMPLECOUNT*BUFMUL);
- }
-
-
- void I_ShutdownSound_linux(void)
- {
- /* Wait till all pending sounds are finished. */
- int done = 0;
- int i;
-
-
- /* FIXME (below). */
- fprintf( stderr, "I_ShutdownSound: NOT finishing pending sounds\n");
- fflush( stderr );
-
- while ( !done )
- {
- for( i=0 ; i<8 && !channels[i] ; i++);
-
- /* FIXME. No proper channel output. */
- /* if (i==8) */
- done=1;
- }
- #ifdef SNDINTR
- I_SoundDelTimer();
- #endif
-
- /* Cleaning up -releasing the DSP device. */
- close ( audio_fd );
- }
-
-
-
-
-
-
- void
- I_InitSound_linux()
- {
- int i;
-
- #ifdef SNDINTR
- fprintf( stderr, "I_SoundSetTimer: %d microsecs\n", SOUND_INTERVAL );
- I_SoundSetTimer( SOUND_INTERVAL );
- #endif
-
- /* Secure and configure sound device first. */
- fprintf( stderr, "I_InitSound: ");
-
- audio_fd = open("/dev/dsp", O_WRONLY);
- if (audio_fd<0)
- fprintf(stderr, "Could not open /dev/dsp\n");
-
-
- i = 11 | (2<<16);
- myioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &i);
- myioctl(audio_fd, SNDCTL_DSP_RESET, 0);
-
- i=SAMPLERATE;
-
- myioctl(audio_fd, SNDCTL_DSP_SPEED, &i);
-
- i=1;
- myioctl(audio_fd, SNDCTL_DSP_STEREO, &i);
-
- myioctl(audio_fd, SNDCTL_DSP_GETFMTS, &i);
-
- if (i&=AFMT_S16_LE)
- myioctl(audio_fd, SNDCTL_DSP_SETFMT, &i);
- else
- fprintf(stderr, "Could not play signed 16 data\n");
-
- fprintf(stderr, " configured audio device\n" );
-
- /* Initialize external data (all sounds) at start, keep static. */
- fprintf( stderr, "I_InitSound: ");
-
- for (i=1 ; i<NUMSFX ; i++)
- {
- sfxinfo_t *sfx=&S_sfx[i];
-
- sfx->nbusing=0;
-
- /* Alias? Example is the chaingun sound linked to pistol. */
- if (!sfx->link)
- {
- /* Load data from WAD file. */
- sfx->data = getsfx( sfx->name, &lengths[i] );
- Z_ChangeTag ( (sfx->data)-8, PU_CACHE);
- }
- #if 0
- else
- {
- /* Previously loaded already? */
- sfx->data = sfx->link->data;
- lengths[i] = lengths[(sfx->link - S_sfx)/sizeof(sfxinfo_t)];
-
- }
- #endif
- }
-
- fprintf( stderr, " pre-cached all sound data\n");
-
- /* Now initialize mixbuffer with zero. */
- for ( i = 0; i< MIXBUFFERSIZE; i++ )
- mixbuffer[i] = 0;
-
- /* Finished initialization. */
- fprintf(stderr, "I_InitSound: sound module ready\n");
- }
-
- void I_SndUpdate_linux(void)
- {
- /* Sound mixing for the buffer is snychronous. */
- I_UpdateSound();
- /* Synchronous sound output is explicitly called. */
- #ifndef SNDINTR
- /* Update sound output. */
- I_SubmitSound();
- #endif
- }
-
- /* */
- /* Experimental stuff. */
- /* A Linux timer interrupt, for asynchronous */
- /* sound output. */
- /* I ripped this out of the Timer class in */
- /* our Difference Engine, including a few */
- /* SUN remains... */
- /* */
-
- #ifdef SNDINTR
-
- #ifdef sun
- typedef sigset_t tSigSet;
- #else
- typedef int tSigSet;
- #endif /* sun */
-
-
- /* We might use SIGVTALRM and ITIMER_VIRTUAL, if the process */
- /* time independend timer happens to get lost due to heavy load. */
- /* SIGALRM and ITIMER_REAL doesn't really work well. */
- /* There are issues with profiling as well. */
- static int /*__itimer_which*/ itimer = ITIMER_REAL;
-
- static int sig = SIGALRM;
-
- /* Interrupt handler. */
- void I_HandleSoundTimer( int ignore )
- {
- /* Debug. */
- /* fprintf( stderr, "%c", '+' ); fflush( stderr ); */
-
- /* Feed sound device if necesary. */
- if ( flag )
- {
- /* See I_SubmitSound(). */
- /* Write it to DSP device. */
- write(audio_fd, mixbuffer, SAMPLECOUNT*BUFMUL);
-
- /* Reset flag counter. */
- flag = 0;
- }
- else
- return;
-
- /* UNUSED, but required. */
- ignore = 0;
- return;
- }
-
- /* Get the interrupt. Set duration in millisecs. */
- int I_SoundSetTimer( int duration_of_tick )
- {
- /* Needed for gametick clockwork. */
- struct itimerval value;
- struct itimerval ovalue;
- struct sigaction act;
- struct sigaction oact;
-
- int res;
-
- /* This sets to SA_ONESHOT and SA_NOMASK, thus we can not use it. */
- /* signal( _sig, handle_SIG_TICK ); */
-
- /* Now we have to change this attribute for repeated calls. */
- act.sa_handler = I_HandleSoundTimer;
- #ifndef sun
- /* ac t.sa_mask = _sig; */
- #endif
- act.sa_flags = SA_RESTART;
-
- sigaction( sig, &act, &oact );
-
- value.it_interval.tv_sec = 0;
- value.it_interval.tv_usec = duration_of_tick;
- value.it_value.tv_sec = 0;
- value.it_value.tv_usec = duration_of_tick;
-
- /* Error is -1. */
- res = setitimer( itimer, &value, &ovalue );
-
- /* Debug. */
- if ( res == -1 )
- fprintf( stderr, "I_SoundSetTimer: interrupt n.a.\n");
-
- return res;
- }
-
-
- /* Remove the interrupt. Set duration to zero. */
- void I_SoundDelTimer()
- {
- /* Debug. */
- if ( I_SoundSetTimer( 0 ) == -1)
- fprintf( stderr, "I_SoundDelTimer: failed to remove interrupt. Doh!\n");
- }
-
- #endif /* SNDINTR */
-