jedi-academy/codemp/client/snd_dma_console.cpp
2013-04-23 15:21:39 +10:00

2933 lines
67 KiB
C++

/*****************************************************************************
* 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;
}