kolibrios-gitea/programs/games/doom/trunk/i_sound.c
Sergey Semyonov (Serge) 0e85b8f362 still Win version. buffered stream io
git-svn-id: svn://kolibrios.org@300 a494cfbc-eb01-0410-851d-a64ba20cac60
2007-01-29 20:02:19 +00:00

672 lines
15 KiB
C

// 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.
//
//-----------------------------------------------------------------------------
static const char
rcsid[] = "$Id: i_unix.c,v 1.5 1997/02/03 22:45:10 b1 Exp $";
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <sys/types.h>
#include <fcntl.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"
// 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 16
// 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];
//
// 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.
//
static unsigned short handlenums = 0;
int addsfx(int sfxid, int volume, int step, int seperation)
{
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++;
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;
volume *=7;
// 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;
}
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;
}
//
// 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)
//
int I_StartSound(int id, int vol, int sep,
int pitch, int priority )
{
// Returns a handle (not used).
id = addsfx( id, vol, steptable[pitch], sep );
return id;
}
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 )
{
// 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];
channelstepremainder[ chan ] += channelstep[ chan ];
channels[ chan ] += channelstepremainder[ chan ] >> 16;
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;
}
I_SubmitSound(mixbuffer);
}
//
// 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)
{
// Wait till all pending sounds are finished.
int done = 0;
int i;
// FIXME (below).
printf( "I_ShutdownSound: NOT finishing pending sounds\n");
while ( !done )
{
for( i=0 ; i<8 && !channels[i] ; i++);
// FIXME. No proper channel output.
//if (i==8)
done=1;
}
return;
}
void I_InitSound()
{ int i;
printf("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)];
}
}
printf( " pre-cached all sound data\n");
// Now initialize mixbuffer with zero.
for ( i = 0; i< MIXBUFFERSIZE; i++ )
mixbuffer[i] = 0;
// Finished initialization.
printf("I_InitSound: sound module ready\n");
}
//
// 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;
}
// Interrupt handler.
void I_HandleSoundTimer( int ignore )
{
// UNUSED, but required.
ignore = 0;
return;
}