747 lines
16 KiB
C
747 lines
16 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2007 ©
|
|
// s_load.c - sound managment
|
|
//=======================================================================
|
|
|
|
#include "sound.h"
|
|
#include "s_stream.h"
|
|
|
|
// during registration it is possible to have more sounds
|
|
// than could actually be referenced during gameplay,
|
|
// because we don't want to free anything until we are
|
|
// sure we won't need it.
|
|
#define MAX_SFX 4096
|
|
#define MAX_SFX_HASH (MAX_SFX/4)
|
|
|
|
static sfx_t s_knownSfx[MAX_SFX];
|
|
static sfx_t *s_sfxHashList[MAX_SFX_HASH];
|
|
static int s_numSfx = 0;
|
|
bool s_registering = false;
|
|
int s_registration_sequence = 0;
|
|
|
|
typedef struct loadformat_s
|
|
{
|
|
char *formatstring;
|
|
char *ext;
|
|
bool (*loadfunc)( const char *name, byte **wav, wavinfo_t *info );
|
|
} loadformat_t;
|
|
|
|
/*
|
|
=================
|
|
S_SoundList_f
|
|
=================
|
|
*/
|
|
void S_SoundList_f( void )
|
|
{
|
|
sfx_t *sfx;
|
|
int i, samples = 0;
|
|
int totalSfx = 0;
|
|
|
|
Msg("\n");
|
|
Msg(" -samples -hz-- -format- -name--------\n");
|
|
|
|
for( i = 0; i < s_numSfx; i++ )
|
|
{
|
|
sfx = &s_knownSfx[i];
|
|
if( sfx->loaded )
|
|
{
|
|
samples += sfx->samples;
|
|
if( sfx->loopstart >= 0 ) Msg( "L" );
|
|
else Msg( " " );
|
|
Msg( "%8i ", sfx->samples );
|
|
Msg( "%5i ", sfx->rate );
|
|
|
|
switch( sfx->format )
|
|
{
|
|
case AL_FORMAT_STEREO16: Msg( "STEREO16 " ); break;
|
|
case AL_FORMAT_STEREO8: Msg( "STEREO8 " ); break;
|
|
case AL_FORMAT_MONO16: Msg( "MONO16 " ); break;
|
|
case AL_FORMAT_MONO8: Msg( "MONO8 " ); break;
|
|
default: Msg( "???????? " ); break;
|
|
}
|
|
|
|
if( sfx->name[0] == '#' ) Msg( "%s", &sfx->name[1] );
|
|
else Msg( "sound/%s", sfx->name );
|
|
Msg( "\n" );
|
|
totalSfx++;
|
|
}
|
|
}
|
|
|
|
Msg("-------------------------------------------\n");
|
|
Msg("%i total samples\n", samples );
|
|
Msg("%i total sounds\n", totalSfx );
|
|
Msg("\n");
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
WAV LOADING
|
|
|
|
=======================================================================
|
|
*/
|
|
static byte *iff_data;
|
|
static byte *iff_dataPtr;
|
|
static byte *iff_end;
|
|
static byte *iff_lastChunk;
|
|
static int iff_chunkLen;
|
|
|
|
/*
|
|
=================
|
|
S_GetLittleShort
|
|
=================
|
|
*/
|
|
static short S_GetLittleShort( void )
|
|
{
|
|
short val = 0;
|
|
|
|
val += (*(iff_dataPtr+0) << 0);
|
|
val += (*(iff_dataPtr+1) << 8);
|
|
iff_dataPtr += 2;
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_GetLittleLong
|
|
=================
|
|
*/
|
|
static int S_GetLittleLong( void )
|
|
{
|
|
int val = 0;
|
|
|
|
val += (*(iff_dataPtr+0) << 0);
|
|
val += (*(iff_dataPtr+1) << 8);
|
|
val += (*(iff_dataPtr+2) << 16);
|
|
val += (*(iff_dataPtr+3) << 24);
|
|
iff_dataPtr += 4;
|
|
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_FindNextChunk
|
|
=================
|
|
*/
|
|
static void S_FindNextChunk( const char *name )
|
|
{
|
|
while( 1 )
|
|
{
|
|
iff_dataPtr = iff_lastChunk;
|
|
if( iff_dataPtr >= iff_end )
|
|
{
|
|
// didn't find the chunk
|
|
iff_dataPtr = NULL;
|
|
return;
|
|
}
|
|
|
|
iff_dataPtr += 4;
|
|
iff_chunkLen = S_GetLittleLong();
|
|
if (iff_chunkLen < 0)
|
|
{
|
|
iff_dataPtr = NULL;
|
|
return;
|
|
}
|
|
|
|
iff_dataPtr -= 8;
|
|
iff_lastChunk = iff_dataPtr + 8 + ((iff_chunkLen + 1) & ~1);
|
|
if(!com.strncmp( iff_dataPtr, name, 4 ))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_FindChunk
|
|
=================
|
|
*/
|
|
static void S_FindChunk( const char *name )
|
|
{
|
|
iff_lastChunk = iff_data;
|
|
S_FindNextChunk( name );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_LoadWAV
|
|
=================
|
|
*/
|
|
static bool S_LoadWAV( const char *name, byte **wav, wavinfo_t *info )
|
|
{
|
|
byte *buffer, *out;
|
|
int length, samples;
|
|
|
|
buffer = FS_LoadFile( name, &length );
|
|
if( !buffer ) return false;
|
|
|
|
iff_data = buffer;
|
|
iff_end = buffer + length;
|
|
|
|
// dind "RIFF" chunk
|
|
S_FindChunk( "RIFF" );
|
|
if( !( iff_dataPtr && !com.strncmp( iff_dataPtr + 8, "WAVE", 4 )))
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: missing 'RIFF/WAVE' chunks (%s)\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
// get "fmt " chunk
|
|
iff_data = iff_dataPtr + 12;
|
|
S_FindChunk( "fmt " );
|
|
if( !iff_dataPtr )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: missing 'fmt ' chunk (%s)\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
iff_dataPtr += 8;
|
|
if( S_GetLittleShort() != 1 )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: microsoft PCM format only (%s)\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
info->channels = S_GetLittleShort();
|
|
if( info->channels != 1 && info->channels != 2 )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: only mono and stereo WAV files supported (%s)\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
info->rate = S_GetLittleLong();
|
|
iff_dataPtr += 4+2;
|
|
|
|
info->width = S_GetLittleShort() / 8;
|
|
if( info->width != 1 && info->width != 2 )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
// get cue chunk
|
|
S_FindChunk( "cue " );
|
|
if( iff_dataPtr )
|
|
{
|
|
iff_dataPtr += 32;
|
|
info->loopstart = S_GetLittleLong();
|
|
S_FindNextChunk( "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker
|
|
if( iff_dataPtr )
|
|
{
|
|
if( !com.strncmp((const char *)iff_dataPtr + 28, "mark", 4 ))
|
|
{
|
|
// this is not a proper parse, but it works with CoolEdit...
|
|
iff_dataPtr += 24;
|
|
info->samples = info->loopstart + S_GetLittleLong(); // samples in loop
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
info->loopstart = -1;
|
|
info->samples = 0;
|
|
}
|
|
|
|
// find data chunk
|
|
S_FindChunk( "data" );
|
|
if( !iff_dataPtr )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: missing 'data' chunk (%s)\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
iff_dataPtr += 4;
|
|
samples = S_GetLittleLong() / info->width;
|
|
|
|
if( info->samples )
|
|
{
|
|
if( samples < info->samples )
|
|
{
|
|
MsgDev( D_ERROR, "S_LoadWAV: %s has a bad loop length\n", name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
}
|
|
else info->samples = samples;
|
|
|
|
if( info->samples <= 0 )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: file with %i samples (%s)\n", info->samples, name );
|
|
Mem_Free( buffer );
|
|
return false;
|
|
}
|
|
|
|
// Load the data
|
|
*wav = out = Z_Malloc( info->samples * info->width * info->channels );
|
|
Mem_Copy( out, buffer + (iff_dataPtr - buffer), info->samples * info->width * info->channels );
|
|
Mem_Free( buffer );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=======================================================================
|
|
|
|
OGG LOADING
|
|
|
|
=======================================================================
|
|
*/
|
|
typedef struct
|
|
{
|
|
byte *buffer;
|
|
ogg_int64_t ind;
|
|
ogg_int64_t buffsize;
|
|
} ov_decode_t;
|
|
|
|
static size_t ovc_read( void *ptr, size_t size, size_t nb, void *datasource )
|
|
{
|
|
ov_decode_t *sound = (ov_decode_t *)datasource;
|
|
size_t remain, length;
|
|
|
|
remain = sound->buffsize - sound->ind;
|
|
length = size * nb;
|
|
if( remain < length ) length = remain - remain % size;
|
|
|
|
Mem_Copy( ptr, sound->buffer + sound->ind, length );
|
|
sound->ind += length;
|
|
|
|
return length / size;
|
|
}
|
|
|
|
static int ovc_seek ( void *datasource, ogg_int64_t offset, int whence )
|
|
{
|
|
ov_decode_t *sound = (ov_decode_t*)datasource;
|
|
|
|
switch( whence )
|
|
{
|
|
case SEEK_SET:
|
|
break;
|
|
case SEEK_CUR:
|
|
offset += sound->ind;
|
|
break;
|
|
case SEEK_END:
|
|
offset += sound->buffsize;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
if( offset < 0 || offset > sound->buffsize )
|
|
return -1;
|
|
|
|
sound->ind = offset;
|
|
return 0;
|
|
}
|
|
|
|
static int ovc_close( void *datasource )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static long ovc_tell (void *datasource)
|
|
{
|
|
return ((ov_decode_t*)datasource)->ind;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_LoadOGG
|
|
=================
|
|
*/
|
|
static bool S_LoadOGG( const char *name, byte **wav, wavinfo_t *info )
|
|
{
|
|
vorbisfile_t vf;
|
|
vorbis_info_t *vi;
|
|
vorbis_comment_t *vc;
|
|
fs_offset_t filesize;
|
|
ov_decode_t ov_decode;
|
|
ogg_int64_t length, done = 0;
|
|
ov_callbacks_t ov_callbacks = { ovc_read, ovc_seek, ovc_close, ovc_tell };
|
|
byte *data, *buffer;
|
|
const char *comm;
|
|
int dummy;
|
|
long ret;
|
|
|
|
// load the file
|
|
data = FS_LoadFile( name, &filesize );
|
|
if( !data ) return false;
|
|
|
|
// Open it with the VorbisFile API
|
|
ov_decode.buffer = data;
|
|
ov_decode.ind = 0;
|
|
ov_decode.buffsize = filesize;
|
|
if( ov_open_callbacks( &ov_decode, &vf, NULL, 0, ov_callbacks ) < 0 )
|
|
{
|
|
MsgDev( D_ERROR, "S_LoadOGG: couldn't open ogg stream %s\n", name );
|
|
Mem_Free( data );
|
|
return false;
|
|
}
|
|
|
|
// get the stream information
|
|
vi = ov_info( &vf, -1 );
|
|
if( vi->channels != 1 )
|
|
{
|
|
MsgDev( D_ERROR, "S_LoadOGG: only mono OGG files supported (%s)\n", name );
|
|
ov_clear( &vf );
|
|
Mem_Free( data );
|
|
return false;
|
|
}
|
|
|
|
info->channels = vi->channels;
|
|
info->rate = vi->rate;
|
|
info->width = 2; // always 16-bit PCM
|
|
info->loopstart = -1;
|
|
length = ov_pcm_total( &vf, -1 ) * vi->channels * 2; // 16 bits => "* 2"
|
|
if( !length )
|
|
{
|
|
// bad ogg file
|
|
MsgDev( D_ERROR, "S_LoadOGG: (%s) is probably corrupted\n", name );
|
|
ov_clear( &vf );
|
|
Mem_Free( data );
|
|
return false;
|
|
}
|
|
buffer = (byte *)Z_Malloc( length );
|
|
|
|
// decompress ogg into pcm wav format
|
|
while((ret = ov_read( &vf, &buffer[done], (int)(length - done), big_endian, 2, 1, &dummy )) > 0)
|
|
done += ret;
|
|
info->samples = done / ( vi->channels * 2 );
|
|
vc = ov_comment( &vf, -1 );
|
|
|
|
if( vc )
|
|
{
|
|
comm = vorbis_comment_query( vc, "LOOP_START", 0 );
|
|
if( comm )
|
|
{
|
|
//FXIME: implement
|
|
Msg("ogg 'cue' %d\n", com.atoi(comm) );
|
|
//info->loopstart = bound( 0, com.atoi(comm), info->samples );
|
|
}
|
|
}
|
|
|
|
// close file
|
|
ov_clear( &vf );
|
|
Mem_Free( data );
|
|
*wav = buffer; // load the data
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_UploadSound
|
|
=================
|
|
*/
|
|
static void S_UploadSound( byte *data, wavinfo_t *info, sfx_t *sfx )
|
|
{
|
|
int size;
|
|
|
|
// calculate buffer size
|
|
size = info->samples * info->width * info->channels;
|
|
sfx->loopstart = info->loopstart;
|
|
sfx->samples = info->samples;
|
|
sfx->rate = info->rate;
|
|
|
|
// Set buffer format
|
|
if( info->width == 2 )
|
|
{
|
|
if( info->channels == 2 ) sfx->format = AL_FORMAT_STEREO16;
|
|
else sfx->format = AL_FORMAT_MONO16;
|
|
}
|
|
else
|
|
{
|
|
if( info->channels == 2 ) sfx->format = AL_FORMAT_STEREO8;
|
|
else sfx->format = AL_FORMAT_MONO8;
|
|
}
|
|
|
|
// upload the sound
|
|
palGenBuffers( 1, &sfx->bufferNum );
|
|
palBufferData( sfx->bufferNum, sfx->format, data, size, sfx->rate );
|
|
S_CheckForErrors();
|
|
|
|
sfx->loaded = true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_CreateDefaultSound
|
|
=================
|
|
*/
|
|
static void S_CreateDefaultSound( byte **wav, wavinfo_t *info )
|
|
{
|
|
byte *out;
|
|
int i;
|
|
|
|
info->rate = 22050;
|
|
info->width = 2;
|
|
info->channels = 1;
|
|
info->samples = 11025;
|
|
|
|
*wav = out = Z_Malloc( info->samples * info->width );
|
|
|
|
if( s_check_errors->integer )
|
|
{
|
|
// create 1 kHz tone as default sound
|
|
for( i = 0; i < info->samples; i++ )
|
|
((short *)out)[i] = sin(i * 0.1f) * 20000;
|
|
}
|
|
else
|
|
{
|
|
// create silent sound
|
|
for( i = 0; i < info->samples; i++ )
|
|
((short *)out)[i] = i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_LoadSound
|
|
=================
|
|
*/
|
|
loadformat_t load_formats[] =
|
|
{
|
|
{ "sound/%s.%s", "ogg", S_LoadOGG },
|
|
{ "sound/%s.%s", "wav", S_LoadWAV },
|
|
{ "%s.%s", "ogg", S_LoadOGG },
|
|
{ "%s.%s", "wav", S_LoadWAV },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
bool S_LoadSound( sfx_t *sfx )
|
|
{
|
|
byte *data;
|
|
wavinfo_t info;
|
|
const char *ext;
|
|
string loadname, path;
|
|
loadformat_t *format;
|
|
bool anyformat;
|
|
|
|
if( !sfx ) return false;
|
|
if( sfx->name[0] == '*' ) return false;
|
|
if( sfx->loaded ) return true; // see if still in memory
|
|
|
|
// load it from disk
|
|
ext = FS_FileExtension( sfx->name );
|
|
anyformat = !com.stricmp( ext, "" ) ? true : false;
|
|
|
|
com.strncpy( loadname, sfx->name, sizeof( loadname ));
|
|
FS_StripExtension( loadname ); // remove extension if needed
|
|
Mem_Set( &info, 0, sizeof( info ));
|
|
|
|
// developer warning
|
|
if( !anyformat ) MsgDev( D_NOTE, "Note: %s will be loading only with ext .%s\n", loadname, ext );
|
|
|
|
// now try all the formats in the selected list
|
|
for( format = load_formats; format->formatstring; format++ )
|
|
{
|
|
if( anyformat || !com.stricmp( ext, format->ext ))
|
|
{
|
|
com.sprintf( path, format->formatstring, loadname, format->ext );
|
|
if( format->loadfunc( path, &data, &info ))
|
|
goto snd_loaded;
|
|
}
|
|
}
|
|
|
|
sfx->default_sound = true;
|
|
MsgDev( D_WARN, "FS_LoadSound: couldn't load %s\n", sfx->name );
|
|
S_CreateDefaultSound( &data, &info );
|
|
info.loopstart = -1;
|
|
|
|
snd_loaded:
|
|
// load it in
|
|
S_UploadSound( data, &info, sfx );
|
|
Mem_Free( data );
|
|
|
|
return true;
|
|
}
|
|
|
|
// =======================================================================
|
|
// Load a sound
|
|
// =======================================================================
|
|
/*
|
|
=================
|
|
S_FindSound
|
|
=================
|
|
*/
|
|
sfx_t *S_FindSound( const char *name )
|
|
{
|
|
int i;
|
|
sfx_t *sfx;
|
|
uint hash;
|
|
|
|
if( !name || !name[0] ) return NULL;
|
|
if( com.strlen( name ) >= MAX_STRING )
|
|
{
|
|
MsgDev( D_ERROR, "S_FindSound: sound name too long: %s", name );
|
|
return NULL;
|
|
}
|
|
|
|
// see if already loaded
|
|
hash = Com_HashKey( name, MAX_SFX_HASH );
|
|
for( sfx = s_sfxHashList[hash]; sfx; sfx = sfx->hashNext )
|
|
{
|
|
if( !com.strcmp( sfx->name, name ))
|
|
{
|
|
// prolonge registration
|
|
sfx->touchFrame = s_registration_sequence;
|
|
return sfx;
|
|
}
|
|
}
|
|
|
|
// find a free sfx slot spot
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++)
|
|
{
|
|
if( !sfx->name[0] ) break; // free spot
|
|
}
|
|
if( i == s_numSfx )
|
|
{
|
|
if( s_numSfx == MAX_SFX )
|
|
{
|
|
MsgDev( D_ERROR, "S_FindName: MAX_SFX limit exceeded\n" );
|
|
return NULL;
|
|
}
|
|
s_numSfx++;
|
|
}
|
|
|
|
sfx = &s_knownSfx[i];
|
|
Mem_Set( sfx, 0, sizeof( *sfx ));
|
|
com.strncpy( sfx->name, name, MAX_STRING );
|
|
sfx->touchFrame = s_registration_sequence;
|
|
sfx->hashValue = Com_HashKey( sfx->name, MAX_SFX_HASH );
|
|
|
|
// link it in
|
|
sfx->hashNext = s_sfxHashList[sfx->hashValue];
|
|
s_sfxHashList[sfx->hashValue] = sfx;
|
|
|
|
return sfx;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
S_FreeSound
|
|
==================
|
|
*/
|
|
static void S_FreeSound( sfx_t *sfx )
|
|
{
|
|
sfx_t *hashSfx;
|
|
sfx_t **prev;
|
|
|
|
if( !sfx || !sfx->name[0] ) return;
|
|
|
|
// de-link it from the hash tree
|
|
prev = &s_sfxHashList[sfx->hashValue];
|
|
while( 1 )
|
|
{
|
|
hashSfx = *prev;
|
|
if( !hashSfx )
|
|
break;
|
|
|
|
if( hashSfx == sfx )
|
|
{
|
|
*prev = hashSfx->hashNext;
|
|
break;
|
|
}
|
|
prev = &hashSfx->hashNext;
|
|
}
|
|
|
|
palDeleteBuffers( 1, &sfx->bufferNum );
|
|
Mem_Set( sfx, 0, sizeof( *sfx ));
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
S_BeginRegistration
|
|
=====================
|
|
*/
|
|
void S_BeginRegistration( void )
|
|
{
|
|
s_registration_sequence++;
|
|
s_registering = true;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
S_EndRegistration
|
|
=====================
|
|
*/
|
|
void S_EndRegistration( void )
|
|
{
|
|
sfx_t *sfx;
|
|
int i;
|
|
|
|
// free any sounds not from this registration sequence
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
{
|
|
if( !sfx->name[0] ) continue;
|
|
if( sfx->touchFrame != s_registration_sequence )
|
|
S_FreeSound( sfx ); // don't need this sound
|
|
}
|
|
|
|
// load everything in
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
{
|
|
if( !sfx->name[0] ) continue;
|
|
S_LoadSound( sfx );
|
|
}
|
|
s_registering = false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_RegisterSound
|
|
|
|
=================
|
|
*/
|
|
sound_t S_RegisterSound( const char *name )
|
|
{
|
|
sfx_t *sfx;
|
|
|
|
if( !al_state.initialized )
|
|
return -1;
|
|
|
|
sfx = S_FindSound( name );
|
|
if( !sfx ) return -1;
|
|
|
|
sfx->touchFrame = s_registration_sequence;
|
|
if( !s_registering ) S_LoadSound( sfx );
|
|
|
|
return sfx - s_knownSfx;
|
|
}
|
|
|
|
sfx_t *S_GetSfxByHandle( sound_t handle )
|
|
{
|
|
if( handle < 0 || handle >= s_numSfx )
|
|
{
|
|
MsgDev( D_ERROR, "S_GetSfxByHandle: handle %i out of range (%i)\n", handle, s_numSfx );
|
|
return NULL;
|
|
}
|
|
return &s_knownSfx[handle];
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_FreeSounds
|
|
=================
|
|
*/
|
|
void S_FreeSounds( void )
|
|
{
|
|
sfx_t *sfx;
|
|
int i;
|
|
|
|
// stop all sounds
|
|
S_StopAllSounds();
|
|
|
|
// free all sounds
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
S_FreeSound( sfx );
|
|
|
|
s_numSfx = 0;
|
|
Mem_Set( s_knownSfx, 0, sizeof( s_knownSfx ));
|
|
Mem_Set( s_sfxHashList, 0, sizeof( s_sfxHashList ));
|
|
} |