2013-04-19 02:52:48 +00:00
/*****************************************************************************
* name : snd_dma . c
*
* desc : main control for any streaming sound output device
*
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// leave this as first line for PCH reasons...
//
// #include "../server/exe_headers.h"
# include "snd_local_console.h"
# include "snd_music.h"
// #include "../../toolbox/zlib/zlib.h"
# include "../client/client.h"
// Doesn't do anything on Xbox
qboolean s_shutUp = qfalse ;
# ifdef _XBOX
# include <Xtl.h>
# endif
# ifdef _GAMECUBE
typedef const char * LPCSTR ;
# endif
static void S_Play_f ( void ) ;
# ifndef _JK2MP
static void S_PlayEx_f ( void ) ;
# endif
static void S_SoundList_f ( void ) ;
static void S_Music_f ( void ) ;
void S_Update_ ( ) ;
void S_StopAllSounds ( void ) ;
static void S_UpdateBackgroundTrack ( void ) ;
unsigned int S_HashName ( const char * name ) ;
static int SND_FreeSFXMem ( sfx_t * sfx ) ;
/*static void S_FreeAllSFXMem(void);
static void S_UnCacheDynamicMusic ( void ) ;
*/
extern unsigned long crc32 ( unsigned long crc , const unsigned char * buf , unsigned long len ) ;
extern int Sys_GetFileCodeSize ( int code ) ;
extern void Sys_StreamInit ( void ) ;
extern void Sys_StreamShutdown ( void ) ;
qboolean SND_RegisterAudio_Clean ( void ) ;
void S_KillEntityChannel ( int entnum , int chan ) ;
//////////////////////////
//
// vars for bgrnd music track...
//
typedef struct
{
//
// disk-load stuff
//
char sLoadedDataName [ MAX_QPATH ] ;
int iFileCode ;
int iFileSeekTo ;
bool bLoaded ;
//
// remaining dynamic fields...
//
int iXFadeVolumeSeekTime ;
int iXFadeVolumeSeekTo ; // when changing this, set the above timer to Sys_Milliseconds().
// Note that this should be thought of more as an up/down bool rather than as a
// number now, in other words set it only to 0 or 255. I'll probably change this
// to actually be a bool later.
int iXFadeVolume ; // 0 = silent, 255 = max mixer vol, though still modulated via overall music_volume
float fSmoothedOutVolume ;
qboolean bActive ; // whether playing or not
qboolean bExists ; // whether was even loaded for this level (ie don't try and start playing it)
//
// new dynamic fields...
//
qboolean bTrackSwitchPending ;
qboolean bLooping ;
MusicState_e eTS_NewState ;
float fTS_NewTime ;
//
// Generic...
//
int s_backgroundSize ;
int s_backgroundBPS ;
void Rewind ( )
{
iFileSeekTo = 0 ;
}
void SeekTo ( float fTime )
{
iFileSeekTo = ( int ) ( ( float ) ( s_backgroundBPS ) * fTime ) ;
}
float TotalTime ( void )
{
return ( float ) ( s_backgroundSize ) / ( float ) ( s_backgroundBPS ) ;
}
float PlayTime ( void )
{
ALfloat playTime ;
alGetStreamf ( AL_TIME , & playTime ) ;
return playTime ;
}
float ElapsedTime ( void )
{
return fmod ( PlayTime ( ) , TotalTime ( ) ) ;
}
} MusicInfo_t ;
static void S_SetDynamicMusicState ( MusicState_e musicState ) ;
# define fDYNAMIC_XFADE_SECONDS (1.f)
static MusicInfo_t tMusic_Info [ eBGRNDTRACK_NUMBEROF ] = { 0 } ;
static qboolean bMusic_IsDynamic = qfalse ;
static MusicState_e eMusic_StateActual = eBGRNDTRACK_EXPLORE ; // actual state, can be any enum
static MusicState_e eMusic_StateRequest = eBGRNDTRACK_EXPLORE ; // requested state, can only be explore, action, boss, or silence
static char sMusic_BackgroundLoop [ MAX_QPATH ] = { 0 } ; // only valid for non-dynamic music
static char sInfoOnly_CurrentDynamicMusicSet [ 64 ] ; // any old reasonable size, only has to fit stuff like "kejim_post"
//
//////////////////////////
// =======================================================================
// Internal sound data & structures
// =======================================================================
// only begin attenuating sound volumes when outside the FULLVOLUME range
# define SOUND_FULLVOLUME 256
# define SOUND_ATTENUATE 0.0008f
# define VOICE_ATTENUATE 0.004f
// This number has dramatic affects on volume. QA has
// determined that 100 is too quiet in some spots and
// 200 is too loud in others. Modify with care...
# define SOUND_REF_DIST_BASE 150.f
# define SOUND_UPDATE_TIME 100
const float SOUND_FMAXVOL = 0.75 ; //1.0;
const int SOUND_MAXVOL = 255 ;
int s_soundStarted ;
qboolean s_soundMuted ;
int s_loopEnabled ;
int s_updateTime ;
struct listener_t
{
ALuint handle ;
ALfloat pos [ 3 ] ;
ALfloat orient [ 6 ] ;
int entnum ;
} ;
# define SND_MAX_LISTENERS 2
static listener_t s_listeners [ SND_MAX_LISTENERS ] ;
static int s_numListeners ;
static int s_numChannels ; // Number of AL Sources == Num of Channels
# ifdef _XBOX
# define MAX_CHANNELS_2D 64
# define MAX_CHANNELS_3D 64
# else
# define MAX_CHANNELS_2D 30
# define MAX_CHANNELS_3D 30
# endif
# define MAX_CHANNELS (MAX_CHANNELS_2D + MAX_CHANNELS_3D)
static channel_t * s_channels ;
# define MAX_SFX 2048
# define INVALID_CODE 0
static sfx_t * s_sfxBlock ;
static int * s_sfxCodes ;
static bool s_registered = false ;
static int s_defaultSound = 0 ;
typedef struct
{
int volume ;
vec3_t origin ;
sfx_t * sfx ;
int entnum ;
int entchannel ;
bool bProcessed ;
bool bMarked ;
} loopSound_t ;
# define MAX_LOOP_SOUNDS 32
static int numLoopSounds ;
static loopSound_t * loopSounds ;
int * s_entityWavVol = NULL ;
cvar_t * s_effects_volume ;
cvar_t * s_music_volume ;
cvar_t * s_voice_volume ;
cvar_t * s_testsound ;
cvar_t * s_allowDynamicMusic ;
cvar_t * s_show ;
cvar_t * s_separation ;
cvar_t * s_CPUType ;
cvar_t * s_debugdynamic ;
cvar_t * s_soundpoolmegs ;
cvar_t * s_language ; // note that this is distinct from "g_language"
void S_SoundInfo_f ( void ) {
Com_Printf ( " ----- Sound Info ----- \n " ) ;
if ( ! s_soundStarted ) {
Com_Printf ( " sound system not started \n " ) ;
} else {
if ( s_soundMuted ) {
Com_Printf ( " sound system is muted \n " ) ;
}
}
S_DisplayFreeMemory ( ) ;
Com_Printf ( " ---------------------- \n " ) ;
}
/*
= = = = = = = = = = = = = = = =
S_Init
= = = = = = = = = = = = = = = =
*/
void S_Init ( void ) {
ALCcontext * ALCContext = NULL ;
ALCdevice * ALCDevice = NULL ;
cvar_t * cv ;
Com_Printf ( " \n ------- sound initialization ------- \n " ) ;
AS_Init ( ) ;
s_effects_volume = Cvar_Get ( " s_effects_volume " , " 0.5 " , CVAR_ARCHIVE ) ;
s_voice_volume = Cvar_Get ( " s_voice_volume " , " 1.0 " , CVAR_ARCHIVE ) ;
s_music_volume = Cvar_Get ( " s_music_volume " , " 0.25 " , CVAR_ARCHIVE ) ;
s_separation = Cvar_Get ( " s_separation " , " 0.5 " , CVAR_ARCHIVE ) ;
s_allowDynamicMusic = Cvar_Get ( " s_allowDynamicMusic " , " 1 " , CVAR_ARCHIVE ) ;
s_show = Cvar_Get ( " s_show " , " 0 " , CVAR_CHEAT ) ;
s_testsound = Cvar_Get ( " s_testsound " , " 0 " , CVAR_CHEAT ) ;
s_debugdynamic = Cvar_Get ( " s_debugdynamic " , " 0 " , CVAR_CHEAT ) ;
s_CPUType = Cvar_Get ( " sys_cpuid " , " " , 0 ) ;
s_soundpoolmegs = Cvar_Get ( " s_soundpoolmegs " , " 6 " , CVAR_ARCHIVE ) ;
s_language = Cvar_Get ( " s_language " , " english " , CVAR_ARCHIVE | CVAR_NORESTART ) ;
cv = Cvar_Get ( " s_initsound " , " 1 " , CVAR_ROM ) ;
if ( ! cv - > integer ) {
s_soundStarted = 0 ; // needed in case you set s_initsound to 0 midgame then snd_restart (div0 err otherwise later)
Com_Printf ( " not initializing. \n " ) ;
Com_Printf ( " ------------------------------------ \n " ) ;
return ;
}
Cmd_AddCommand ( " play " , S_Play_f ) ;
# ifndef _JK2MP
Cmd_AddCommand ( " playex " , S_PlayEx_f ) ;
# endif
Cmd_AddCommand ( " music " , S_Music_f ) ;
Cmd_AddCommand ( " soundlist " , S_SoundList_f ) ;
Cmd_AddCommand ( " soundinfo " , S_SoundInfo_f ) ;
Cmd_AddCommand ( " soundstop " , S_StopAllSounds ) ;
s_entityWavVol = new int [ MAX_GENTITIES ] ;
// clear out the lip synching override array
memset ( s_entityWavVol , 0 , sizeof ( int ) * MAX_GENTITIES ) ;
ALCDevice = alcOpenDevice ( ( ALubyte * ) " DirectSound3D " ) ;
if ( ! ALCDevice )
return ;
//Create context(s)
ALCContext = alcCreateContext ( ALCDevice , NULL ) ;
if ( ! ALCContext )
return ;
//Set active context
alcMakeContextCurrent ( ALCContext ) ;
if ( alcGetError ( ALCDevice ) ! = ALC_NO_ERROR )
return ;
s_channels = new channel_t [ MAX_CHANNELS ] ;
s_sfxBlock = new sfx_t [ MAX_SFX ] ;
s_sfxCodes = new int [ MAX_SFX ] ;
memset ( s_sfxCodes , INVALID_CODE , sizeof ( int ) * MAX_SFX ) ;
loopSounds = new loopSound_t [ MAX_LOOP_SOUNDS ] ;
S_StopAllSounds ( ) ;
s_soundStarted = 1 ;
s_soundMuted = 1 ;
s_loopEnabled = 0 ;
s_updateTime = 0 ;
S_SoundInfo_f ( ) ;
memset ( s_channels , 0 , sizeof ( channel_t ) * MAX_CHANNELS ) ;
s_numChannels = 0 ;
// create music channel
alGenStream ( ) ;
Com_Printf ( " ------------------------------------ \n " ) ;
S_InitLoad ( ) ;
}
// only called from snd_restart. QA request...
//
void S_ReloadAllUsedSounds ( void )
{
if ( s_soundStarted & & ! s_soundMuted )
{
// new bit, reload all soundsthat are used on the current level->..
//
for ( int i = 0 ; i < MAX_SFX ; + + i )
{
if ( s_sfxCodes [ i ] = = INVALID_CODE | | s_sfxCodes [ i ] = = s_defaultSound ) continue ;
sfx_t * sfx = & s_sfxBlock [ i ] ;
if ( ( sfx - > iFlags & SFX_FLAG_UNLOADED ) & &
! ( sfx - > iFlags & ( SFX_FLAG_DEFAULT | SFX_FLAG_DEMAND ) ) )
{
S_StartLoadSound ( sfx ) ;
}
}
}
}
// =======================================================================
// Shutdown sound engine
// =======================================================================
void S_Shutdown ( void )
{
ALCcontext * ALCContext ;
ALCdevice * ALCDevice ;
int i ;
delete [ ] s_entityWavVol ;
s_entityWavVol = NULL ;
if ( ! s_soundStarted ) {
return ;
}
alDeleteStream ( ) ;
// Release all the AL Sources (including Music channel (Source 0))
for ( i = 0 ; i < s_numChannels ; i + + )
{
alDeleteSources ( 1 , & ( s_channels [ i ] . alSource ) ) ;
}
S_FreeAllSFXMem ( ) ;
S_UnCacheDynamicMusic ( ) ;
// Release listeners
for ( i = 0 ; i < s_numListeners ; + + i )
{
alDeleteListeners ( 1 , & s_listeners [ i ] . handle ) ;
}
s_numListeners = 0 ;
delete [ ] s_channels ;
delete [ ] s_sfxBlock ;
delete [ ] s_sfxCodes ;
delete [ ] loopSounds ;
// Get active context
ALCContext = alcGetCurrentContext ( ) ;
// Get device for active context
ALCDevice = alcGetContextsDevice ( ALCContext ) ;
// Release context(s)
alcDestroyContext ( ALCContext ) ;
// Close device
alcCloseDevice ( ALCDevice ) ;
s_numChannels = 0 ;
s_soundStarted = 0 ;
Cmd_RemoveCommand ( " play " ) ;
Cmd_RemoveCommand ( " music " ) ;
Cmd_RemoveCommand ( " stopsound " ) ;
Cmd_RemoveCommand ( " soundlist " ) ;
Cmd_RemoveCommand ( " soundinfo " ) ;
AS_Free ( ) ;
S_CloseLoad ( ) ;
}
/*
Mutes / Unmutes all OpenAL sound
*/
void S_AL_MuteAllSounds ( qboolean bMute )
{
if ( ! s_soundStarted ) return ;
if ( bMute ) alGain ( 0.f ) ;
else alGain ( 1.f ) ;
}
void S_SetVolume ( float volume )
{
if ( ! s_soundStarted ) return ;
alGain ( volume ) ;
alUpdate ( ) ;
}
// =======================================================================
// Load a sound
// =======================================================================
/*
= = = = = = = = = = = = = = = = = =
S_FixMusicFileExtension
= = = = = = = = = = = = = = = = = =
*/
char * S_FixMusicFileName ( const char * name )
{
static char xname [ MAX_QPATH ] ;
# if defined(_XBOX)
const char * ext = " wxb " ;
# elif defined(_WINDOWS)
const char * ext = " wav " ;
# elif defined(_GAMECUBE)
const char * ext = " adp " ;
# endif
Q_strncpyz ( xname , name , sizeof ( xname ) ) ;
if ( xname [ strlen ( xname ) - 4 ] ! = ' . ' )
{
strcat ( xname , " . " ) ;
strcat ( xname , ext ) ;
}
else
{
int len = strlen ( xname ) ;
xname [ len - 3 ] = ext [ 0 ] ;
xname [ len - 2 ] = ext [ 1 ] ;
xname [ len - 1 ] = ext [ 2 ] ;
}
# ifdef _GAMECUBE
if ( ! strncmp ( " music/ " , xname , 6 ) | |
! strncmp ( " music \\ " , xname , 6 ) )
{
char chan_name [ MAX_QPATH ] ;
/*
ALint is_stereo ;
alGeti ( AL_STEREO , & is_stereo ) ;
sprintf ( chan_name , " music-%s/%s " ,
is_stereo ? " stereo " : " mono " , & xname [ 6 ] ) ;
strcpy ( xname , chan_name ) ;
*/
sprintf ( chan_name , " music-stereo/%s " , & xname [ 6 ] ) ;
strcpy ( xname , chan_name ) ;
}
# endif
return xname ;
}
/*
= = = = = = = = = = = = = = = = = =
S_HashName
= = = = = = = = = = = = = = = = = =
*/
unsigned int S_HashName ( const char * name ) {
if ( ! name ) {
Com_Error ( ERR_FATAL , " S_HashName: NULL \n " ) ;
}
if ( ! name [ 0 ] ) {
Com_Error ( ERR_FATAL , " S_HashName: empty name \n " ) ;
}
if ( strlen ( name ) > = MAX_QPATH ) {
Com_Error ( ERR_FATAL , " Sound name too long: %s " , name ) ;
}
char sSoundNameNoExt [ MAX_QPATH ] ;
COM_StripExtension ( name , sSoundNameNoExt ) ;
Q_strlwr ( sSoundNameNoExt ) ;
for ( int i = 0 ; i < strlen ( sSoundNameNoExt ) ; + + i )
{
if ( sSoundNameNoExt [ i ] = = ' \\ ' ) sSoundNameNoExt [ i ] = ' / ' ;
}
return crc32 ( 0 , ( const byte * ) sSoundNameNoExt , strlen ( sSoundNameNoExt ) ) ;
}
/*
= = = = = = = = = = = = = = = = = = =
S_DisableSounds
Disables sounds until the next S_BeginRegistration .
This is called when the hunk is cleared and the sounds
are no longer valid .
= = = = = = = = = = = = = = = = = = =
*/
void S_DisableSounds ( void ) {
if ( ! s_soundStarted ) return ;
S_StopAllSounds ( ) ;
SND_RegisterAudio_Clean ( ) ; // unregister sounds
s_soundMuted = qtrue ;
}
void S_SetLoopState ( qboolean s ) {
if ( ! s_soundStarted ) return ;
s_loopEnabled = s ;
}
void S_CreateSources ( void ) {
int i ;
// Remove any old sources
for ( i = 0 ; i < s_numChannels ; + + i )
{
alDeleteSources ( 1 , & s_channels [ i ] . alSource ) ;
}
s_numChannels = 0 ;
// Create as many AL Sources (up to Max) as possible
int limit = MAX_CHANNELS_2D + MAX_CHANNELS_3D / s_numListeners ;
for ( i = 0 ; i < limit ; i + + )
{
if ( i < MAX_CHANNELS_2D )
{
alGenSources2D ( 1 , & s_channels [ i ] . alSource ) ;
s_channels [ i ] . b2D = true ;
}
else
{
alGenSources3D ( 1 , & s_channels [ i ] . alSource ) ;
s_channels [ i ] . b2D = false ;
}
if ( alGetError ( ) ! = AL_NO_ERROR )
{
// Reached limit of sources
break ;
}
if ( ! s_channels [ i ] . b2D )
{
alSourcef ( s_channels [ i ] . alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE ) ;
}
s_numChannels + + ;
}
assert ( s_numChannels > MAX_CHANNELS_2D ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
S_BeginRegistration
= = = = = = = = = = = = = = = = = = = = =
*/
extern bool debugSoundOff ;
void S_BeginRegistration ( void )
{
if ( ! s_soundStarted ) return ;
int i ;
int num_listeners = 1 ;
if ( ! debugSoundOff ) {
s_soundMuted = qfalse ; // we can play again
}
// Create listeners
assert ( num_listeners < = SND_MAX_LISTENERS ) ;
if ( num_listeners < s_numListeners )
{
// remove some listeners
for ( i = num_listeners ; i < s_numListeners ; + + i )
{
alDeleteListeners ( 1 , & s_listeners [ i ] . handle ) ;
}
s_numListeners = num_listeners ;
S_CreateSources ( ) ;
}
else if ( num_listeners > s_numListeners )
{
// add some listeners
for ( i = s_numListeners ; i < num_listeners ; + + i )
{
memset ( & s_listeners [ i ] , 0 , sizeof ( listener_t ) ) ;
s_listeners [ i ] . entnum = i ;
s_listeners [ i ] . orient [ 2 ] = - 1 ;
s_listeners [ i ] . orient [ 4 ] = 1 ;
alGenListeners ( 1 , & s_listeners [ i ] . handle ) ;
alListenerfv ( s_listeners [ i ] . handle , AL_POSITION , s_listeners [ i ] . pos ) ;
alListenerfv ( s_listeners [ i ] . handle , AL_ORIENTATION , s_listeners [ i ] . orient ) ;
}
s_numListeners = num_listeners ;
S_CreateSources ( ) ;
}
S_SetLoopState ( qtrue ) ;
if ( ! s_registered ) {
s_defaultSound = S_RegisterSound ( " sound/null.wav " ) ;
S_LoadSound ( s_defaultSound ) ;
s_registered = true ;
}
}
/*
= = = = = = = = = = = = = = = = = =
S_LookupSfx
= = = = = = = = = = = = = = = = = =
*/
sfxHandle_t S_LookupSfx ( int hash )
{
for ( int i = 0 ; i < MAX_SFX ; + + i )
{
if ( s_sfxCodes [ i ] = = hash )
{
return i ;
}
}
return - 1 ;
}
/*
= = = = = = = = = = = = = = = = = =
S_AllocSfx
= = = = = = = = = = = = = = = = = =
*/
sfxHandle_t S_AllocSfx ( int hash )
{
for ( int i = 0 ; i < MAX_SFX ; + + i )
{
if ( s_sfxCodes [ i ] = = INVALID_CODE )
{
s_sfxCodes [ i ] = hash ;
return i ;
}
}
return - 1 ;
}
/*
= = = = = = = = = = = = = = = = = =
S_RegisterSound
Creates a default buzz sound if the file can ' t be loaded
= = = = = = = = = = = = = = = = = =
*/
sfxHandle_t S_RegisterSound ( const char * name )
{
sfx_t * sfx ;
unsigned int hash ;
sfxHandle_t handle ;
if ( ! s_soundStarted ) {
return 0 ;
}
if ( strlen ( name ) > = MAX_QPATH | | ! name [ 0 ] ) {
Com_Printf ( S_COLOR_RED " Sound name exceeds MAX_QPATH - %s \n " , name ) ;
return s_defaultSound ;
}
hash = S_HashName ( name ) ;
handle = S_LookupSfx ( hash ) ;
if ( handle < 0 )
{
handle = S_AllocSfx ( hash ) ;
if ( handle < 0 )
Com_Error ( ERR_DROP , " No free sound channels " ) ;
sfx = & s_sfxBlock [ handle ] ;
memset ( sfx , 0 , sizeof ( sfx_t ) ) ;
if ( strlen ( name ) < 5 | | name [ 0 ] = = ' * ' ) sfx - > iFileCode = - 1 ;
else sfx - > iFileCode = S_GetFileCode ( name ) ;
sfx - > iFlags | = SFX_FLAG_UNLOADED ;
}
else
{
sfx = & s_sfxBlock [ handle ] ;
}
SND_TouchSFX ( sfx ) ;
if ( sfx - > iFileCode = = - 1 ) sfx - > iFlags | = SFX_FLAG_DEFAULT ;
if ( strstr ( name , " chars " ) | |
strstr ( name , " chr_d " ) | |
strstr ( name , " chr_f " ) )
{
sfx - > iFlags | = SFX_FLAG_VOICE ;
sfx - > iFlags | = SFX_FLAG_DEMAND ;
}
if ( sfx - > iFlags & SFX_FLAG_DEFAULT )
{
sfx - > iFlags | = SFX_FLAG_RESIDENT ;
return s_defaultSound ;
}
return handle ;
}
//=============================================================================
channel_t * S_FindFurthestChannel ( void )
{
int ch_idx ;
channel_t * ch ;
channel_t * ch_firstToDie = NULL ;
int li_idx ;
listener_t * li ;
int longestDist = - 1 ;
int dist ;
for ( li_idx = 0 , li = s_listeners ; li_idx < s_numListeners ; + + li_idx , + + li )
{
for ( ch_idx = MAX_CHANNELS_2D , ch = s_channels + ch_idx ;
ch_idx < s_numChannels ; ch_idx + + , ch + + )
{
dist =
( ( li - > pos [ 0 ] - ch - > origin [ 0 ] ) * ( li - > pos [ 0 ] - ch - > origin [ 0 ] ) ) +
( ( li - > pos [ 1 ] - ch - > origin [ 1 ] ) * ( li - > pos [ 1 ] - ch - > origin [ 1 ] ) ) +
( ( li - > pos [ 2 ] - ch - > origin [ 2 ] ) * ( li - > pos [ 2 ] - ch - > origin [ 2 ] ) ) ;
if ( dist > longestDist )
{
longestDist = dist ;
ch_firstToDie = ch ;
}
}
}
return ch_firstToDie ;
}
static bool IsListenerEnt ( int entnum )
{
for ( int i = 0 ; i < s_numListeners ; + + i )
{
if ( s_listeners [ i ] . entnum = = entnum ) return true ;
}
return false ;
}
/*
= = = = = = = = = = = = = = = = =
S_PickChannel
= = = = = = = = = = = = = = = = =
*/
channel_t * S_PickChannel ( int entnum , int entchannel , bool is2D , sfx_t * sfx )
{
int ch_idx ;
channel_t * ch , * ch_firstToDie ;
bool foundChan = false ;
if ( entchannel < 0 )
{
Com_Error ( ERR_DROP , " S_PickChannel: entchannel<0 " ) ;
}
// Check for replacement sound, or find the best one to replace
ch_firstToDie = s_channels ;
unsigned int age = 0xFFFFFFFF ;
for ( ch_idx = 0 , ch = s_channels + ch_idx ; ch_idx < s_numChannels ; ch_idx + + , ch + + )
{
// Special check to prevent 2d voices from being played
// twice in 2 player games...
if ( is2D & & ch - > b2D & &
sfx = = ch - > thesfx & &
ch - > bPlaying & &
( sfx - > iFlags & SFX_FLAG_VOICE ) )
{
return NULL ;
}
// See if the channel is free
if ( ! ch - > thesfx & & is2D = = ch - > b2D & & ch - > iLastPlayTime < age )
{
ch_firstToDie = ch ;
age = ch - > iLastPlayTime ;
foundChan = true ;
}
}
if ( ! foundChan )
{
for ( ch_idx = 0 , ch = s_channels + ch_idx ; ch_idx < s_numChannels ; ch_idx + + , ch + + )
{
if ( ( ch - > entnum = = entnum ) & &
( ch - > entchannel = = entchannel ) & &
( ch - > entchannel ! = CHAN_AMBIENT ) & &
( ! IsListenerEnt ( ch - > entnum ) ) & &
( ch - > b2D = = is2D ) & &
( ! ch_firstToDie - > thesfx | |
! ( ch_firstToDie - > thesfx - > iFlags & SFX_FLAG_LOADING ) ) )
{
// Same entity and same type of sound effect (entchannel)
ch_firstToDie = ch ;
foundChan = true ;
break ;
}
}
}
if ( ! foundChan )
{
if ( is2D )
{
// Find random sound effect
ch_firstToDie = s_channels + ( rand ( ) % MAX_CHANNELS_2D ) ;
}
else
{
// Find sound effect furthest from listeners
ch_firstToDie = S_FindFurthestChannel ( ) ;
}
}
assert ( ch_firstToDie - > b2D = = is2D ) ;
if ( ch_firstToDie - > thesfx & & ch_firstToDie - > thesfx - > iFlags & SFX_FLAG_LOADING )
{
// If the sound is loading, just give up...
return NULL ;
}
if ( ch_firstToDie - > bPlaying )
{
# ifdef _XBOX
// We have an insane amount of channels on the Xbox
// and stopping one is a blocking operation. Let's
// just assume that no one will care if a sound is
// dropped when over 100 are already playing...
return NULL ;
# else
// Stop sound
alSourceStop ( ch_firstToDie - > alSource ) ;
ch_firstToDie - > bPlaying = false ;
# endif
}
// Reset channel variables
alSourcei ( ch_firstToDie - > alSource , AL_BUFFER , 0 ) ;
ch_firstToDie - > thesfx = NULL ;
ch_firstToDie - > bLooping = false ;
return ch_firstToDie ;
}
// =======================================================================
// Start a sound effect
// =======================================================================
static void SetChannelOrigin ( channel_t * ch , const vec3_t origin , int entityNum )
{
if ( origin )
{
ch - > origin [ 0 ] = origin [ 0 ] ;
ch - > origin [ 1 ] = origin [ 1 ] ;
ch - > origin [ 2 ] = origin [ 2 ] ;
}
else
{
vec3_t pos ;
extern void G_EntityPosition ( int i , vec3_t ret ) ;
G_EntityPosition ( entityNum , pos ) ;
ch - > origin [ 0 ] = pos [ 0 ] ;
ch - > origin [ 1 ] = pos [ 1 ] ;
ch - > origin [ 2 ] = pos [ 2 ] ;
}
ch - > bOriginDirty = true ;
}
/*
= = = = = = = = = = = = = = = = = = = =
S_StartAmbientSound
Starts an ambient , ' one - shot " sound.
= = = = = = = = = = = = = = = = = = = =
*/
void S_StartAmbientSound ( const vec3_t origin , int entityNum , unsigned char volume , sfxHandle_t sfxHandle )
{
channel_t * ch ;
/*const*/ sfx_t * sfx ;
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
if ( sfxHandle < 0 | | sfxHandle > MAX_SFX | | s_sfxCodes [ sfxHandle ] = = INVALID_CODE ) {
return ;
}
if ( ! origin & & ( entityNum < 0 | | entityNum > MAX_GENTITIES ) ) {
Com_Error ( ERR_DROP , " S_StartAmbientSound: bad entitynum %i " , entityNum ) ;
}
sfx = & s_sfxBlock [ sfxHandle ] ;
if ( sfx - > iFlags & SFX_FLAG_UNLOADED ) {
S_StartLoadSound ( sfx ) ;
}
SND_TouchSFX ( sfx ) ;
// pick a channel to play on
bool is2D = false ;
for ( int i = 0 ; i < s_numListeners ; + + i )
{
if ( ( entityNum = = s_listeners [ i ] . entnum & & ! origin ) | |
( origin & &
origin [ 0 ] = = s_listeners [ i ] . pos [ 0 ] & &
origin [ 1 ] = = s_listeners [ i ] . pos [ 1 ] & &
origin [ 2 ] = = s_listeners [ i ] . pos [ 2 ] ) )
{
is2D = true ;
break ;
}
}
ch = S_PickChannel ( entityNum , CHAN_AMBIENT , is2D , NULL ) ;
if ( ! ch ) {
return ;
}
if ( ! is2D )
{
SetChannelOrigin ( ch , origin , entityNum ) ;
}
ch - > master_vol = volume ;
ch - > fLastVolume = - 1 ;
ch - > entnum = entityNum ;
ch - > entchannel = CHAN_AMBIENT ;
ch - > thesfx = sfx ;
}
/*
= = = = = = = = = = = = = = = = = = = =
S_MuteSound
Mutes sound on specified channel for specified entity .
This seems to be implemented quite incorrectly on PC . I
think the following is what this function should do . . .
Perhaps we should actually be changing the volume on all
the channels that meet our criteria , but for now we ' ll just
kill the sounds and hope it does what is expected .
= = = = = = = = = = = = = = = = = = = =
*/
void S_MuteSound ( int entityNum , int entchannel )
{
S_KillEntityChannel ( entityNum , entchannel ) ;
/*
if ( ! s_soundStarted ) {
return ;
}
//I guess this works.
channel_t * ch = S_PickChannel ( entityNum , entchannel ) ;
if ( ! ch )
{
return ;
}
ch - > master_vol = 0 ;
ch - > entnum = 0 ;
ch - > entchannel = 0 ;
ch - > thesfx = 0 ;
ch - > startSample = 0 ;
ch - > leftvol = 0 ;
ch - > rightvol = 0 ;
*/
}
/*
= = = = = = = = = = = = = = = = = = = =
S_StartSound
Validates the parms and ques the sound up
if pos is NULL , the sound will be dynamically sourced from the entity
Entchannel 0 will never override a playing sound
= = = = = = = = = = = = = = = = = = = =
*/
void S_StartSound ( const vec3_t origin , int entityNum , int entchannel , sfxHandle_t sfxHandle )
{
channel_t * ch ;
/*const*/ sfx_t * sfx ;
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
if ( sfxHandle < 0 | | sfxHandle > MAX_SFX | | s_sfxCodes [ sfxHandle ] = = INVALID_CODE ) {
return ;
}
if ( ! origin & & ( entityNum < 0 | | entityNum > MAX_GENTITIES ) ) {
Com_Error ( ERR_DROP , " S_StartSound: bad entitynum %i " , entityNum ) ;
}
sfx = & s_sfxBlock [ sfxHandle ] ;
if ( sfx - > iFlags & SFX_FLAG_UNLOADED ) {
S_StartLoadSound ( sfx ) ;
}
SND_TouchSFX ( sfx ) ;
// pick a channel to play on
bool is2D = false ;
for ( int i = 0 ; i < s_numListeners ; + + i )
{
if ( ( entityNum = = s_listeners [ i ] . entnum & & ! origin ) | |
( origin & &
origin [ 0 ] = = s_listeners [ i ] . pos [ 0 ] & &
origin [ 1 ] = = s_listeners [ i ] . pos [ 1 ] & &
origin [ 2 ] = = s_listeners [ i ] . pos [ 2 ] ) )
{
is2D = true ;
break ;
}
}
ch = S_PickChannel ( entityNum , entchannel , is2D , sfx ) ;
if ( ! ch ) {
return ;
}
if ( ! is2D )
{
SetChannelOrigin ( ch , origin , entityNum ) ;
}
if ( entchannel = = CHAN_AUTO & & ( sfx - > iFlags & SFX_FLAG_VOICE ) ) {
entchannel = CHAN_VOICE ; // Compensate of the incompetance of others. Yeah. ;)
// entchannel = CHAN_VOICE_ATTEN; // Super hack to put Rancor noises on a different channel E3!
}
ch - > master_vol = SOUND_MAXVOL ; //FIXME: Um.. control?
ch - > fLastVolume = - 1 ;
ch - > entnum = entityNum ;
ch - > entchannel = entchannel ;
ch - > thesfx = sfx ;
if ( entchannel < CHAN_AMBIENT & & IsListenerEnt ( ch - > entnum ) )
{
ch - > master_vol = SOUND_MAXVOL * SOUND_FMAXVOL ; //this won't be attenuated so let it scale down
}
if ( entchannel = = CHAN_VOICE | | entchannel = = CHAN_VOICE_ATTEN | | entchannel = = CHAN_VOICE_GLOBAL )
{
s_entityWavVol [ ch - > entnum ] = - 1 ; //we've started the sound but it's silent for now
}
}
/*
= = = = = = = = = = = = = = = = = =
S_StartLocalSound
= = = = = = = = = = = = = = = = = =
*/
void S_StartLocalSound ( sfxHandle_t sfxHandle , int channelNum ) {
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
// Play a 2D sound -- doesn't matter which listener we use
S_StartSound ( NULL , 0 , channelNum , sfxHandle ) ;
}
/*
= = = = = = = = = = = = = = = = = =
S_StartLocalLoopingSound
= = = = = = = = = = = = = = = = = =
*/
void S_StartLocalLoopingSound ( sfxHandle_t sfxHandle ) {
vec3_t nullVec = { 0 , 0 , 0 } ;
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
// Play a 2D sound -- doesn't matter which listener we use
S_AddLoopingSound ( 0 , nullVec , nullVec , sfxHandle , CHAN_AMBIENT ) ;
}
// Kill an voice sounds from an entity
void S_KillEntityChannel ( int entnum , int chan )
{
int i ;
channel_t * ch ;
if ( ! s_soundStarted ) {
return ;
}
if ( entnum < s_numListeners & & chan = = CHAN_VOICE ) {
// don't kill player death sounds
return ;
}
ch = s_channels ;
for ( i = 0 ; i < s_numChannels ; i + + , ch + + )
{
if ( ch - > bPlaying & &
ch - > entnum = = entnum & &
ch - > entchannel = = chan )
{
alSourceStop ( ch - > alSource ) ;
ch - > bPlaying = false ;
alSourcei ( ch - > alSource , AL_BUFFER , 0 ) ;
ch - > thesfx = NULL ;
ch - > bLooping = false ;
}
}
}
/*
= = = = = = = = = = = = = = = = = =
S_StopLoopingSound
Stops all active looping sounds on a specified entity .
Sort of a slow method though , isn ' t there some better way ?
= = = = = = = = = = = = = = = = = =
*/
void S_StopLoopingSound ( int entnum )
{
if ( ! s_soundStarted ) {
return ;
}
int i = 0 ;
while ( i < numLoopSounds )
{
if ( loopSounds [ i ] . entnum = = entnum )
{
int x = i + 1 ;
while ( x < numLoopSounds )
{
memcpy ( & loopSounds [ x - 1 ] , & loopSounds [ x ] , sizeof ( loopSounds [ x ] ) ) ;
x + + ;
}
numLoopSounds - - ;
}
i + + ;
}
}
// returns length in milliseconds of supplied sound effect... (else 0 for bad handle now)
//
float S_GetSampleLengthInMilliSeconds ( sfxHandle_t sfxHandle )
{
sfx_t * sfx ;
if ( ! s_soundStarted )
{ //we have no sound, so let's just make a reasonable guess
return 512 ;
}
if ( s_sfxCodes [ sfxHandle ] = = INVALID_CODE ) {
return 0.0f ;
}
sfx = & s_sfxBlock [ sfxHandle ] ;
int size = Sys_GetFileCodeSize ( sfx - > iFileCode ) ;
if ( size < 0 ) return 0 ;
return 1000 * size / ( 22050 / 2 ) ;
}
/*
= = = = = = = = = = = = = = = = = =
S_LoadSound
= = = = = = = = = = = = = = = = = =
*/
void S_LoadSound ( sfxHandle_t sfxHandle )
{
/*const*/ sfx_t * sfx ;
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
if ( sfxHandle < 0 | | sfxHandle > MAX_SFX | | s_sfxCodes [ sfxHandle ] = = INVALID_CODE ) {
return ;
}
sfx = & s_sfxBlock [ sfxHandle ] ;
if ( sfx - > iFlags & SFX_FLAG_UNLOADED ) {
S_StartLoadSound ( sfx ) ;
extern void S_DrainRawSoundData ( void ) ;
S_DrainRawSoundData ( ) ;
}
}
/*
= = = = = = = = = = = = = = = = = =
S_ClearSoundBuffer
If we are about to perform file access , clear the buffer
so sound doesn ' t stutter .
= = = = = = = = = = = = = = = = = =
*/
void S_ClearSoundBuffer ( void ) {
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
}
/*
= = = = = = = = = = = = = = = = = =
S_StopAllSounds
= = = = = = = = = = = = = = = = = =
*/
void S_StopSounds ( void )
{
int i ; //, j;
channel_t * ch ;
if ( ! s_soundStarted ) {
return ;
}
// stop looping sounds
S_ClearLoopingSounds ( ) ;
// clear all the s_channels
ch = s_channels ;
for ( i = 0 ; i < s_numChannels ; i + + , ch + + )
{
if ( ch - > bPlaying )
{
alSourceStop ( ch - > alSource ) ;
ch - > bPlaying = false ;
}
alSourcei ( ch - > alSource , AL_BUFFER , 0 ) ;
ch - > thesfx = NULL ;
ch - > bLooping = false ;
}
// clear out the lip synching override array
memset ( s_entityWavVol , 0 , sizeof ( int ) * MAX_GENTITIES ) ;
S_ClearSoundBuffer ( ) ;
}
/*
= = = = = = = = = = = = = = = = = =
S_StopAllSounds
and music
= = = = = = = = = = = = = = = = = =
*/
void S_StopAllSounds ( void ) {
if ( ! s_soundStarted ) {
return ;
}
// stop the background music
S_StopBackgroundTrack ( ) ;
S_StopSounds ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
continuous looping sounds are added each frame
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = = = = =
S_ClearLoopingSounds
= = = = = = = = = = = = = = = = = =
*/
void S_ClearLoopingSounds ( void )
{
if ( ! s_soundStarted ) {
return ;
}
int i ;
for ( i = 0 ; i < MAX_LOOP_SOUNDS ; i + + )
{
loopSounds [ i ] . bProcessed = false ;
loopSounds [ i ] . bMarked = false ;
loopSounds [ i ] . sfx = NULL ;
}
numLoopSounds = 0 ;
}
/*
= = = = = = = = = = = = = = = = = =
S_AddLoopingSound
Called during entity generation for a frame
= = = = = = = = = = = = = = = = = =
*/
void S_AddLoopingSound ( int entityNum , const vec3_t origin , const vec3_t velocity , sfxHandle_t sfxHandle , int chan ) {
/*const*/ sfx_t * sfx ;
if ( ! s_soundStarted | | s_soundMuted | | ! s_loopEnabled ) {
return ;
}
if ( numLoopSounds > = MAX_LOOP_SOUNDS ) {
return ;
}
if ( sfxHandle < 0 | | sfxHandle > MAX_SFX | | s_sfxCodes [ sfxHandle ] = = INVALID_CODE ) {
return ;
}
sfx = & s_sfxBlock [ sfxHandle ] ;
if ( sfx - > iFlags & SFX_FLAG_UNLOADED ) {
S_StartLoadSound ( sfx ) ;
}
SND_TouchSFX ( sfx ) ;
loopSounds [ numLoopSounds ] . origin [ 0 ] = origin [ 0 ] ;
loopSounds [ numLoopSounds ] . origin [ 1 ] = origin [ 1 ] ;
loopSounds [ numLoopSounds ] . origin [ 2 ] = origin [ 2 ] ;
loopSounds [ numLoopSounds ] . sfx = sfx ;
loopSounds [ numLoopSounds ] . volume = SOUND_MAXVOL ;
loopSounds [ numLoopSounds ] . entnum = entityNum ;
loopSounds [ numLoopSounds ] . entchannel = chan ;
numLoopSounds + + ;
}
/*
= = = = = = = = = = = = = = = = = =
S_AddAmbientLoopingSound
= = = = = = = = = = = = = = = = = =
*/
void S_AddAmbientLoopingSound ( const vec3_t origin , unsigned char volume , sfxHandle_t sfxHandle )
{
/*const*/ sfx_t * sfx ;
if ( ! s_soundStarted | | s_soundMuted | | ! s_loopEnabled ) {
return ;
}
if ( numLoopSounds > = MAX_LOOP_SOUNDS ) {
return ;
}
if ( volume = = 0 )
return ;
if ( sfxHandle < 0 | | sfxHandle > MAX_SFX | | s_sfxCodes [ sfxHandle ] = = INVALID_CODE ) {
return ;
}
sfx = & s_sfxBlock [ sfxHandle ] ;
if ( sfx - > iFlags & SFX_FLAG_UNLOADED ) {
S_StartLoadSound ( sfx ) ;
}
SND_TouchSFX ( sfx ) ;
loopSounds [ numLoopSounds ] . origin [ 0 ] = origin [ 0 ] ;
loopSounds [ numLoopSounds ] . origin [ 1 ] = origin [ 1 ] ;
loopSounds [ numLoopSounds ] . origin [ 2 ] = origin [ 2 ] ;
loopSounds [ numLoopSounds ] . sfx = sfx ;
loopSounds [ numLoopSounds ] . volume = volume ;
loopSounds [ numLoopSounds ] . entnum = - 1 ;
numLoopSounds + + ;
}
/*
= = = = = = = = = = = = = = = = = = = = =
S_UpdateEntityPosition
let the sound system know where an entity currently is
= = = = = = = = = = = = = = = = = = = = = =
*/
void S_UpdateEntityPosition ( int entityNum , const vec3_t origin )
{
if ( ! s_soundStarted ) {
return ;
}
channel_t * ch ;
int i ;
if ( entityNum < 0 | | entityNum > MAX_GENTITIES ) {
Com_Error ( ERR_DROP , " S_UpdateEntityPosition: bad entitynum %i " , entityNum ) ;
}
if ( entityNum = = 0 )
return ;
ch = s_channels ;
for ( i = 0 ; i < s_numChannels ; i + + , ch + + )
{
if ( ( ch - > bPlaying ) & &
( ch - > entnum = = entityNum ) & &
( ! ch - > b2D ) )
{
if ( ch - > origin [ 0 ] ! = origin [ 0 ] | |
ch - > origin [ 1 ] ! = origin [ 1 ] | |
ch - > origin [ 2 ] ! = origin [ 2 ] )
{
ch - > origin [ 0 ] = origin [ 0 ] ;
ch - > origin [ 1 ] = origin [ 1 ] ;
ch - > origin [ 2 ] = origin [ 2 ] ;
ch - > bOriginDirty = true ;
}
}
}
}
/*
= = = = = = = = = = = =
S_Respatialize
Change the volumes of all the playing sounds for changes in their positions
= = = = = = = = = = = =
*/
void S_Respatialize ( int entityNum , const vec3_t head , vec3_t axis [ 3 ] , qboolean inwater )
{
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
int index = 0 ;
#if 0
extern qboolean g_isMultiplayer ;
if ( g_isMultiplayer ) {
index = entityNum ;
}
# endif
if ( index > = s_numListeners ) {
return ;
}
listener_t * li = & s_listeners [ index ] ;
li - > entnum = entityNum ;
li - > pos [ 0 ] = head [ 0 ] ;
li - > pos [ 1 ] = head [ 1 ] ;
li - > pos [ 2 ] = head [ 2 ] ;
alListenerfv ( li - > handle , AL_POSITION , li - > pos ) ;
li - > orient [ 0 ] = axis [ 0 ] [ 0 ] ;
li - > orient [ 1 ] = axis [ 0 ] [ 1 ] ;
li - > orient [ 2 ] = axis [ 0 ] [ 2 ] ;
li - > orient [ 3 ] = axis [ 2 ] [ 0 ] ;
li - > orient [ 4 ] = axis [ 2 ] [ 1 ] ;
li - > orient [ 5 ] = axis [ 2 ] [ 2 ] ;
alListenerfv ( li - > handle , AL_ORIENTATION , li - > orient ) ;
}
/*
= = = = = = = = = = = =
S_Update
Called once each time through the main loop
= = = = = = = = = = = =
*/
void S_Update ( void ) {
if ( ! s_soundStarted ) {
return ;
}
// don't update too often
int now = Sys_Milliseconds ( ) ;
if ( now - s_updateTime < SOUND_UPDATE_TIME ) {
return ;
}
s_updateTime = now ;
if ( s_soundMuted ) {
alUpdate ( ) ;
return ;
}
// finish up any pending loads
S_UpdateLoading ( ) ;
// update the music stream
S_UpdateBackgroundTrack ( ) ;
// mix some sound
S_Update_ ( ) ;
alUpdate ( ) ;
}
static void UpdatePosition ( channel_t * ch )
{
if ( ! ch - > b2D )
{
if ( ch - > bLooping & & ch - > bPlaying )
{
loopSound_t * loop = & loopSounds [ ch - > loopChannel ] ;
if ( loop - > origin [ 0 ] ! = ch - > origin [ 0 ] | |
loop - > origin [ 1 ] ! = ch - > origin [ 1 ] | |
loop - > origin [ 2 ] ! = ch - > origin [ 2 ] )
{
ch - > origin [ 0 ] = loop - > origin [ 0 ] ;
ch - > origin [ 1 ] = loop - > origin [ 1 ] ;
ch - > origin [ 2 ] = loop - > origin [ 2 ] ;
ch - > bOriginDirty = true ;
}
}
if ( ch - > bOriginDirty )
{
alSourcefv ( ch - > alSource , AL_POSITION , ch - > origin ) ;
ch - > bOriginDirty = false ;
}
}
}
static void UpdateGain ( channel_t * ch )
{
float v = 0.f ;
if ( ch - > bLooping & & ch - > bPlaying )
{
loopSound_t * loop = & loopSounds [ ch - > loopChannel ] ;
if ( loop - > volume ! = ch - > master_vol )
{
ch - > master_vol = loop - > volume ;
}
}
if ( ch - > entchannel = = CHAN_ANNOUNCER | |
ch - > entchannel = = CHAN_VOICE | |
ch - > entchannel = = CHAN_VOICE_ATTEN | |
ch - > entchannel = = CHAN_VOICE_GLOBAL )
{
v = ( ( float ) ( ch - > master_vol ) * s_voice_volume - > value ) / 255.0f ;
}
else if ( ch - > entchannel = = CHAN_MUSIC )
{
v = ( ( float ) ( ch - > master_vol ) * s_music_volume - > value ) / 255.f ;
}
else
{
v = ( ( float ) ( ch - > master_vol ) * s_effects_volume - > value ) / 255.f ;
}
if ( ch - > fLastVolume ! = v )
{
alSourcef ( ch - > alSource , AL_GAIN , v ) ;
ch - > fLastVolume = v ;
}
}
static void UpdatePlayState ( channel_t * ch )
{
if ( ! ch - > bPlaying ) return ;
if ( ch - > bLooping )
{
// Looping sound
loopSound_t * loop = & loopSounds [ ch - > loopChannel ] ;
if ( loop - > bProcessed = = false & & loop - > sfx ! = NULL & &
( loop - > sfx = = ch - > thesfx | |
( loop - > sfx - > iFlags & SFX_FLAG_DEFAULT ) ) )
{
// Playing
loop - > bProcessed = true ;
}
else
{
// Sound no longer needed
alSourceStop ( ch - > alSource ) ;
alSourcei ( ch - > alSource , AL_BUFFER , 0 ) ;
ch - > thesfx = NULL ;
ch - > bPlaying = false ;
}
}
else
{
// Single shot sound
ALint state ;
alGetSourcei ( ch - > alSource , AL_SOURCE_STATE , & state ) ;
if ( state = = AL_STOPPED )
{
alSourcei ( ch - > alSource , AL_BUFFER , 0 ) ;
ch - > thesfx = NULL ;
ch - > bPlaying = false ;
}
}
}
static void UpdateAttenuation ( channel_t * ch )
{
if ( ! ch - > b2D )
{
/*
if ( ch - > entchannel = = CHAN_VOICE | | ch - > entchannel = = CHAN_VOICE_ATTEN | | ch - > entchannel = = CHAN_VOICE_GLOBAL )
{
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , 1500.0f ) ;
}
else
{
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , 400.0f ) ;
}
*/
#if 0
extern qboolean g_isMultiplayer ;
if ( ! g_isMultiplayer )
{
# endif
switch ( ch - > entchannel )
{
case CHAN_VOICE :
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE * 3.f ) ;
break ;
case CHAN_LESS_ATTEN :
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE * 8.f ) ;
break ;
case CHAN_VOICE_ATTEN :
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE * 1.35f ) ;
break ;
case CHAN_VOICE_GLOBAL :
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE * 100.f ) ;
break ;
default :
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE ) ;
break ;
}
#if 0
}
else
{
alSourcef ( ch - > alSource , AL_REFERENCE_DISTANCE , SOUND_REF_DIST_BASE * 2.f ) ;
}
# endif
}
}
static void PlaySingleShot ( channel_t * ch )
{
alSourcei ( ch - > alSource , AL_LOOPING , AL_FALSE ) ;
UpdateAttenuation ( ch ) ;
UpdatePosition ( ch ) ;
// Attach buffer to source
alSourcei ( ch - > alSource , AL_BUFFER , ch - > thesfx - > Buffer ) ;
// Clear error state, and check for successful Play call
alGetError ( ) ;
alSourcePlay ( ch - > alSource ) ;
if ( alGetError ( ) = = AL_NO_ERROR )
{
ch - > bPlaying = true ;
ch - > iLastPlayTime = Sys_Milliseconds ( ) ;
}
}
void UpdateLoopingSounds ( )
{
// Look for non-processed loops that are ready to play
for ( int j = 0 ; j < numLoopSounds ; j + + )
{
loopSound_t * loop = & loopSounds [ j ] ;
{
// merge all loops with the same sfx into a single loop
float num = 1 ;
for ( int k = j + 1 ; k < numLoopSounds ; + + k )
{
if ( loopSounds [ k ] . sfx = = loop - > sfx )
{
loop - > origin [ 0 ] + = loopSounds [ k ] . origin [ 0 ] ;
loop - > origin [ 1 ] + = loopSounds [ k ] . origin [ 1 ] ;
loop - > origin [ 2 ] + = loopSounds [ k ] . origin [ 2 ] ;
loop - > volume + = loopSounds [ k ] . volume ;
loopSounds [ k ] . bProcessed = true ;
num + = 1 ;
}
}
loop - > origin [ 0 ] / = num ;
loop - > origin [ 1 ] / = num ;
loop - > origin [ 2 ] / = num ;
loop - > volume / = ( int ) num ;
}
if ( loop - > bProcessed = = false & & ( loop - > sfx - > iFlags & SFX_FLAG_RESIDENT ) )
{
// play the loop
bool is2D = false ;
for ( int i = 0 ; i < s_numListeners ; + + i )
{
if ( loop - > entnum = = s_listeners [ i ] . entnum | |
( loop - > origin [ 0 ] = = s_listeners [ i ] . pos [ 0 ] & &
loop - > origin [ 1 ] = = s_listeners [ i ] . pos [ 1 ] & &
loop - > origin [ 2 ] = = s_listeners [ i ] . pos [ 2 ] ) )
{
is2D = true ;
break ;
}
}
channel_t * ch = S_PickChannel ( 0 , 0 , is2D , NULL ) ;
if ( ! ch ) continue ;
ch - > master_vol = loop - > volume ;
ch - > fLastVolume = - 1 ;
ch - > entnum = loop - > entnum ;
ch - > entchannel = loop - > entchannel ;
ch - > thesfx = loop - > sfx ;
ch - > loopChannel = j ;
ch - > bLooping = true ;
ch - > origin [ 0 ] = loop - > origin [ 0 ] ;
ch - > origin [ 1 ] = loop - > origin [ 1 ] ;
ch - > origin [ 2 ] = loop - > origin [ 2 ] ;
ch - > bOriginDirty = true ;
alSourcei ( ch - > alSource , AL_LOOPING , AL_TRUE ) ;
alSourcei ( ch - > alSource , AL_BUFFER , ch - > thesfx - > Buffer ) ;
UpdateAttenuation ( ch ) ;
UpdatePosition ( ch ) ;
UpdateGain ( ch ) ;
alGetError ( ) ;
alSourcePlay ( ch - > alSource ) ;
if ( alGetError ( ) = = AL_NO_ERROR )
{
ch - > bPlaying = true ;
ch - > iLastPlayTime = Sys_Milliseconds ( ) ;
}
}
}
}
static void SyncChannelLoops ( void )
{
channel_t * ch ;
int i , j ;
// Try to match up channels with looping sounds
// (The order of sounds in loopSounds can change
// frame to frame.)
ch = s_channels ;
for ( i = 0 ; i < s_numChannels ; i + + , ch + + )
{
if ( ch - > bPlaying & & ch - > bLooping )
{
for ( j = 0 ; j < numLoopSounds ; + + j )
{
if ( ch - > thesfx = = loopSounds [ j ] . sfx & &
! loopSounds [ j ] . bMarked )
{
ch - > loopChannel = j ;
loopSounds [ j ] . bMarked = true ;
break ;
}
}
}
}
}
void S_Update_ ( void )
{
channel_t * ch ;
int i ;
if ( ! s_soundStarted | | s_soundMuted ) {
return ;
}
memset ( s_entityWavVol , 0 , sizeof ( int ) * MAX_GENTITIES ) ;
SyncChannelLoops ( ) ;
ch = s_channels ;
for ( i = 0 ; i < s_numChannels ; i + + , ch + + )
{
if ( ! ch - > thesfx ) continue ;
if ( ch - > thesfx - > iFlags & SFX_FLAG_UNLOADED )
{
// if the sound is not going to be loaded, force the
// playing flag high, stop the source, and hope that
// the update code cleans it up...
ch - > bPlaying = true ;
alSourceStop ( ch - > alSource ) ;
continue ;
}
if ( ch - > entchannel = = CHAN_VOICE | |
ch - > entchannel = = CHAN_VOICE_ATTEN | |
ch - > entchannel = = CHAN_VOICE_GLOBAL )
{
s_entityWavVol [ ch - > entnum ] = ch - > bPlaying ? 4 : - 1 ;
}
if ( ! ( ch - > thesfx - > iFlags & SFX_FLAG_RESIDENT ) ) continue ;
UpdatePosition ( ch ) ;
UpdateGain ( ch ) ;
if ( ch - > bPlaying )
{
UpdatePlayState ( ch ) ;
}
else
{
PlaySingleShot ( ch ) ;
}
}
UpdateLoopingSounds ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
console functions
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
static void S_Play_f ( void ) {
int i ;
sfxHandle_t h ;
char name [ 256 ] ;
i = 1 ;
while ( i < Cmd_Argc ( ) ) {
if ( ! strrchr ( Cmd_Argv ( i ) , ' . ' ) ) {
Com_sprintf ( name , sizeof ( name ) , " %s.wav " , Cmd_Argv ( 1 ) ) ;
} else {
Q_strncpyz ( name , Cmd_Argv ( i ) , sizeof ( name ) ) ;
}
h = S_RegisterSound ( name ) ;
if ( h ) {
S_StartLocalSound ( h , CHAN_LOCAL_SOUND ) ;
}
i + + ;
}
}
/*
* Crazy expanded play function :
* playex < file name > xOffset yOffset zOffset channel
*/
# ifndef _JK2MP
static void S_PlayEx_f ( void ) {
sfxHandle_t h ;
char name [ 256 ] = { 0 } ;
vec3_t origin ;
int entchannel ;
if ( Cmd_Argc ( ) < 6 )
return ;
Q_strncpyz ( name , Cmd_Argv ( 1 ) , sizeof ( name ) ) ;
h = S_RegisterSound ( name ) ;
if ( ! h )
return ;
extern void G_EntityPosition ( int i , vec3_t ret ) ;
G_EntityPosition ( 0 , origin ) ;
origin [ 0 ] + = atof ( Cmd_Argv ( 2 ) ) ;
origin [ 1 ] + = atof ( Cmd_Argv ( 3 ) ) ;
origin [ 2 ] + = atof ( Cmd_Argv ( 4 ) ) ;
entchannel = atoi ( Cmd_Argv ( 5 ) ) ;
S_StartSound ( origin , 0 , entchannel , h ) ;
}
# endif
static void S_Music_f ( void ) {
int c ;
c = Cmd_Argc ( ) ;
if ( c = = 2 ) {
S_StartBackgroundTrack ( Cmd_Argv ( 1 ) , Cmd_Argv ( 1 ) , qfalse ) ;
} else if ( c = = 3 ) {
S_StartBackgroundTrack ( Cmd_Argv ( 1 ) , Cmd_Argv ( 2 ) , qfalse ) ;
} else {
Com_Printf ( " music <musicfile> [loopfile] \n " ) ;
return ;
}
}
void S_SoundList_f ( void ) {
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
background music functions
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
// fixme: need to move this into qcommon sometime?, but too much stuff altered by other people and I won't be able
// to compile again for ages if I check that out...
//
// DO NOT replace this with a call to FS_FileExists, that's for checking about writing out, and doesn't work for this.
//
qboolean S_MusicFileExists ( const char * psFilename )
{
fileHandle_t fhTemp ;
char * pLoadName = S_FixMusicFileName ( psFilename ) ;
FS_FOpenFileRead ( pLoadName , & fhTemp , qtrue ) ; // qtrue so I can fclose the handle without closing a PAK
if ( ! fhTemp )
return qfalse ;
FS_FCloseFile ( fhTemp ) ;
return qtrue ;
}
static void S_StopBackgroundTrack_Actual ( MusicInfo_t * pMusicInfo )
{
pMusicInfo - > bLoaded = false ;
pMusicInfo - > Rewind ( ) ;
alStreamStop ( ) ;
}
static void FreeMusic ( MusicInfo_t * pMusicInfo )
{
pMusicInfo - > sLoadedDataName [ 0 ] = ' \0 ' ;
}
// called only by snd_shutdown (from snd_restart or app exit)
//
static void S_UnCacheDynamicMusic ( void )
{
for ( int i = eBGRNDTRACK_DATABEGIN ; i ! = eBGRNDTRACK_DATAEND ; i + + )
{
FreeMusic ( & tMusic_Info [ i ] ) ;
}
}
static qboolean S_StartBackgroundTrack_Actual ( MusicInfo_t * pMusicInfo , qboolean qbDynamic , const char * intro , const char * loop )
{
Q_strncpyz ( sMusic_BackgroundLoop , loop , sizeof ( sMusic_BackgroundLoop ) ) ;
char * name = S_FixMusicFileName ( intro ) ;
if ( ! intro [ 0 ] ) {
S_StopBackgroundTrack_Actual ( pMusicInfo ) ;
return qfalse ;
}
// new bit, if file requested is not same any loaded one (if prev was in-mem), ditch it...
//
if ( Q_stricmp ( name , pMusicInfo - > sLoadedDataName ) )
{
FreeMusic ( pMusicInfo ) ;
}
//
// open up a wav file and get all the info
//
fileHandle_t handle ;
int len = FS_FOpenFileRead ( name , & handle , qtrue ) ;
if ( ! handle ) {
Com_Printf ( S_COLOR_YELLOW " WARNING: couldn't open music file %s \n " , name ) ;
S_StopBackgroundTrack_Actual ( pMusicInfo ) ;
return qfalse ;
}
# if defined(_XBOX) || defined(_WINDOWS)
// read enough of the file to get the header...
byte buffer [ 128 ] ;
FS_Read ( buffer , sizeof ( buffer ) , handle ) ;
FS_FCloseFile ( handle ) ;
wavinfo_t info = GetWavInfo ( buffer ) ;
if ( info . size = = 0 ) {
Com_Printf ( S_COLOR_YELLOW " WARNING: Invalid format in %s \n " , name ) ;
S_StopBackgroundTrack_Actual ( pMusicInfo ) ;
return qfalse ;
}
pMusicInfo - > s_backgroundSize = info . size ;
pMusicInfo - > s_backgroundBPS = info . rate * info . width / 8 ;
if ( info . format = = AL_FORMAT_STEREO4 )
{
pMusicInfo - > s_backgroundBPS < < = 1 ;
}
# elif defined(_GAMECUBE)
FS_FCloseFile ( handle ) ;
pMusicInfo - > s_backgroundSize = len ;
pMusicInfo - > s_backgroundBPS = 48000 * 4 / 8 * 2 ;
# endif
Q_strncpyz ( pMusicInfo - > sLoadedDataName , intro , sizeof ( pMusicInfo - > sLoadedDataName ) ) ;
pMusicInfo - > iFileCode = Sys_GetFileCode ( name ) ;
pMusicInfo - > bLoaded = true ;
return qtrue ;
}
static void S_SwitchDynamicTracks ( MusicState_e eOldState , MusicState_e eNewState , qboolean bNewTrackStartsFullVolume )
{
// copy old track into fader...
//
tMusic_Info [ eBGRNDTRACK_FADE ] = tMusic_Info [ eOldState ] ;
tMusic_Info [ eBGRNDTRACK_FADE ] . iXFadeVolumeSeekTime = Sys_Milliseconds ( ) ;
tMusic_Info [ eBGRNDTRACK_FADE ] . iXFadeVolumeSeekTo = 0 ;
//
// ... and deactivate...
//
tMusic_Info [ eOldState ] . bActive = qfalse ;
//
// set new track to either full volume or fade up...
//
tMusic_Info [ eNewState ] . bActive = qtrue ;
tMusic_Info [ eNewState ] . iXFadeVolumeSeekTime = Sys_Milliseconds ( ) ;
tMusic_Info [ eNewState ] . iXFadeVolumeSeekTo = 255 ;
tMusic_Info [ eNewState ] . iXFadeVolume = bNewTrackStartsFullVolume ? 255 : 0 ;
eMusic_StateActual = eNewState ;
// sanity check
if ( tMusic_Info [ eNewState ] . iFileSeekTo > = tMusic_Info [ eNewState ] . s_backgroundSize )
{
tMusic_Info [ eNewState ] . iFileSeekTo = 0 ;
}
}
// called by both the config-string parser and the console-command state-changer...
//
// This either changes the music right now (copying track structures etc), or leaves the new state as pending
// so it gets picked up by the general music player if in a transition that can't be overridden...
//
static void S_SetDynamicMusicState ( MusicState_e eNewState )
{
if ( eMusic_StateRequest ! = eNewState )
{
eMusic_StateRequest = eNewState ;
if ( s_debugdynamic - > integer )
{
LPCSTR psNewStateString = Music_BaseStateToString ( eNewState , qtrue ) ;
psNewStateString = psNewStateString ? psNewStateString : " <unknown> " ;
Com_Printf ( S_COLOR_MAGENTA " S_SetDynamicMusicState( Request: \" %s \" ) \n " , psNewStateString ) ;
}
}
}
static void S_HandleDynamicMusicStateChange ( void )
{
if ( eMusic_StateRequest ! = eMusic_StateActual )
{
// check whether or not the new request can be honoured, given what's currently playing...
//
if ( Music_StateCanBeInterrupted ( eMusic_StateActual , eMusic_StateRequest ) )
{
switch ( eMusic_StateRequest )
{
case eBGRNDTRACK_EXPLORE : // ... from action or silence
{
switch ( eMusic_StateActual )
{
case eBGRNDTRACK_ACTION : // action->explore
{
// find the transition track to play, and the entry point for explore when we get there,
// and also see if we're at a permitted exit point to switch at all...
//
float fPlayingTimeElapsed = tMusic_Info [ eMusic_StateActual ] . ElapsedTime ( ) ;
// supply:
//
// playing point in float seconds
// enum of track being queried
//
// get:
//
// enum of transition track to switch to
// float time of entry point of new track *after* transition
MusicState_e eTransition ;
float fNewTrackEntryTime = 0.0f ;
if ( Music_AllowedToTransition ( fPlayingTimeElapsed , eBGRNDTRACK_ACTION , & eTransition , & fNewTrackEntryTime ) )
{
tMusic_Info [ eTransition ] . Rewind ( ) ;
tMusic_Info [ eTransition ] . bTrackSwitchPending = qtrue ;
tMusic_Info [ eTransition ] . bLooping = qfalse ;
tMusic_Info [ eTransition ] . eTS_NewState = eMusic_StateRequest ;
tMusic_Info [ eTransition ] . fTS_NewTime = fNewTrackEntryTime ;
S_SwitchDynamicTracks ( eMusic_StateActual , eTransition , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
}
break ;
case eBGRNDTRACK_SILENCE : // silence->explore
{
tMusic_Info [ eMusic_StateRequest ] . Rewind ( ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eMusic_StateRequest , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
default : // trying to transition from some state I wasn't aware you could transition from (shouldn't happen), so ignore
{
assert ( 0 ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eBGRNDTRACK_SILENCE , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
}
}
break ;
case eBGRNDTRACK_SILENCE : // from explore or action
{
switch ( eMusic_StateActual )
{
case eBGRNDTRACK_ACTION : // action->silence
case eBGRNDTRACK_EXPLORE : // explore->silence
{
// find the transition track to play, and the entry point for explore when we get there,
// and also see if we're at a permitted exit point to switch at all...
//
float fPlayingTimeElapsed = tMusic_Info [ eMusic_StateActual ] . ElapsedTime ( ) ;
MusicState_e eTransition ;
float fNewTrackEntryTime = 0.0f ;
if ( Music_AllowedToTransition ( fPlayingTimeElapsed , eMusic_StateActual , & eTransition , & fNewTrackEntryTime ) )
{
tMusic_Info [ eTransition ] . Rewind ( ) ;
tMusic_Info [ eTransition ] . bTrackSwitchPending = qtrue ;
tMusic_Info [ eTransition ] . bLooping = qfalse ;
tMusic_Info [ eTransition ] . eTS_NewState = eMusic_StateRequest ;
tMusic_Info [ eTransition ] . fTS_NewTime = 0.0f ; //fNewTrackEntryTime; irrelevant when switching to silence
S_SwitchDynamicTracks ( eMusic_StateActual , eTransition , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
}
break ;
default : // some unhandled type switching to silence
assert ( 0 ) ; // fall through since boss case just does silence->switch anyway
case eBGRNDTRACK_BOSS : // boss->silence
{
tMusic_Info [ eBGRNDTRACK_SILENCE ] . Rewind ( ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eBGRNDTRACK_SILENCE , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
}
}
break ;
case eBGRNDTRACK_ACTION : // anything->action
{
switch ( eMusic_StateActual )
{
case eBGRNDTRACK_SILENCE : // silence->action
{
tMusic_Info [ eMusic_StateRequest ] . Rewind ( ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eMusic_StateRequest , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
default : // !silence->action
{
float fEntryTime = Music_GetRandomEntryTime ( eMusic_StateRequest ) ;
tMusic_Info [ eMusic_StateRequest ] . SeekTo ( fEntryTime ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eMusic_StateRequest , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
}
}
break ;
case eBGRNDTRACK_BOSS :
{
tMusic_Info [ eMusic_StateRequest ] . Rewind ( ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eMusic_StateRequest , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
case eBGRNDTRACK_DEATH :
{
tMusic_Info [ eMusic_StateRequest ] . Rewind ( ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , eMusic_StateRequest , qtrue ) ; // qboolean bNewTrackStartsFullVolume
}
break ;
default : assert ( 0 ) ; break ; // unknown new mode request, so just ignore it
}
}
}
}
static char gsIntroMusic [ MAX_QPATH ] = { 0 } ;
static char gsLoopMusic [ MAX_QPATH ] = { 0 } ;
void S_RestartMusic ( void )
{
if ( s_soundStarted & & ! s_soundMuted )
{
S_StartBackgroundTrack ( gsIntroMusic , gsLoopMusic , qfalse ) ; // ( default music start will set the state to EXPLORE )
S_SetDynamicMusicState ( eMusic_StateRequest ) ; // restore to prev state
}
}
// Basic logic here is to see if the intro file specified actually exists, and if so, then it's not dynamic music,
// When called by the cgame start it loads up, then stops the playback (because of stutter issues), so that when the
// actual snapshot is received and the real play request is processed the data has already been loaded so will be quicker.
//
void S_StartBackgroundTrack ( const char * intro , const char * loop , qboolean bCalledByCGameStart )
{
bMusic_IsDynamic = qfalse ;
if ( ! s_soundStarted )
{ //we have no sound, so don't even bother trying
return ;
}
if ( ! intro ) {
intro = " " ;
}
if ( ! loop | | ! loop [ 0 ] ) {
loop = intro ;
}
Q_strncpyz ( gsIntroMusic , intro , sizeof ( gsIntroMusic ) ) ;
Q_strncpyz ( gsLoopMusic , loop , sizeof ( gsLoopMusic ) ) ;
char sName [ MAX_QPATH ] ;
Q_strncpyz ( sName , intro , sizeof ( sName ) ) ;
COM_DefaultExtension ( sName , sizeof ( sName ) , " .wxb " ) ;
// if dynamic music not allowed, then just stream the explore music instead of playing dynamic...
//
if ( ! s_allowDynamicMusic - > integer & & Music_DynamicDataAvailable ( intro ) ) // "intro", NOT "sName" (i.e. don't use version with ".wxb" extension)
{
LPCSTR psMusicName = Music_GetFileNameForState ( eBGRNDTRACK_DATABEGIN ) ;
if ( psMusicName & & S_MusicFileExists ( psMusicName ) )
{
Q_strncpyz ( sName , psMusicName , sizeof ( sName ) ) ;
}
}
// conceptually we always play the 'intro'[/sName] track, intro-to-loop transition is handled in UpdateBackGroundTrack().
//
if ( ( strstr ( sName , " / " ) & & S_MusicFileExists ( sName ) ) ) // strstr() check avoids extra file-exists check at runtime if reverting from streamed music to dynamic since literal files all need at least one slash in their name (eg "music/blah")
{
Com_DPrintf ( " S_StartBackgroundTrack: Found/using non-dynamic music track '%s' \n " , sName ) ;
tMusic_Info [ eBGRNDTRACK_NONDYNAMIC ] . bLooping = qtrue ;
S_StartBackgroundTrack_Actual ( & tMusic_Info [ eBGRNDTRACK_NONDYNAMIC ] , bMusic_IsDynamic , sName , sName ) ;
}
else
{
if ( Music_DynamicDataAvailable ( intro ) ) // "intro", NOT "sName" (i.e. don't use version with ".wxb" extension)
{
int i ;
extern const char * Music_GetLevelSetName ( void ) ;
Q_strncpyz ( sInfoOnly_CurrentDynamicMusicSet , Music_GetLevelSetName ( ) , sizeof ( sInfoOnly_CurrentDynamicMusicSet ) ) ;
for ( i = eBGRNDTRACK_DATABEGIN ; i ! = eBGRNDTRACK_DATAEND ; i + + )
{
qboolean bOk = qfalse ;
LPCSTR psMusicName = Music_GetFileNameForState ( ( MusicState_e ) i ) ;
if ( psMusicName & & ( ! Q_stricmp ( tMusic_Info [ i ] . sLoadedDataName , psMusicName ) | | S_MusicFileExists ( psMusicName ) ) )
{
bOk = S_StartBackgroundTrack_Actual ( & tMusic_Info [ i ] , qtrue , psMusicName , loop ) ;
}
tMusic_Info [ i ] . bExists = bOk ;
if ( ! tMusic_Info [ i ] . bExists )
{
FreeMusic ( & tMusic_Info [ i ] ) ;
}
}
//
// default all tracks to OFF first (and set any other vars)
//
for ( i = 0 ; i < eBGRNDTRACK_NUMBEROF ; i + + )
{
tMusic_Info [ i ] . bActive = qfalse ;
tMusic_Info [ i ] . bTrackSwitchPending = qfalse ;
tMusic_Info [ i ] . bLooping = qtrue ;
tMusic_Info [ i ] . fSmoothedOutVolume = 0.25f ;
}
tMusic_Info [ eBGRNDTRACK_DEATH ] . bLooping = qfalse ;
if ( tMusic_Info [ eBGRNDTRACK_EXPLORE ] . bExists & &
tMusic_Info [ eBGRNDTRACK_ACTION ] . bExists
)
{
Com_DPrintf ( " S_StartBackgroundTrack: Found dynamic music tracks \n " ) ;
bMusic_IsDynamic = qtrue ;
//
// ... then start the default music state...
//
eMusic_StateActual = eMusic_StateRequest = eBGRNDTRACK_EXPLORE ;
MusicInfo_t * pMusicInfo = & tMusic_Info [ eMusic_StateActual ] ;
pMusicInfo - > bActive = qtrue ;
pMusicInfo - > iXFadeVolumeSeekTime = Sys_Milliseconds ( ) ;
pMusicInfo - > iXFadeVolumeSeekTo = 255 ;
pMusicInfo - > iXFadeVolume = 0 ;
}
else
{
Com_Printf ( S_COLOR_RED " Dynamic music did not have both 'action' and 'explore' versions, inhibiting... \n " ) ;
S_StopBackgroundTrack ( ) ;
}
}
else
{
if ( sName [ 0 ] ! = ' . ' ) // blank name with ".wxb" or whatever attached - no error print out
{
Com_Printf ( S_COLOR_RED " Unable to find music \" %s \" as explicit track or dynamic music entry! \n " , sName ) ;
S_StopBackgroundTrack ( ) ;
}
}
}
if ( bCalledByCGameStart )
{
S_StopBackgroundTrack ( ) ;
}
}
void S_StopBackgroundTrack ( void )
{
for ( int i = 0 ; i < eBGRNDTRACK_NUMBEROF ; i + + )
{
S_StopBackgroundTrack_Actual ( & tMusic_Info [ i ] ) ;
}
}
// qboolean return is true only if we're changing from a streamed intro to a dynamic loop...
//
static qboolean S_UpdateBackgroundTrack_Actual ( MusicInfo_t * pMusicInfo , qboolean bFirstOrOnlyMusicTrack , float fDefaultVolume )
{
float fMasterVol = fDefaultVolume ; // s_musicVolume->value;
if ( bMusic_IsDynamic )
{
// step xfade volume...
//
if ( pMusicInfo - > iXFadeVolume ! = pMusicInfo - > iXFadeVolumeSeekTo )
{
int iFadeMillisecondsElapsed = Sys_Milliseconds ( ) - pMusicInfo - > iXFadeVolumeSeekTime ;
if ( iFadeMillisecondsElapsed > ( fDYNAMIC_XFADE_SECONDS * 1000 ) )
{
pMusicInfo - > iXFadeVolume = pMusicInfo - > iXFadeVolumeSeekTo ;
}
else
{
pMusicInfo - > iXFadeVolume = ( int ) ( 255.0f * ( ( float ) iFadeMillisecondsElapsed / ( fDYNAMIC_XFADE_SECONDS * 1000.0f ) ) ) ;
if ( pMusicInfo - > iXFadeVolumeSeekTo = = 0 ) // bleurgh
pMusicInfo - > iXFadeVolume = 255 - pMusicInfo - > iXFadeVolume ;
}
}
fMasterVol * = ( float ) ( ( float ) pMusicInfo - > iXFadeVolume / 255.0f ) ;
}
if ( pMusicInfo - > bLoaded = = false ) {
return qfalse ;
}
pMusicInfo - > fSmoothedOutVolume = ( pMusicInfo - > fSmoothedOutVolume + fMasterVol ) / 2.0f ;
alStreamf ( AL_GAIN , pMusicInfo - > fSmoothedOutVolume ) ;
// don't bother playing anything if musicvolume is 0
if ( pMusicInfo - > fSmoothedOutVolume < = 0 ) {
return qfalse ;
}
// start playing if necessary
if ( pMusicInfo - > bLooping )
{
ALint state ;
alGetStreami ( AL_SOURCE_STATE , & state ) ;
if ( state ! = AL_PLAYING )
{
alStreamPlay ( pMusicInfo - > iFileSeekTo ,
pMusicInfo - > iFileCode ,
pMusicInfo - > bLooping ) ;
}
}
if ( pMusicInfo - > PlayTime ( ) > = pMusicInfo - > TotalTime ( ) )
{
// loop the music, or play the next piece if we were on the intro...
// (but not for dynamic, that can only be used for loop music)
//
if ( bMusic_IsDynamic ) // needs special logic for this, different call
{
pMusicInfo - > Rewind ( ) ;
}
else
{
// for non-dynamic music we need to check if "sMusic_BackgroundLoop" is an actual filename,
// or if it's a dynamic music specifier (which can't literally exist), in which case it should set
// a return flag then exit...
//
char sTestName [ MAX_QPATH * 2 ] ; // *2 so COM_DefaultExtension doesn't do an ERR_DROP if there was no space
// for an extension, since this is a "soft" test
Q_strncpyz ( sTestName , sMusic_BackgroundLoop , sizeof ( sTestName ) ) ;
COM_DefaultExtension ( sTestName , sizeof ( sTestName ) , " .mp3 " ) ;
if ( S_MusicFileExists ( sTestName ) )
{
// Restart the music
alStreamStop ( ) ;
alStreamPlay ( pMusicInfo - > iFileSeekTo ,
pMusicInfo - > iFileCode ,
pMusicInfo - > bLooping ) ;
}
else
{
// proposed file doesn't exist, but this may be a dynamic track we're wanting to loop,
// so exit with a special flag...
//
return qtrue ;
}
}
}
return qfalse ;
}
// used to be just for dynamic, but now even non-dynamic music has to know whether it should be silent or not...
//
static LPCSTR S_Music_GetRequestedState ( void )
{
// This doesn't do anything in MP - just return NULL
# ifndef _JK2MP
int iStringOffset = cl . gameState . stringOffsets [ CS_DYNAMIC_MUSIC_STATE ] ;
if ( iStringOffset )
{
LPCSTR psCommand = cl . gameState . stringData + iStringOffset ;
return psCommand ;
}
# endif
return NULL ;
}
// scan the configstring to see if there's been a state-change requested...
// (note that even if the state doesn't change it still gets here, so do a same-state check for applying)
//
// then go on to do transition handling etc...
//
static void S_CheckDynamicMusicState ( void )
{
LPCSTR psCommand = S_Music_GetRequestedState ( ) ;
if ( psCommand )
{
MusicState_e eNewState ;
if ( ! Q_stricmpn ( psCommand , " silence " , 7 ) )
{
eNewState = eBGRNDTRACK_SILENCE ;
}
else if ( ! Q_stricmpn ( psCommand , " action " , 6 ) )
{
eNewState = eBGRNDTRACK_ACTION ;
}
else if ( ! Q_stricmpn ( psCommand , " boss " , 4 ) )
{
// special case, boss music is optional and may not be defined...
//
if ( tMusic_Info [ eBGRNDTRACK_BOSS ] . bExists )
{
eNewState = eBGRNDTRACK_BOSS ;
}
else
{
// ( leave it playing current track )
//
eNewState = eMusic_StateActual ;
}
}
else if ( ! Q_stricmpn ( psCommand , " death " , 5 ) )
{
// special case, death music is optional and may not be defined...
//
if ( tMusic_Info [ eBGRNDTRACK_DEATH ] . bExists )
{
eNewState = eBGRNDTRACK_DEATH ;
}
else
{
// ( leave it playing current track, typically either boss or action )
//
eNewState = eMusic_StateActual ;
}
}
else
{
// seems a reasonable default...
//
eNewState = eBGRNDTRACK_EXPLORE ;
}
S_SetDynamicMusicState ( eNewState ) ;
}
S_HandleDynamicMusicStateChange ( ) ;
}
static void S_UpdateBackgroundTrack ( void )
{
if ( bMusic_IsDynamic )
{
S_CheckDynamicMusicState ( ) ;
if ( eMusic_StateActual ! = eBGRNDTRACK_SILENCE )
{
MusicInfo_t * pMusicInfoCurrent = & tMusic_Info [ ( eMusic_StateActual = = eBGRNDTRACK_FADE ) ? eBGRNDTRACK_EXPLORE : eMusic_StateActual ] ;
MusicInfo_t * pMusicInfoFadeOut = & tMusic_Info [ eBGRNDTRACK_FADE ] ;
if ( pMusicInfoCurrent - > bLoaded )
{
float fRemainingTimeInSeconds = 1000000 ;
if ( pMusicInfoFadeOut - > bActive )
{
S_UpdateBackgroundTrack_Actual ( pMusicInfoFadeOut , qfalse , s_music_volume - > value ) ; // inactive-checked internally
//
// only do this for the fader!...
//
if ( pMusicInfoFadeOut - > iXFadeVolume = = 0 )
{
pMusicInfoFadeOut - > bActive = qfalse ;
// play if we have a file
if ( pMusicInfoCurrent - > iFileCode )
{
alStreamPlay ( pMusicInfoCurrent - > iFileSeekTo ,
pMusicInfoCurrent - > iFileCode ,
pMusicInfoCurrent - > bLooping ) ;
pMusicInfoCurrent - > iXFadeVolumeSeekTime = Sys_Milliseconds ( ) ;
}
else
{
alStreamStop ( ) ;
}
}
}
else
{
S_UpdateBackgroundTrack_Actual ( pMusicInfoCurrent , qtrue , s_music_volume - > value ) ;
fRemainingTimeInSeconds = pMusicInfoCurrent - > TotalTime ( ) - pMusicInfoCurrent - > ElapsedTime ( ) ;
}
if ( fRemainingTimeInSeconds < fDYNAMIC_XFADE_SECONDS * 2 )
{
// now either loop current track, switch if finishing a transition, or stop if finished a death...
//
if ( pMusicInfoCurrent - > bTrackSwitchPending )
{
pMusicInfoCurrent - > bTrackSwitchPending = qfalse ; // ack
tMusic_Info [ pMusicInfoCurrent - > eTS_NewState ] . SeekTo ( pMusicInfoCurrent - > fTS_NewTime ) ;
S_SwitchDynamicTracks ( eMusic_StateActual , pMusicInfoCurrent - > eTS_NewState , qfalse ) ; // qboolean bNewTrackStartsFullVolume
}
else
{
// normal looping, so set rewind current track, set volume to 0 and fade up to full (unless death track playing, then stays quiet)
// (while fader copy of end-section fades down)
//
// copy current track to fader...
//
* pMusicInfoFadeOut = * pMusicInfoCurrent ; // struct copy
pMusicInfoFadeOut - > iXFadeVolumeSeekTime = Sys_Milliseconds ( ) ;
pMusicInfoFadeOut - > iXFadeVolumeSeekTo = 0 ;
//
pMusicInfoCurrent - > Rewind ( ) ;
pMusicInfoCurrent - > iXFadeVolumeSeekTime = Sys_Milliseconds ( ) ;
pMusicInfoCurrent - > iXFadeVolumeSeekTo = ( eMusic_StateActual = = eBGRNDTRACK_DEATH ) ? 0 : 255 ;
pMusicInfoCurrent - > iXFadeVolume = 0 ;
}
}
}
}
else
{
// special case, when foreground music is shut off but fader still running to fade off previous track...
//
MusicInfo_t * pMusicInfoFadeOut = & tMusic_Info [ eBGRNDTRACK_FADE ] ;
if ( pMusicInfoFadeOut - > bActive )
{
S_UpdateBackgroundTrack_Actual ( pMusicInfoFadeOut , qtrue , s_music_volume - > value ) ;
if ( pMusicInfoFadeOut - > iXFadeVolume = = 0 )
{
pMusicInfoFadeOut - > bActive = qfalse ;
alStreamStop ( ) ;
}
}
}
}
else
{
// standard / non-dynamic one-track music...
//
LPCSTR psCommand = S_Music_GetRequestedState ( ) ; // special check just for "silence" case...
qboolean bShouldBeSilent = ( psCommand & & ! Q_stricmp ( psCommand , " silence " ) ) ;
float fDesiredVolume = bShouldBeSilent ? 0.0f : s_music_volume - > value ;
//
// internal to this code is a volume-smoother...
//
qboolean bNewTrackDesired = S_UpdateBackgroundTrack_Actual ( & tMusic_Info [ eBGRNDTRACK_NONDYNAMIC ] , qtrue , fDesiredVolume ) ;
if ( bNewTrackDesired )
{
S_StartBackgroundTrack ( sMusic_BackgroundLoop , sMusic_BackgroundLoop , qfalse ) ;
}
}
}
int SND_GetMemoryUsed ( void )
{
ALint used ;
alGeti ( AL_MEMORY_USED , & used ) ;
return used ;
}
void SND_update ( sfx_t * sfx )
{
while ( SND_GetMemoryUsed ( ) > ( s_soundpoolmegs - > integer * 1024 * 1024 * 3 / 4 ) )
{
int iBytesFreed = SND_FreeOldestSound ( sfx ) ;
if ( iBytesFreed = = 0 )
break ; // sanity
}
}
// free any allocated sfx mem...
//
// now returns # bytes freed to help with z_malloc()-fail recovery
//
static int SND_FreeSFXMem ( sfx_t * sfx )
{
int iOrgMem = SND_GetMemoryUsed ( ) ;
alGetError ( ) ;
if ( sfx - > Buffer )
{
alDeleteBuffers ( 1 , & ( sfx - > Buffer ) ) ;
sfx - > Buffer = 0 ;
}
sfx - > iFlags & = ~ ( SFX_FLAG_RESIDENT | SFX_FLAG_LOADING ) ;
sfx - > iFlags | = SFX_FLAG_UNLOADED ;
return iOrgMem - SND_GetMemoryUsed ( ) ;
}
void S_DisplayFreeMemory ( )
{
}
void SND_TouchSFX ( sfx_t * sfx )
{
sfx - > iLastTimeUsed = Com_Milliseconds ( ) + 1 ;
}
// currently this is only called during snd_shutdown or snd_restart
//
static void S_FreeAllSFXMem ( void )
{
for ( int i = 0 ; i < MAX_SFX ; + + i )
{
if ( s_sfxCodes [ i ] ! = INVALID_CODE & & i ! = s_defaultSound )
{
SND_FreeSFXMem ( & s_sfxBlock [ i ] ) ;
}
}
}
// returns number of bytes freed up...
//
// new param is so we can be usre of not freeing ourselves (without having to rely on possible uninitialised timers etc)
//
int SND_FreeOldestSound ( sfx_t * pButNotThisOne /* = NULL */ )
{
int iBytesFreed = 0 ;
sfx_t * sfx ;
int iOldest = Com_Milliseconds ( ) ;
int iUsed = 0 ;
bool bDemandLoad = false ;
// start on 1 so we never dump the default sound...
//
for ( int i = 0 ; i < MAX_SFX ; + + i )
{
if ( s_sfxCodes [ i ] = = INVALID_CODE | | i = = s_defaultSound ) continue ;
sfx = & s_sfxBlock [ i ] ;
if ( sfx ! = pButNotThisOne )
{
// Don't throw out the default sound, sounds that
// are not in memory, or sounds newer then the oldest.
// Also, throw out demand load sounds first.
//
if ( ! ( sfx - > iFlags & SFX_FLAG_DEFAULT ) & &
( sfx - > iFlags & SFX_FLAG_RESIDENT ) & &
( ! bDemandLoad | | ( sfx - > iFlags & SFX_FLAG_DEMAND ) ) & &
sfx - > iLastTimeUsed < iOldest )
{
// new bit, we can't throw away any sfx_t struct in use by a channel,
// else the paint code will crash...
//
int iChannel ;
for ( iChannel = 0 ; iChannel < s_numChannels ; iChannel + + )
{
channel_t * ch = & s_channels [ iChannel ] ;
if ( ch - > thesfx = = sfx )
break ; // damn, being used
}
if ( iChannel = = s_numChannels )
{
// this sfx_t struct wasn't used by any channels, so we can lose it...
//
iUsed = i ;
iOldest = sfx - > iLastTimeUsed ;
bDemandLoad = ( sfx - > iFlags & SFX_FLAG_DEMAND ) ;
}
}
}
}
if ( iUsed )
{
sfx = & s_sfxBlock [ iUsed ] ;
iBytesFreed = SND_FreeSFXMem ( sfx ) ;
}
return iBytesFreed ;
}
int SND_FreeOldestSound ( void )
{
return SND_FreeOldestSound ( NULL ) ; // I had to add a void-arg version of this because of link issues, sigh
}
// just before we drop into a level, ensure the audio pool is under whatever the maximum
// pool size is (but not by dropping out sounds used by the current level)...
//
// returns qtrue if at least one sound was dropped out, so z_malloc-fail recovery code knows if anything changed
//
qboolean SND_RegisterAudio_Clean ( void )
{
if ( ! s_soundStarted ) {
return qfalse ;
}
qboolean bAtLeastOneSoundDropped = qfalse ;
Com_DPrintf ( " SND_RegisterAudio_Clean(): \n " ) ;
extern void S_DrainRawSoundData ( void ) ;
S_DrainRawSoundData ( ) ;
{
for ( int i = 0 ; i < MAX_SFX ; + + i )
{
if ( s_sfxCodes [ i ] = = INVALID_CODE | | i = = s_defaultSound ) continue ;
sfx_t * sfx = & s_sfxBlock [ i ] ;
if ( sfx - > iFlags & ( SFX_FLAG_RESIDENT | SFX_FLAG_DEMAND ) )
{
qboolean bDeleteThis = qtrue ;
//if (bDeleteThis)
{
int iChannel ;
for ( iChannel = 0 ; iChannel < s_numChannels ; iChannel + + )
{
if ( s_channels [ iChannel ] . thesfx = = sfx )
{
bDeleteThis = false ;
break ;
}
}
if ( bDeleteThis )
{
if ( ! ( sfx - > iFlags & SFX_FLAG_DEFAULT ) & &
( sfx - > iFlags & SFX_FLAG_RESIDENT ) & &
SND_FreeSFXMem ( sfx ) )
{
bAtLeastOneSoundDropped = qtrue ;
}
if ( sfx - > iFlags & SFX_FLAG_DEMAND )
{
s_sfxCodes [ i ] = INVALID_CODE ;
}
}
}
}
}
}
Com_DPrintf ( " SND_RegisterAudio_Clean(): Ok \n " ) ;
return bAtLeastOneSoundDropped ;
}
qboolean SND_RegisterAudio_LevelLoadEnd ( qboolean bDeleteEverythingNotUsedThisLevel /* 99% qfalse */ )
{
return qfalse ;
}
qboolean S_FileExists ( const char * psFilename )
{
// This is only really used for music. Need to swap .mp3 with .wxb on Xbox
char * fixedName = S_FixMusicFileName ( psFilename ) ;
// VVFIXME : This can be done better?
fileHandle_t fhTemp ;
FS_FOpenFileRead ( fixedName , & fhTemp , qtrue ) ; // qtrue so I can fclose the handle without closing a PAK
if ( ! fhTemp )
return qfalse ;
FS_FCloseFile ( fhTemp ) ;
return qtrue ;
}