ref: 1e30507db78e1361b409cd7e5ca0f231a7c5a016
dir: /src/i_sound.c/
// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id: i_sound.c 23 2005-07-23 21:32:47Z fraggle $ // // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 2005 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA // 02111-1307, USA. // // $Log$ // Revision 1.5 2005/07/23 21:32:47 fraggle // Add missing errno.h, fix crash on startup when no IWAD present // // Revision 1.4 2005/07/23 19:17:11 fraggle // Use ANSI-standard limit constants. Remove LINUX define. // // Revision 1.3 2005/07/23 17:21:35 fraggle // Remove step table (unused, adds dependency on pow function) // // Revision 1.2 2005/07/23 16:44:55 fraggle // Update copyright to GNU GPL // // Revision 1.1.1.1 2005/07/23 16:20:46 fraggle // Initial import // // // DESCRIPTION: // System interface for sound. // //----------------------------------------------------------------------------- static const char rcsid[] = "$Id: i_sound.c 23 2005-07-23 21:32:47Z fraggle $"; #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <errno.h> #include <math.h> #include <sys/time.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> // Linux voxware output. #include <linux/soundcard.h> // 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" // UNIX hack, to be removed. #ifdef SNDSERV // Separate sound server process. FILE* sndserver=0; char* sndserver_filename = "./sndserver "; #elif SNDINTR // Update all 30 millisecs, approx. 30fps synchronized. // Linux resolution is allegedly 10 millisecs, // scale is microseconds. #define SOUND_INTERVAL 500 // Get the interrupt. Set duration in millisecs. int I_SoundSetTimer( int duration_of_tick ); void I_SoundDelTimer( void ); #else // None? #endif // A quick hack to establish a protocol between // synchronous mix buffer updates and asynchronous // audio writes. Probably redundant with gametic. static int flag = 0; // 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]; // 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); } } // // This function loads the sound data from the WAD lump, // for single sound. // void* getsfx ( char* sfxname, int* len ) { unsigned char* sfx; unsigned char* paddedsfx; int i; int size; int paddedsize; char name[20]; int sfxlump; // Get the sound data from the WAD, allocate lump // in zone memory. sprintf(name, "ds%s", sfxname); // Now, there is a severe problem with the // sound handling, in it is not (yet/anymore) // gamemode aware. That means, sounds from // DOOM II will be requested even with DOOM // shareware. // The sound list is wired into sounds.c, // which sets the external variable. // I do not do runtime patches to that // variable. Instead, we will use a // default sound for replacement. if ( W_CheckNumForName(name) == -1 ) sfxlump = W_GetNumForName("dspistol"); else sfxlump = W_GetNumForName(name); size = W_LumpLength( sfxlump ); // Debug. // fprintf( stderr, "." ); //fprintf( stderr, " -loading %s (lump %d, %d bytes)\n", // sfxname, sfxlump, size ); //fflush( stderr ); sfx = (unsigned char*)W_CacheLumpNum( sfxlump, PU_STATIC ); // Pads the sound effect out to the mixing buffer size. // The original realloc would interfere with zone memory. paddedsize = ((size-8 + (SAMPLECOUNT-1)) / SAMPLECOUNT) * SAMPLECOUNT; // Allocate from zone memory. paddedsfx = (unsigned char*)Z_Malloc( paddedsize+8, PU_STATIC, 0 ); // ddt: (unsigned char *) realloc(sfx, paddedsize+8); // This should interfere with zone memory handling, // which does not kick in in the soundserver. // Now copy and pad. memcpy( paddedsfx, sfx, size ); for (i=size ; i<paddedsize+8 ; i++) paddedsfx[i] = 128; // Remove the cached lump. Z_Free( sfx ); // Preserve padded length. *len = paddedsize; // Return allocated padded data. return (void *) (paddedsfx + 8); } // // This function adds a sound to the // list of currently active sounds, // which is maintained as a given number // (eight, usually) of internal channels. // Returns a handle. // int addsfx ( int sfxid, int volume, int step, int seperation ) { static unsigned short handlenums = 0; int i; int rc = -1; int oldest = gametic; int oldestnum = 0; int slot; int rightvol; int leftvol; // Chainsaw troubles. // Play these sound effects only one at a time. if ( sfxid == sfx_sawup || sfxid == sfx_sawidl || sfxid == sfx_sawful || sfxid == sfx_sawhit || sfxid == sfx_stnmov || sfxid == sfx_pistol ) { // Loop all channels, check. for (i=0 ; i<NUM_CHANNELS ; i++) { // Active, and using the same SFX? if ( (channels[i]) && (channelids[i] == sfxid) ) { // Reset. channels[i] = 0; // We are sure that iff, // there will only be one. break; } } } // Loop all channels to find oldest SFX. for (i=0; (i<NUM_CHANNELS) && (channels[i]); i++) { if (channelstart[i] < oldest) { oldestnum = i; oldest = channelstart[i]; } } // Tales from the cryptic. // If we found a channel, fine. // If not, we simply overwrite the first one, 0. // Probably only happens at startup. if (i == NUM_CHANNELS) slot = oldestnum; else slot = i; // Okay, in the less recent channel, // we will handle the new SFX. // Set pointer to raw data. channels[slot] = (unsigned char *) S_sfx[sfxid].data; // Set pointer to end of raw data. channelsend[slot] = channels[slot] + lengths[sfxid]; // Reset current handle number, limited to 0..100. if (!handlenums) handlenums = 100; // Assign current handle number. // Preserved so sounds could be stopped (unused). channelhandles[slot] = rc = handlenums++; // Set stepping??? // Kinda getting the impression this is never used. channelstep[slot] = step; // ??? channelstepremainder[slot] = 0; // Should be gametic, I presume. channelstart[slot] = gametic; // Separation, that is, orientation/stereo. // range is: 1 - 256 seperation += 1; // Per left/right channel. // x^2 seperation, // adjust volume properly. leftvol = volume - ((volume*seperation*seperation) >> 16); ///(256*256); seperation = seperation - 257; rightvol = volume - ((volume*seperation*seperation) >> 16); // Sanity check, clamp volume. if (rightvol < 0 || rightvol > 127) I_Error("rightvol out of bounds"); if (leftvol < 0 || leftvol > 127) I_Error("leftvol out of bounds"); // Get the proper lookup table piece // for this volume level??? channelleftvol_lookup[slot] = &vol_lookup[leftvol*256]; channelrightvol_lookup[slot] = &vol_lookup[rightvol*256]; // Preserve sound SFX id, // e.g. for avoiding duplicates of chainsaw. channelids[slot] = sfxid; // You tell me. return rc; } // // SFX API // Note: this was called by S_Init. // However, whatever they did in the // old DPMS based DOS version, this // were simply dummies in the Linux // version. // See soundserver initdata(). // void I_SetChannels() { // Init internal lookups (raw data, mixing buffer, channels). // This function sets up internal lookups used during // the mixing process. int i; int j; // Okay, reset internal mixing channels to zero. /*for (i=0; i<NUM_CHANNELS; i++) { channels[i] = 0; }*/ // Generates volume lookup tables // which also turn the unsigned samples // into signed samples. for (i=0 ; i<128 ; i++) for (j=0 ; j<256 ; j++) vol_lookup[i*256+j] = (i*(j-128)*256)/127; } void I_SetSfxVolume(int volume) { // Identical to DOS. // Basically, this should propagate // the menu/config file setting // to the state variable used in // the mixing. snd_SfxVolume = volume; } // MUSIC API - dummy. Some code from DOS version. void I_SetMusicVolume(int volume) { // Internal state variable. snd_MusicVolume = volume; // Now set volume on output device. // Whatever( snd_MusciVolume ); } // // Retrieve the raw data lump index // for a given SFX name. // int I_GetSfxLumpNum(sfxinfo_t* sfx) { char namebuf[9]; sprintf(namebuf, "ds%s", sfx->name); return W_GetNumForName(namebuf); } // // 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 ( int id, int vol, int sep, int pitch, int priority ) { // UNUSED priority = 0; #ifdef SNDSERV if (sndserver) { fprintf(sndserver, "p%2.2x%2.2x%2.2x%2.2x\n", id, pitch, vol, sep); fflush(sndserver); } // warning: control reaches end of non-void function. return id; #else // Debug. //fprintf( stderr, "starting sound %d", id ); // fprintf( stderr, "/handle is %d\n", id ); return id; #endif } void I_StopSound (int handle) { // You need the handle returned by StartSound. // Would be looping all channels, // tracking down the handle, // an setting the channel to zero. // UNUSED. handle = 0; } int I_SoundIsPlaying(int handle) { // Ouch. return gametic < handle; } // // 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_UpdateSoundParams ( int handle, int vol, int sep, int pitch) { // I fail too see that this is used. // Would be using the handle to identify // on which channel the sound might be active, // and resetting the channel parameters. // UNUSED. handle = vol = sep = pitch = 0; } void I_ShutdownSound(void) { #ifdef SNDSERV if (sndserver) { // Send a "quit" command. fprintf(sndserver, "q\n"); fflush(sndserver); } #else // 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 ); #endif // Done. return; } void I_InitSound() { #ifdef SNDSERV char buffer[256]; if (getenv("DOOMWADDIR")) sprintf(buffer, "%s/%s", getenv("DOOMWADDIR"), sndserver_filename); else sprintf(buffer, "%s", sndserver_filename); // start sound process if ( !access(buffer, X_OK) ) { strcat(buffer, " -quiet"); sndserver = popen(buffer, "w"); } else fprintf(stderr, "Could not start sound server [%s]\n", buffer); #else 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++) { // Alias? Example is the chaingun sound linked to pistol. if (!S_sfx[i].link) { // Load data from WAD file. S_sfx[i].data = getsfx( S_sfx[i].name, &lengths[i] ); } else { // Previously loaded already? S_sfx[i].data = S_sfx[i].link->data; lengths[i] = lengths[(S_sfx[i].link - S_sfx)/sizeof(sfxinfo_t)]; } } 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"); #endif } // // MUSIC API. // Still no music done. // Remains. Dummies. // void I_InitMusic(void) { } void I_ShutdownMusic(void) { } static int looping=0; static int musicdies=-1; void I_PlaySong(int handle, int looping) { // UNUSED. handle = looping = 0; musicdies = gametic + TICRATE*30; } void I_PauseSong (int handle) { // UNUSED. handle = 0; } void I_ResumeSong (int handle) { // UNUSED. handle = 0; } void I_StopSong(int handle) { // UNUSED. handle = 0; looping = 0; musicdies = 0; } void I_UnRegisterSong(int handle) { // UNUSED. handle = 0; } int I_RegisterSong(void* data) { // UNUSED. data = NULL; return 1; } // Is the song playing? int I_QrySongPlaying(int handle) { // UNUSED. handle = 0; return looping || musicdies > gametic; } // // 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 sun typedef sigset_t tSigSet; #else typedef int tSigSet; #endif // 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"); }