625 lines
13 KiB
C
625 lines
13 KiB
C
//=======================================================================
|
|
// Copyright XashXT Group 2007 ©
|
|
// s_load.c - sound managment
|
|
//=======================================================================
|
|
|
|
#include "sound.h"
|
|
#include "byteorder.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
|
|
|
|
static sfx_t s_knownSfx[MAX_SFX];
|
|
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 )
|
|
{
|
|
int i;
|
|
sfx_t *sfx;
|
|
sfxcache_t *sc;
|
|
int size, totalSfx = 0;
|
|
int totalSize = 0;
|
|
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
{
|
|
if( !sfx->registration_sequence )
|
|
continue;
|
|
|
|
sc = sfx->cache;
|
|
if( sc )
|
|
{
|
|
size = sc->length * sc->width * (sc->stereo + 1);
|
|
totalSize += size;
|
|
if( sc->loopstart >= 0 ) Msg( "L" );
|
|
else Msg( " " );
|
|
|
|
if( sfx->name[0] == '#' )
|
|
Msg( " (%2db) %s : %s\n", sc->width * 8, memprint( size ), &sfx->name[1] );
|
|
else Msg( " (%2db) %s : sound/%s\n", sc->width * 8, memprint( size ), sfx->name );
|
|
totalSfx++;
|
|
}
|
|
}
|
|
|
|
Msg("-------------------------------------------\n");
|
|
Msg("%i total sounds\n", totalSfx );
|
|
Msg("%s total memory\n", memprint( totalSize ));
|
|
Msg("\n");
|
|
}
|
|
|
|
/*
|
|
================
|
|
S_ResampleSfx
|
|
================
|
|
*/
|
|
void S_ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data )
|
|
{
|
|
float stepscale;
|
|
int outcount, srcsample;
|
|
int i, sample, samplefrac, fracstep;
|
|
sfxcache_t *sc;
|
|
|
|
if( !sfx ) return;
|
|
sc = sfx->cache;
|
|
if( !sc ) return;
|
|
|
|
stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2
|
|
|
|
outcount = sc->length / stepscale;
|
|
sc->length = outcount;
|
|
if( sc->loopstart != -1 )
|
|
sc->loopstart = sc->loopstart / stepscale;
|
|
|
|
sc->speed = dma.speed;
|
|
if( s_loadas8bit->integer )
|
|
sc->width = 1;
|
|
else sc->width = inwidth;
|
|
sc->stereo = 0;
|
|
|
|
// resample / decimate to the current source rate
|
|
if( stepscale == 1 && inwidth == 1 && sc->width == 1 )
|
|
{
|
|
// fast special case
|
|
for( i = 0; i < outcount; i++ )
|
|
((signed char *)sc->data)[i] = (int)((unsigned char)(data[i]) - 128);
|
|
}
|
|
else
|
|
{
|
|
// general case
|
|
samplefrac = 0;
|
|
fracstep = stepscale * 256;
|
|
for( i = 0; i < outcount; i++ )
|
|
{
|
|
srcsample = samplefrac >> 8;
|
|
samplefrac += fracstep;
|
|
|
|
if( inwidth == 2 ) sample = LittleShort(((short *)data)[srcsample] );
|
|
else sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
|
|
|
|
if( sc->width == 2 ) ((short *)sc->data)[i] = sample;
|
|
else ((signed char *)sc->data)[i] = sample >> 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
// return true if char 'c' is one of 1st 2 characters in pch
|
|
bool S_TestSoundChar( const char *pch, char c )
|
|
{
|
|
int i;
|
|
char *pcht = (char *)pch;
|
|
|
|
// check first 2 characters
|
|
for( i = 0; i < 2; i++ )
|
|
{
|
|
if( *pcht == c )
|
|
return true;
|
|
pcht++;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
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 )
|
|
{
|
|
MsgDev( D_WARN, "S_LoadWAV: only mono 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 );
|
|
Mem_Copy( out, buffer + (iff_dataPtr - buffer), info->samples * info->width );
|
|
Mem_Free( buffer );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
S_UploadSound
|
|
=================
|
|
*/
|
|
static void S_UploadSound( byte *data, wavinfo_t *info, sfx_t *sfx )
|
|
{
|
|
sfxcache_t *sc;
|
|
size_t size, samples;
|
|
float stepscale;
|
|
|
|
// calculate buffer size
|
|
stepscale = (float)info->rate / dma.speed;
|
|
samples = info->samples / stepscale;
|
|
size = samples * info->width * info->channels;
|
|
|
|
sc = sfx->cache = Z_Malloc( size + sizeof( sfxcache_t ));
|
|
sc->length = info->samples;
|
|
sc->loopstart = info->loopstart;
|
|
sc->speed = info->rate;
|
|
sc->width = info->width;
|
|
sc->stereo = info->channels;
|
|
|
|
S_ResampleSfx( sfx, sc->speed, sc->width, data + info->dataofs );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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] = com.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", "wav", S_LoadWAV },
|
|
{ "%s.%s", "wav", S_LoadWAV },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
sfxcache_t *S_LoadSound( sfx_t *sfx )
|
|
{
|
|
byte *data;
|
|
wavinfo_t info;
|
|
const char *ext;
|
|
string loadname, path;
|
|
loadformat_t *format;
|
|
bool anyformat;
|
|
|
|
if( !sfx ) return NULL;
|
|
if( sfx->name[0] == '*' ) return NULL;
|
|
if( sfx->cache ) return sfx->cache; // 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 sfx->cache;
|
|
}
|
|
|
|
// =======================================================================
|
|
// Load a sound
|
|
// =======================================================================
|
|
/*
|
|
==================
|
|
S_FindSound
|
|
|
|
==================
|
|
*/
|
|
sfx_t *S_FindSound( const char *name )
|
|
{
|
|
int i;
|
|
sfx_t *sfx;
|
|
|
|
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
|
|
for( i = 0; i < s_numSfx; i++ )
|
|
{
|
|
sfx = &s_knownSfx[i];
|
|
|
|
if( !com.strcmp( sfx->name, name ))
|
|
{
|
|
// prolonge registration
|
|
sfx->registration_sequence = 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->registration_sequence = s_registration_sequence;
|
|
|
|
return 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->registration_sequence != s_registration_sequence )
|
|
{
|
|
// don't need this sound
|
|
if( sfx->cache ) Mem_Free( sfx->cache );
|
|
Mem_Set( sfx, 0, sizeof( *sfx ));
|
|
}
|
|
}
|
|
|
|
// 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( !sound_started )
|
|
return -1;
|
|
|
|
sfx = S_FindSound( name );
|
|
if( !sfx ) return -1;
|
|
|
|
sfx->registration_sequence = 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++ )
|
|
{
|
|
if( !sfx->name[0] ) continue;
|
|
if( sfx->cache ) Mem_Free( sfx->cache );
|
|
Mem_Set( sfx, 0, sizeof( *sfx ));
|
|
}
|
|
|
|
Mem_Set( s_knownSfx, 0, sizeof(s_knownSfx));
|
|
s_numSfx = 0;
|
|
} |