diff options
author | Simon Howard | 2005-07-23 16:19:41 +0000 |
---|---|---|
committer | Simon Howard | 2005-07-23 16:19:41 +0000 |
commit | 66a2cc66d7504c9b64e1c461e62ad2a9d964fa95 (patch) | |
tree | 1e34ee102280a912989ec3084427850372348ebd /src/i_sound.c | |
download | chocolate-doom-66a2cc66d7504c9b64e1c461e62ad2a9d964fa95.tar.gz chocolate-doom-66a2cc66d7504c9b64e1c461e62ad2a9d964fa95.tar.bz2 chocolate-doom-66a2cc66d7504c9b64e1c461e62ad2a9d964fa95.zip |
Initial revision
Subversion-branch: /trunk/chocolate-doom
Subversion-revision: 4
Diffstat (limited to 'src/i_sound.c')
-rw-r--r-- | src/i_sound.c | 988 |
1 files changed, 988 insertions, 0 deletions
diff --git a/src/i_sound.c b/src/i_sound.c new file mode 100644 index 00000000..c363d67d --- /dev/null +++ b/src/i_sound.c @@ -0,0 +1,988 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// $Id: i_sound.c 4 2005-07-23 16:19:41Z fraggle $ +// +// 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$ +// Revision 1.1 2005/07/23 16:20:46 fraggle +// Initial revision +// +// +// DESCRIPTION: +// System interface for sound. +// +//----------------------------------------------------------------------------- + +static const char +rcsid[] = "$Id: i_sound.c 4 2005-07-23 16:19:41Z fraggle $"; + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +#include <math.h> + +#include <sys/time.h> +#include <sys/types.h> + +#ifndef LINUX +#include <sys/filio.h> +#endif + +#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]; + +// 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); + } +} + + + + + +// +// 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; + + int* steptablemid = steptable + 128; + + // Okay, reset internal mixing channels to zero. + /*for (i=0; i<NUM_CHANNELS; i++) + { + channels[i] = 0; + }*/ + + // This table provides step widths for pitch parameters. + // I fail to see that this is currently used. + for (i=-128 ; i<128 ; i++) + steptablemid[i] = (int)(pow(2.0, (i/64.0))*65536.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 ); + + // Returns a handle (not used). + id = addsfx( id, vol, steptable[pitch], sep ); + + // 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"); +} |