|
|
|
/*
|
|
|
|
s_load.c - sounds managment
|
|
|
|
Copyright (C) 2007 Uncle Mike
|
|
|
|
|
|
|
|
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 3 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "sound.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 8192
|
|
|
|
#define MAX_SFX_HASH (MAX_SFX/4)
|
|
|
|
|
|
|
|
static int s_numSfx = 0;
|
|
|
|
static sfx_t s_knownSfx[MAX_SFX];
|
|
|
|
static sfx_t *s_sfxHashList[MAX_SFX_HASH];
|
|
|
|
static string s_sentenceImmediateName; // keep dummy sentence name
|
|
|
|
qboolean s_registering = false;
|
|
|
|
int s_registration_sequence = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
S_SoundList_f
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void S_SoundList_f( void )
|
|
|
|
{
|
|
|
|
sfx_t *sfx;
|
|
|
|
wavdata_t *sc;
|
|
|
|
int i, totalSfx = 0;
|
|
|
|
int totalSize = 0;
|
|
|
|
|
|
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
|
|
{
|
|
|
|
if( !sfx->name[0] )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
sc = sfx->cache;
|
|
|
|
if( sc )
|
|
|
|
{
|
|
|
|
totalSize += sc->size;
|
|
|
|
|
|
|
|
if( sc->loopStart >= 0 ) Con_Printf( "L" );
|
|
|
|
else Con_Printf( " " );
|
|
|
|
if( sfx->name[0] == '*' )
|
|
|
|
Con_Printf( " (%2db) %s : %s\n", sc->width * 8, Q_memprint( sc->size ), sfx->name );
|
|
|
|
else Con_Printf( " (%2db) %s : " DEFAULT_SOUNDPATH "%s\n", sc->width * 8, Q_memprint( sc->size ), sfx->name );
|
|
|
|
totalSfx++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Con_Printf( "-------------------------------------------\n" );
|
|
|
|
Con_Printf( "%i total sounds\n", totalSfx );
|
|
|
|
Con_Printf( "%s total memory\n", Q_memprint( totalSize ));
|
|
|
|
Con_Printf( "\n" );
|
|
|
|
}
|
|
|
|
|
|
|
|
// return true if char 'c' is one of 1st 2 characters in pch
|
|
|
|
qboolean S_TestSoundChar( const char *pch, char c )
|
|
|
|
{
|
|
|
|
char *pcht = (char *)pch;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if( !pch || !*pch )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// check first 2 characters
|
|
|
|
for( i = 0; i < 2; i++ )
|
|
|
|
{
|
|
|
|
if( *pcht == c )
|
|
|
|
return true;
|
|
|
|
pcht++;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// return pointer to first valid character in file name
|
|
|
|
char *S_SkipSoundChar( const char *pch )
|
|
|
|
{
|
|
|
|
char *pcht = (char *)pch;
|
|
|
|
|
|
|
|
// check first character
|
|
|
|
if( *pcht == '!' )
|
|
|
|
pcht++;
|
|
|
|
return pcht;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
S_CreateDefaultSound
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
static wavdata_t *S_CreateDefaultSound( void )
|
|
|
|
{
|
|
|
|
wavdata_t *sc;
|
|
|
|
|
|
|
|
sc = Mem_Calloc( sndpool, sizeof( wavdata_t ));
|
|
|
|
|
|
|
|
sc->width = 2;
|
|
|
|
sc->channels = 1;
|
|
|
|
sc->loopStart = -1;
|
|
|
|
sc->rate = SOUND_DMA_SPEED;
|
|
|
|
sc->samples = SOUND_DMA_SPEED;
|
|
|
|
sc->size = sc->samples * sc->width * sc->channels;
|
|
|
|
sc->buffer = Mem_Calloc( sndpool, sc->size );
|
|
|
|
|
|
|
|
return sc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
S_LoadSound
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
wavdata_t *S_LoadSound( sfx_t *sfx )
|
|
|
|
{
|
|
|
|
wavdata_t *sc = NULL;
|
|
|
|
|
|
|
|
if( !sfx ) return NULL;
|
|
|
|
|
|
|
|
// see if still in memory
|
|
|
|
if( sfx->cache )
|
|
|
|
return sfx->cache;
|
|
|
|
|
|
|
|
if( !COM_CheckString( sfx->name ))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
// load it from disk
|
|
|
|
if( Q_stricmp( sfx->name, "*default" ))
|
|
|
|
{
|
|
|
|
// load it from disk
|
|
|
|
if( host_developer.value > 0 && CL_Active() ) {
|
|
|
|
Con_Printf( S_WARN "S_LoadSound: late precache of %s\n", sfx->name );
|
|
|
|
}
|
|
|
|
if( sfx->name[0] == '*' )
|
|
|
|
sc = FS_LoadSound( sfx->name + 1, NULL, 0 );
|
|
|
|
else sc = FS_LoadSound( sfx->name, NULL, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !sc ) sc = S_CreateDefaultSound();
|
|
|
|
|
|
|
|
if( sc->rate < SOUND_11k ) // some bad sounds
|
|
|
|
Sound_Process( &sc, SOUND_11k, sc->width, SOUND_RESAMPLE );
|
|
|
|
else if( sc->rate > SOUND_11k && sc->rate < SOUND_22k ) // some bad sounds
|
|
|
|
Sound_Process( &sc, SOUND_22k, sc->width, SOUND_RESAMPLE );
|
|
|
|
else if( sc->rate > SOUND_22k && sc->rate <= SOUND_32k ) // some bad sounds
|
|
|
|
Sound_Process( &sc, SOUND_44k, sc->width, SOUND_RESAMPLE );
|
|
|
|
|
|
|
|
sfx->cache = sc;
|
|
|
|
|
|
|
|
return sfx->cache;
|
|
|
|
}
|
|
|
|
|
|
|
|
// =======================================================================
|
|
|
|
// Load a sound
|
|
|
|
// =======================================================================
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
S_FindName
|
|
|
|
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
sfx_t *S_FindName( const char *pname, int *pfInCache )
|
|
|
|
{
|
|
|
|
sfx_t *sfx;
|
|
|
|
uint i, hash;
|
|
|
|
string name;
|
|
|
|
|
|
|
|
if( !COM_CheckString( pname ) || !dma.initialized )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if( Q_strlen( pname ) >= sizeof( sfx->name ))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
Q_strncpy( name, pname, sizeof( name ));
|
|
|
|
COM_FixSlashes( name );
|
|
|
|
|
|
|
|
// see if already loaded
|
|
|
|
hash = COM_HashKey( name, MAX_SFX_HASH );
|
|
|
|
for( sfx = s_sfxHashList[hash]; sfx; sfx = sfx->hashNext )
|
|
|
|
{
|
|
|
|
if( !Q_strcmp( sfx->name, name ))
|
|
|
|
{
|
|
|
|
if( pfInCache )
|
|
|
|
{
|
|
|
|
// indicate whether or not sound is currently in the cache.
|
|
|
|
*pfInCache = ( sfx->cache != NULL ) ? true : false;
|
|
|
|
}
|
|
|
|
// prolonge registration
|
|
|
|
sfx->servercount = 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 )
|
|
|
|
return NULL;
|
|
|
|
s_numSfx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
sfx = &s_knownSfx[i];
|
|
|
|
memset( sfx, 0, sizeof( *sfx ));
|
|
|
|
if( pfInCache ) *pfInCache = false;
|
|
|
|
Q_strncpy( sfx->name, name, MAX_STRING );
|
|
|
|
sfx->servercount = 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
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( sfx->cache )
|
|
|
|
FS_FreeSound( sfx->cache );
|
|
|
|
memset( sfx, 0, sizeof( *sfx ));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=====================
|
|
|
|
S_BeginRegistration
|
|
|
|
|
|
|
|
=====================
|
|
|
|
*/
|
|
|
|
void S_BeginRegistration( void )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
s_registration_sequence++;
|
|
|
|
snd_ambient = false;
|
|
|
|
|
|
|
|
// check for automatic ambient sounds
|
|
|
|
for( i = 0; i < NUM_AMBIENTS; i++ )
|
|
|
|
{
|
|
|
|
if( !GI->ambientsound[i][0] )
|
|
|
|
continue; // empty slot
|
|
|
|
|
|
|
|
ambient_sfx[i] = S_RegisterSound( GI->ambientsound[i] );
|
|
|
|
if( ambient_sfx[i] ) snd_ambient = true; // allow auto-ambients
|
|
|
|
}
|
|
|
|
|
|
|
|
s_registering = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=====================
|
|
|
|
S_EndRegistration
|
|
|
|
|
|
|
|
=====================
|
|
|
|
*/
|
|
|
|
void S_EndRegistration( void )
|
|
|
|
{
|
|
|
|
sfx_t *sfx;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if( !s_registering || !dma.initialized )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// free any sounds not from this registration sequence
|
|
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
|
|
{
|
|
|
|
if( !sfx->name[0] || !Q_stricmp( sfx->name, "*default" ))
|
|
|
|
continue; // don't release default sound
|
|
|
|
|
|
|
|
if( sfx->servercount != 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( !COM_CheckString( name ) || !dma.initialized )
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if( S_TestSoundChar( name, '!' ))
|
|
|
|
{
|
|
|
|
Q_strncpy( s_sentenceImmediateName, name, sizeof( s_sentenceImmediateName ));
|
|
|
|
return SENTENCE_INDEX;
|
|
|
|
}
|
|
|
|
|
|
|
|
// some stupid mappers used leading '/' or '\' in path to models or sounds
|
|
|
|
if( name[0] == '/' || name[0] == '\\' ) name++;
|
|
|
|
if( name[0] == '/' || name[0] == '\\' ) name++;
|
|
|
|
|
|
|
|
sfx = S_FindName( name, NULL );
|
|
|
|
if( !sfx ) return -1;
|
|
|
|
|
|
|
|
sfx->servercount = s_registration_sequence;
|
|
|
|
if( !s_registering ) S_LoadSound( sfx );
|
|
|
|
|
|
|
|
return sfx - s_knownSfx;
|
|
|
|
}
|
|
|
|
|
|
|
|
sfx_t *S_GetSfxByHandle( sound_t handle )
|
|
|
|
{
|
|
|
|
if( !dma.initialized )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
// create new sfx
|
|
|
|
if( handle == SENTENCE_INDEX )
|
|
|
|
return S_FindName( s_sentenceImmediateName, NULL );
|
|
|
|
|
|
|
|
if( handle < 0 || handle >= s_numSfx )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return &s_knownSfx[handle];
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
S_InitSounds
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void S_InitSounds( void )
|
|
|
|
{
|
|
|
|
// create unused 0-entry
|
|
|
|
Q_strncpy( s_knownSfx->name, "*default", MAX_QPATH );
|
|
|
|
s_knownSfx->hashValue = COM_HashKey( s_knownSfx->name, MAX_SFX_HASH );
|
|
|
|
s_knownSfx->hashNext = s_sfxHashList[s_knownSfx->hashValue];
|
|
|
|
s_sfxHashList[s_knownSfx->hashValue] = s_knownSfx;
|
|
|
|
s_knownSfx->cache = S_CreateDefaultSound();
|
|
|
|
s_numSfx = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
S_FreeSounds
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void S_FreeSounds( void )
|
|
|
|
{
|
|
|
|
sfx_t *sfx;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if( !dma.initialized )
|
|
|
|
return;
|
|
|
|
|
|
|
|
// stop all sounds
|
|
|
|
S_StopAllSounds( true );
|
|
|
|
|
|
|
|
// free all sounds
|
|
|
|
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
|
|
|
|
S_FreeSound( sfx );
|
|
|
|
|
|
|
|
memset( s_knownSfx, 0, sizeof( s_knownSfx ));
|
|
|
|
memset( s_sfxHashList, 0, sizeof( s_sfxHashList ));
|
|
|
|
|
|
|
|
s_numSfx = 0;
|
|
|
|
}
|