/***************************************************************************** * name: snd_dma.c * * desc: main control for any streaming sound output device * * $Archive: /MissionPack/code/client/snd_dma.c $ * $Author: Mrelusive $ * $Revision: 117 $ * $Modtime: 6/06/01 2:35p $ * $Date: 6/06/01 7:11p $ * *****************************************************************************/ #include "snd_local.h" #include "snd_mp3.h" #include "client.h" void S_Play_f(void); void S_SoundList_f(void); static void S_Music_f(void); void S_Update_(); void S_StopAllSounds(void); static void S_UpdateBackgroundTrack( void ); extern int RE_RegisterMedia_GetLevel(void); ////////////////////////// // // vars for bgrnd music track... // const int iMP3MusicStream_DiskBytesToRead = 10000;//4096; const int iMP3MusicStream_DiskBufferSize = iMP3MusicStream_DiskBytesToRead * 2;//10; typedef struct { int/*qboolean*/ bIsMP3; // I wanted this kept as qboolean, but {0} init won't work with a typedef'd enum // // MP3 specific... // sfx_t sfxMP3_Bgrnd; MP3STREAM streamMP3_Bgrnd; // this one is pointed at by the sfx_t's ptr, and is NOT the one the decoder uses every cycle channel_t chMP3_Bgrnd; // ... but the one in this struct IS. // // MP3 disk streamer stuff... (if music is non-dynamic) // byte byMP3MusicStream_DiskBuffer[iMP3MusicStream_DiskBufferSize]; int iMP3MusicStream_DiskReadPos; int iMP3MusicStream_DiskWindowPos; // // MP3 disk-load stuff (for use during dynamic music, which is mem-resident) // byte *pLoadedData; // Z_Malloc, Z_Free int iLoadedDataLen; char sLoadedDataName[MAX_QPATH]; // // 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 qboolean bActive; // // Generic... // fileHandle_t s_backgroundFile; // valid handle, else -1 if an MP3 (so that NZ compares still work) wavinfo_t s_backgroundInfo; int s_backgroundSamples; } MusicInfo_t; // this now no longer supports dynamic music for MP codebase... // typedef enum { eBGRNDTRACK_SLOW = 0, // for normal walking around // eBGRNDTRACK_FAST, // for when stuff gets exciting // eBGRNDTRACK_FADE, // the xfade channel // // eBGRNDTRACK_NUMBEROF } MusicState_e; #define fDYNAMIC_XFADE_SECONDS (1.0f) static MusicInfo_t tMusic_Info[eBGRNDTRACK_NUMBEROF] = {0}; static MusicState_e eMusic_State = eBGRNDTRACK_SLOW; static char sMusic_BackgroundLoop[MAX_QPATH] = {0}; // ////////////////////////// // ======================================================================= // Internal sound data & structures // ======================================================================= // only begin attenuating sound volumes when outside the FULLVOLUME range #define SOUND_FULLVOLUME 80 #define SOUND_ATTENUATE 0.0008f channel_t s_channels[MAX_CHANNELS]; channel_t loop_channels[MAX_CHANNELS]; int numLoopChannels; static int s_soundStarted; qboolean s_soundMuted; dma_t dma; static int listener_number; static vec3_t listener_origin; static vec3_t listener_axis[3]; int s_soundtime; // sample PAIRS int s_paintedtime; // sample PAIRS // MAX_SFX may be larger than MAX_SOUNDS because // of custom player sounds #define MAX_SFX 4096 sfx_t s_knownSfx[MAX_SFX]; int s_numSfx = 0; #define LOOP_HASH 128 static sfx_t *sfxHash[LOOP_HASH]; cvar_t *s_volume; cvar_t *s_testsound; cvar_t *s_khz; cvar_t *s_show; cvar_t *s_mixahead; cvar_t *s_mixPreStep; cvar_t *s_musicVolume; cvar_t *s_musicMult; cvar_t *s_separation; cvar_t *s_doppler; cvar_t *s_CPUType; cvar_t *s_language; static loopSound_t loopSounds[MAX_GENTITIES]; static channel_t *freelist = NULL; int s_rawend; portable_samplepair_t s_rawsamples[MAX_RAW_SAMPLES]; /**************************************************************************************************\ * * Open AL Specific * \**************************************************************************************************/ int s_UseOpenAL = false; // Determines if using Open AL or the default software mixer ALfloat listener_pos[3]; // Listener Position ALfloat listener_ori[6]; // Listener Orientation int s_numChannels; // Number of AL Sources == Num of Channels short s_rawdata[MAX_RAW_SAMPLES*4]; // Used for Raw Samples (Music etc...) channel_t *S_OpenALPickChannel(int entnum, int entchannel); void UpdateSingleShotSounds(); void UpdateLoopingSounds(); void UpdateRawSamples(); // EAX Related ALboolean s_bEAX; // Is EAX 3.0 support is available bool s_bEALFileLoaded; // Has an .eal file been loaded for the current level bool s_bInWater; // Underwater effect currently active int s_EnvironmentID; // EAGLE ID of current environment LPEAXMANAGER s_lpEAXManager; // Pointer to EAXManager object HINSTANCE s_hEAXManInst; // Handle of EAXManager DLL EAXSet s_eaxSet; // EAXSet() function EAXGet s_eaxGet; // EAXGet() function bool s_eaxMorphing; // Is EAX Morphing in progress int s_eaxMorphStartTime;// EAX Morph start time int s_eaxMorphCount; // EAX Morph count (1 ... 10) EAXLISTENERPROPERTIES s_eaxLPSource; // Source EAX Parameters EAXLISTENERPROPERTIES s_eaxLPCur; // Current EAX Parameters EAXLISTENERPROPERTIES s_eaxLPDest; // Destination EAX Parameters char s_LevelName[MAX_QPATH]; // Name of current level void InitEAXManager(); void ReleaseEAXManager(); bool LoadEALFile(char *szEALFilename); void UnloadEALFile(); void UpdateEAXListener(bool bUseDefault, bool bUseMorphing); void UpdateEAXBuffer(channel_t *ch); void EALFileInit(char *level); void EAXMorph(); bool EAX3ListenerInterpolate(EAXLISTENERPROPERTIES *lpStartEAX3LP, EAXLISTENERPROPERTIES *lpFinishEAX3LP, float flRatio, EAXLISTENERPROPERTIES *lpResultEAX3LP, bool bCheckValues = false); void Clamp(EAXVECTOR *eaxVector); bool CheckEAX3LP(LPEAXLISTENERPROPERTIES lpEAX3LP); // EAX 3.0 GUIDS ... confidential information ... const GUID DSPROPSETID_EAX30_ListenerProperties = { 0xa8fa6882, 0xb476, 0x11d3, { 0xbd, 0xb9, 0x00, 0xc0, 0xf0, 0x2d, 0xdf, 0x87} }; const GUID DSPROPSETID_EAX30_BufferProperties = { 0xa8fa6881, 0xb476, 0x11d3, { 0xbd, 0xb9, 0x0, 0xc0, 0xf0, 0x2d, 0xdf, 0x87} }; /**************************************************************************************************\ * * End of Open AL Specific * \**************************************************************************************************/ // instead of clearing a whole channel_t struct, we're going to skip the MP3SlidingDecodeBuffer[] buffer in the middle... // #ifndef offsetof #include #endif static inline void Channel_Clear(channel_t *ch) { // memset (ch, 0, sizeof(*ch)); memset(ch,0,offsetof(channel_t,MP3SlidingDecodeBuffer)); byte *const p = (byte *)ch + offsetof(channel_t,MP3SlidingDecodeBuffer) + sizeof(ch->MP3SlidingDecodeBuffer); memset(p,0,(sizeof(*ch) - offsetof(channel_t,MP3SlidingDecodeBuffer)) - sizeof(ch->MP3SlidingDecodeBuffer)); } // ==================================================================== // User-setable variables // ==================================================================== 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"); } Com_Printf("%5d stereo\n", dma.channels - 1); Com_Printf("%5d samples\n", dma.samples); Com_Printf("%5d samplebits\n", dma.samplebits); Com_Printf("%5d submission_chunk\n", dma.submission_chunk); Com_Printf("%5d speed\n", dma.speed); Com_Printf("0x%x dma buffer\n", dma.buffer); if ( tMusic_Info[eBGRNDTRACK_SLOW].s_backgroundFile ) { Com_Printf("Background file: %s\n", sMusic_BackgroundLoop ); } else { Com_Printf("No background file.\n" ); } } S_DisplayFreeMemory(); Com_Printf("----------------------\n" ); } /* ================ S_Init ================ */ void S_Init( void ) { ALCcontext *ALCContext = NULL; ALCdevice *ALCDevice = NULL; ALfloat listenerPos[]={0.0,0.0,0.0}; ALfloat listenerVel[]={0.0,0.0,0.0}; ALfloat listenerOri[]={0.0,0.0,-1.0, 0.0,1.0,0.0}; cvar_t *cv; qboolean r; int i,j; channel_t *ch; Com_Printf("\n------- sound initialization -------\n"); s_volume = Cvar_Get ("s_volume", "0.8", CVAR_ARCHIVE); s_musicVolume = Cvar_Get ("s_musicvolume", "0.5", CVAR_ARCHIVE); //rww - multiply s_musicVolume by this value. Set in cgame when necessary. s_musicMult = Cvar_Get ("s_musicMult", "1", 0); s_separation = Cvar_Get ("s_separation", "0.5", CVAR_ARCHIVE); s_doppler = Cvar_Get ("s_doppler", "1", CVAR_ARCHIVE); s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); s_language = Cvar_Get("s_language","english",CVAR_ARCHIVE | CVAR_NORESTART); MP3_InitCvars(); s_CPUType = Cvar_Get("sys_cpuid","",0); // dontcha just love ID's defines sometimes?... // #if !( (defined __linux__ || __FreeBSD__ ) && (defined __i386__) ) #if !id386 #else extern unsigned int uiMMXAvailable; uiMMXAvailable = !!(s_CPUType->integer >= CPUID_INTEL_MMX); #endif #endif cv = Cvar_Get ("s_initsound", "1", 0); if ( !cv->integer ) { Com_Printf ("not initializing.\n"); Com_Printf("------------------------------------\n"); return; } Cmd_AddCommand("play", S_Play_f); Cmd_AddCommand("music", S_Music_f); Cmd_AddCommand("soundlist", S_SoundList_f); Cmd_AddCommand("soundinfo", S_SoundInfo_f); Cmd_AddCommand("soundstop", S_StopAllSounds); cv = Cvar_Get("s_UseOpenAL" , "0",CVAR_ARCHIVE|CVAR_LATCH); s_UseOpenAL = !!(cv->integer); if (s_UseOpenAL) { 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_soundStarted = 1; s_soundMuted = qtrue; s_soundtime = 0; s_paintedtime = 0; s_rawend = 0; S_StopAllSounds(); S_SoundInfo_f(); // Set default level name memset(s_LevelName, 0, sizeof(s_LevelName)); // Set Listener attributes alListenerfv(AL_POSITION,listenerPos); alListenerfv(AL_VELOCITY,listenerVel); alListenerfv(AL_ORIENTATION,listenerOri); InitEAXManager(); memset(s_channels, 0, sizeof(s_channels)); s_numChannels = 0; // Create as many AL Sources (up to 32) as possible for (i = 0; i < 32; i++) { alGenSources(1, &s_channels[i].alSource); if (alGetError() != AL_NO_ERROR) { // Reached limit of sources break; } alSourcef(s_channels[i].alSource, AL_REFERENCE_DISTANCE, 400.0f); if (alGetError() != AL_NO_ERROR) { break; } s_numChannels++; } // Generate AL Buffers for streaming audio playback (used for MP3s) ch = s_channels + 1; for (i = 1; i < s_numChannels; i++, ch++) { for (j = 0; j < NUM_STREAMING_BUFFERS; j++) { alGenBuffers(1, &(ch->buffers[j].BufferID)); ch->buffers[j].Status = UNQUEUED; ch->buffers[j].Data = (char *)Z_Malloc(STREAMING_BUFFER_SIZE, TAG_SND_RAWDATA, qfalse); } } // Open AL will always use 22K dma.speed = 22050; // These aren't really relevant for Open AL, but for completeness ... dma.channels = 2; dma.samplebits = 16; dma.samples = 0; dma.submission_chunk = 0; dma.buffer = NULL; // Clamp sound volumes between 0.0f and 1.0f if (s_volume->value < 0.f) s_volume->value = 0.f; if (s_volume->value > 1.f) s_volume->value = 1.f; if (s_musicVolume->value < 0.f) s_musicVolume->value = 0.f; if (s_musicVolume->value > 1.f) s_musicVolume->value = 1.f; return; } else { r = SNDDMA_Init(); Com_Printf("------------------------------------\n"); if ( r ) { s_soundStarted = 1; s_soundMuted = (qboolean)1; // s_numSfx = 0; s_soundtime = 0; s_paintedtime = 0; S_StopAllSounds (); S_SoundInfo_f(); } } } /* Mutes / Unmutes all sound */ void S_MuteAllSounds(bool bMute) { if (!s_soundStarted) return; if (!s_UseOpenAL) return; if (bMute) { alListenerf(AL_GAIN, 0.0f); } else { alListenerf(AL_GAIN, 1.0f); } } void S_ChannelFree(channel_t *v) { if (s_UseOpenAL) return; v->thesfx = NULL; *(channel_t **)v = freelist; freelist = (channel_t*)v; } channel_t* S_ChannelMalloc() { channel_t *v; if (s_UseOpenAL) return NULL; if (freelist == NULL) { return NULL; } v = freelist; freelist = *(channel_t **)freelist; v->allocTime = Com_Milliseconds(); return v; } void S_ChannelSetup() { channel_t *p, *q; if (s_UseOpenAL) return; // clear all the sounds so they don't Com_Memset( s_channels, 0, sizeof( s_channels ) ); p = s_channels;; q = p + MAX_CHANNELS; while (--q > p) { *(channel_t **)q = q-1; } *(channel_t **)q = NULL; freelist = p + MAX_CHANNELS - 1; Com_DPrintf("Channel memory manager started\n"); } // ======================================================================= // Shutdown sound engine // ======================================================================= void S_Shutdown( void ) { ALCcontext *ALCContext; ALCdevice *ALCDevice; channel_t *ch; int i,j; if ( !s_soundStarted ) { return; } if (s_UseOpenAL) { // Release all the AL Sources (including Music channel (Source 0)) for (i = 0; i < s_numChannels; i++) { alDeleteSources(1, &(s_channels[i].alSource)); } // Release all the AL Buffers here or not ? S_FreeAllSFXMem(); // Release Streaming AL Buffers ch = s_channels + 1; for (i = 1; i < s_numChannels; i++, ch++) { for (j = 0; j < NUM_STREAMING_BUFFERS; j++) { alDeleteBuffers(1, &(ch->buffers[j].BufferID)); ch->buffers[j].BufferID = 0; ch->buffers[j].Status = UNQUEUED; if (ch->buffers[j].Data) { Z_Free(ch->buffers[j].Data); ch->buffers[j].Data = NULL; } } } // Get active context ALCContext = alcGetCurrentContext(); // Get device for active context ALCDevice = alcGetContextsDevice(ALCContext); // Release context(s) alcDestroyContext(ALCContext); // Close device alcCloseDevice(ALCDevice); ReleaseEAXManager(); s_numChannels = 0; } else { SNDDMA_Shutdown(); } s_soundStarted = 0; Cmd_RemoveCommand("play"); Cmd_RemoveCommand("music"); Cmd_RemoveCommand("stopsound"); Cmd_RemoveCommand("soundlist"); Cmd_RemoveCommand("soundinfo"); } // ======================================================================= // Load a sound // ======================================================================= /* ================ return a hash value for the sfx name ================ */ static long S_HashSFXName(const char *name) { int i; long hash; char letter; hash = 0; i = 0; while (name[i] != '\0') { letter = tolower(name[i]); if (letter =='.') break; // don't include extension if (letter =='\\') letter = '/'; // damn path names hash+=(long)(letter)*(i+119); i++; } hash &= (LOOP_HASH-1); return hash; } /* ================== S_FindName Will allocate a new sfx if it isn't found ================== */ sfx_t *S_FindName( const char *name ) { int i; int hash; sfx_t *sfx; if (!name) { Com_Error (ERR_FATAL, "S_FindName: NULL\n"); } if (!name[0]) { Com_Error (ERR_FATAL, "S_FindName: 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); hash = S_HashSFXName(sSoundNameNoExt); sfx = sfxHash[hash]; // see if already loaded while (sfx) { if (!Q_stricmp(sfx->sSoundName, sSoundNameNoExt) ) { return sfx; } sfx = sfx->next; } /* // find a free sfx for (i=0 ; i < s_numSfx ; i++) { if (!s_knownSfx[i].soundName[0]) { break; } } */ i = s_numSfx; //we don't clear the soundName after failed loads any more, so it'll always be the last entry if (s_numSfx == MAX_SFX) { // ok, no sfx's free, but are there any with defaultSound set? (which the registering ent will never // see because he gets zero returned if it's default...) // for (i=0 ; i < s_numSfx ; i++) { if (s_knownSfx[i].bDefaultSound) { break; } } if (i==s_numSfx) { // genuinely out of handles... // if we ever reach this, let me know and I'll either boost the array or put in a map-used-on // reference to enable sfx_t recycling. TA codebase relies on being able to have structs for every sound // used anywhere, ever, all at once (though audio bit-buffer gets recycled). SOF1 used about 1900 distinct // events, so current MAX_SFX limit should do, or only need a small boost... -ste // Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); } } else { s_numSfx++; } sfx = &s_knownSfx[i]; memset (sfx, 0, sizeof(*sfx)); Q_strncpyz(sfx->sSoundName, sSoundNameNoExt, sizeof(sfx->sSoundName)); sfx->next = sfxHash[hash]; sfxHash[hash] = sfx; return sfx; } /* ================= S_DefaultSound ================= */ void S_DefaultSound( sfx_t *sfx ) { int i; sfx->iSoundLengthInSamples = 512; // #samples, ie shorts sfx->pSoundData = (short *) SND_malloc(512*2, sfx); // ... so *2 for alloc bytes sfx->bInMemory = qtrue; for ( i=0 ; i < sfx->iSoundLengthInSamples ; i++ ) { sfx->pSoundData[i] = i; } } /* =================== 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 ) { S_StopAllSounds(); s_soundMuted = qtrue; } /* ===================== S_BeginRegistration ===================== */ void S_BeginRegistration( void ) { s_soundMuted = qfalse; // we can play again if (s_numSfx == 0) { SND_setup(); s_numSfx = 0; Com_Memset( s_knownSfx, 0, sizeof( s_knownSfx ) ); Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); S_RegisterSound(DEFAULT_SOUND_NAME); S_DefaultSound( &s_knownSfx[0] ); } } void EALFileInit(char *level) { long lRoom; char name[MAX_QPATH]; char szEALFilename[MAX_QPATH]; char *szMapName; // If an EAL File is already unloaded, remove it if (s_bEALFileLoaded) { UnloadEALFile(); } // Reset variables s_bInWater = false; // Try and load an EAL file for the new level COM_StripExtension(level, name); // Find the last occurence of the '/' character szMapName = Q_strrchr(name, '/'); if (szMapName) { Com_sprintf(szEALFilename, MAX_QPATH, "eagle/%s.eal", ++szMapName); } else { Com_sprintf(szEALFilename, MAX_QPATH, "eagle/%s.eal", name); } s_bEALFileLoaded = LoadEALFile(szEALFilename); if (s_bEALFileLoaded) { UpdateEAXListener(true, false); } else { if ((s_bEAX)&&(s_eaxSet)) { lRoom = -10000; s_eaxSet(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ROOM, NULL, &lRoom, sizeof(long)); } } } /* ================== S_RegisterSound Creates a default buzz sound if the file can't be loaded ================== */ sfxHandle_t S_RegisterSound( const char *name) { sfx_t *sfx; if (!s_soundStarted) { return 0; } if (!name) { Com_Error (ERR_FATAL, "S_RegisterSound: NULL name\n"); } if (!name[0]) { Com_Error (ERR_FATAL, "S_RegisterSound: empty name\n"); } if ( strlen( name ) >= MAX_QPATH ) { Com_Error (ERR_FATAL, "S_RegisterSound: Sound name exceeds MAX_QPATH - %s\n", name ); return 0; } sfx = S_FindName( name ); SND_TouchSFX(sfx); if ( sfx->bDefaultSound ) return 0; if (s_UseOpenAL) { if ((sfx->pSoundData) || (sfx->Buffer)) return sfx - s_knownSfx; } else { if ( sfx->pSoundData ) { return sfx - s_knownSfx; } } sfx->bInMemory = qfalse; S_memoryLoad(sfx); if ( sfx->bDefaultSound ) { // Suppress error for inline sounds if(Q_stricmp(sfx->sSoundName, DEFAULT_SOUND_NAME)){ Com_DPrintf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->sSoundName ); } return 0; } return sfx - s_knownSfx; } void S_memoryLoad(sfx_t *sfx) { // load the sound file... // if ( !S_LoadSound( sfx ) ) { // Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->sSoundName ); sfx->bDefaultSound = qtrue; } sfx->bInMemory = qtrue; } //============================================================================= /* ================= S_SpatializeOrigin Used for spatializing s_channels ================= */ void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) { vec_t dot; vec_t dist; vec_t lscale, rscale, scale; vec3_t source_vec; vec3_t vec; float dist_mult = SOUND_ATTENUATE; // the more you divide it down, the further away the sound can be heard // calculate stereo seperation and distance attenuation VectorSubtract(origin, listener_origin, source_vec); dist = VectorNormalize(source_vec); dist -= SOUND_FULLVOLUME; if (dist < 0) dist = 0; // close enough to be at full volume dist *= dist_mult; // different attenuation levels VectorRotate( source_vec, listener_axis, vec ); dot = -vec[1]; if (dma.channels == 1) { // no attenuation = no spatialization rscale = 1.0; lscale = 1.0; } else { rscale = 0.5 * (1.0 + dot); lscale = 0.5 * (1.0 - dot); //rscale = s_separation->value + ( 1.0 - s_separation->value ) * dot; //lscale = s_separation->value - ( 1.0 - s_separation->value ) * dot; if ( rscale < 0 ) { rscale = 0; } if ( lscale < 0 ) { lscale = 0; } } // add in distance effect scale = (1.0 - dist) * rscale; *right_vol = (master_vol * scale); if (*right_vol < 0) *right_vol = 0; scale = (1.0 - dist) * lscale; *left_vol = (master_vol * scale); if (*left_vol < 0) *left_vol = 0; } static qboolean S_CheckChannelStomp( int chan1, int chan2 ) { if ( chan1 == chan2 ) { return qtrue; } // Hmmmm. without CHAN_VOICE_ATTEN this logic just dups the above, so for now... // // if ( ( chan1 == CHAN_VOICE /* || chan1 == CHAN_VOICE_ATTEN*/ ) && ( chan2 == CHAN_VOICE /*|| chan2 == CHAN_VOICE_ATTEN*/ ) ) // { // return qtrue; // } return qfalse; } channel_t *S_PickChannel(int entnum, int entchannel) { int ch_idx; channel_t *ch, *ch_firstToDie; qboolean foundChan = qfalse; if (s_UseOpenAL) return S_OpenALPickChannel(entnum, entchannel); 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[0]; for ( int pass = 0; (pass < ((entchannel == CHAN_AUTO)?1:2)) && !foundChan; pass++ ) { for (ch_idx = 0, ch = &s_channels[ch_idx]; ch_idx < MAX_CHANNELS ; ch_idx++, ch++ ) { if ( entchannel == CHAN_AUTO || pass > 0 ) {//if we're on the second pass, or we're doing a CHAN_AUTO then just find the first open chan if ( !ch->thesfx ) {//grab the first open channel ch_firstToDie = ch; foundChan = qtrue; break; } } else if ( ch->entnum == entnum //&& (/*entchannel != CHAN_AMBIENT*/1 || pass) // don't override Ambient sounds unless 2nd pass (ie all channels in use) && S_CheckChannelStomp( ch->entchannel, entchannel ) ) { // always override sound from same entity if ( s_show->integer == 1 && ch->thesfx ) { Com_Printf( S_COLOR_YELLOW"...overrides %s\n", ch->thesfx->sSoundName ); ch->thesfx = 0; //just to clear the next error msg } ch_firstToDie = ch; foundChan = qtrue; break; } // don't let anything else override local player sounds if ( ch->entnum == listener_number && entnum != listener_number && ch->thesfx) { continue; } // Ignore this, loopSounds are different array under TA codebase... // // // don't override loop sounds // if ( ch->loopSound ) { // continue; // } if ( ch->startSample < ch_firstToDie->startSample ) { ch_firstToDie = ch; } } } if ( s_show->integer == 1 && ch_firstToDie->thesfx ) { Com_Printf( S_COLOR_RED"***kicking %s\n", ch_firstToDie->thesfx->sSoundName ); } Channel_Clear(ch_firstToDie); // memset (ch_firstToDie, 0, sizeof(*ch_firstToDie)); return ch_firstToDie; } /* For use with Open AL Allows more than one sound of the same type to emanate from the same entity - sounds much better on hardware this way esp. rapid fire modes of weapons! */ channel_t *S_OpenALPickChannel(int entnum, int entchannel) { int ch_idx; channel_t *ch, *ch_firstToDie; bool foundChan = false; float source_pos[3]; 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 + 1; // channel 0 is reserved for Music for (ch_idx = 1, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) { // See if the channel is free if (!ch->thesfx) { ch_firstToDie = ch; foundChan = true; break; } } if (!foundChan) { for (ch_idx = 1, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) { if ( (ch->entnum == entnum) && (ch->entchannel == entchannel) && (ch->entnum != listener_number) ) { // Same entity and same type of sound effect (entchannel) ch_firstToDie = ch; foundChan = true; break; } } } int longestDist; int dist; if (!foundChan) { // Find sound effect furthest from listener ch = s_channels + 1; if (ch->fixed_origin) { // Convert to Open AL co-ordinates source_pos[0] = ch->origin[0]; source_pos[1] = ch->origin[2]; source_pos[2] = -ch->origin[1]; longestDist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); } else { if (ch->entnum == listener_number) longestDist = 0; else { // Convert to Open AL co-ordinates source_pos[0] = loopSounds[ch->entnum].origin[0]; source_pos[1] = loopSounds[ch->entnum].origin[2]; source_pos[2] = -loopSounds[ch->entnum].origin[1]; longestDist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); } } for (ch_idx = 2, ch = s_channels + ch_idx; ch_idx < s_numChannels; ch_idx++, ch++) { if (ch->fixed_origin) { // Convert to Open AL co-ordinates source_pos[0] = ch->origin[0]; source_pos[1] = ch->origin[2]; source_pos[2] = -ch->origin[1]; dist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); } else { if (ch->entnum == listener_number) dist = 0; else { // Convert to Open AL co-ordinates source_pos[0] = loopSounds[ch->entnum].origin[0]; source_pos[1] = loopSounds[ch->entnum].origin[2]; source_pos[2] = -loopSounds[ch->entnum].origin[1]; dist = ((listener_pos[0] - source_pos[0]) * (listener_pos[0] - source_pos[0])) + ((listener_pos[1] - source_pos[1]) * (listener_pos[1] - source_pos[1])) + ((listener_pos[2] - source_pos[2]) * (listener_pos[2] - source_pos[2])); } } if (dist > longestDist) { longestDist = dist; ch_firstToDie = ch; } } } if (ch_firstToDie->bPlaying) { if (s_show->integer == 1) { Com_Printf(S_COLOR_RED"***kicking %s\n", ch_firstToDie->thesfx->sSoundName ); } // Stop sound alSourceStop(ch_firstToDie->alSource); ch_firstToDie->bPlaying = false; } // Reset channel variables memset(&ch_firstToDie->MP3StreamHeader, 0, sizeof(MP3STREAM)); ch_firstToDie->bLooping = false; ch_firstToDie->bProcessed = false; ch_firstToDie->bStreaming = false; return ch_firstToDie; } /* ==================== S_MuteSound Gets the specified ent/channel and mutes any sound currently playing on it ==================== */ void S_MuteSound(int entityNum, int entchannel) { channel_t *ch; int i; if (entchannel < 1) { return; } if (s_UseOpenAL) { ch = s_channels + 1; for(i = 1; i < s_numChannels; i++,ch++) { if ((ch->entnum == entityNum) && (ch->entchannel == entchannel)) { alSourcef(ch->alSource, AL_GAIN, 0.0f); break; } } } else { ch = S_PickChannel( entityNum, entchannel ); if (!ch) { return; } ch->master_vol = 0; //just kill the volume and leave the rest alone, as to not actually interrupt anything expecting the sound to go through ch->leftvol = 0; ch->rightvol = 0; } } // ======================================================================= // Start a sound effect // ======================================================================= /* ==================== 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(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { channel_t *ch; sfx_t *sfx; int i; int curTime; if ( !s_soundStarted || s_soundMuted ) { return; } if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Error( ERR_DROP, "S_StartSound: handle %i out of range", sfxHandle ); } sfx = &s_knownSfx[ sfxHandle ]; if (sfx->bInMemory == qfalse) { S_memoryLoad(sfx); } if ( s_show->integer == 1 ) { Com_Printf( "%i : %s\n", s_paintedtime, sfx->sSoundName ); } if (s_UseOpenAL) { // To avoid playing the same sound multiple times ... if ((entityNum == ENTITYNUM_NONE) && (origin)) { // Check if we have already started playing this sound within 50 milliseconds ago ch = s_channels + 1; curTime = Com_Milliseconds(); for (i = 1; i < s_numChannels; i++, ch++) { if ((ch->thesfx == sfx) && (curTime < (ch->thesfx->iLastTimeUsed + 50))) { return; } } } else if (entchannel == CHAN_WEAPON) { // Check if we are playing a 'charging' sound, if so, stop it now .. ch = s_channels + 1; for (i = 1; i < s_numChannels; i++, ch++) { if ((ch->entnum == entityNum) && (ch->entchannel == CHAN_WEAPON) && (ch->thesfx) && (strstr(strlwr(ch->thesfx->sSoundName), "altcharge") != NULL)) { // Stop this sound alSourceStop(ch->alSource); alSourcei(ch->alSource, AL_BUFFER, NULL); ch->bPlaying = false; ch->thesfx = NULL; break; } } } else { ch = s_channels + 1; for (i = 1; i < s_numChannels; i++, ch++) { if ((ch->entnum == entityNum) && (ch->thesfx) && (strstr(strlwr(ch->thesfx->sSoundName), "falling") != NULL)) { // Stop this sound alSourceStop(ch->alSource); alSourcei(ch->alSource, AL_BUFFER, NULL); ch->bPlaying = false; ch->thesfx = NULL; break; } } } } SND_TouchSFX(sfx); // pick a channel to play on //--------------------------------- ch = S_PickChannel(entityNum, entchannel); ch->allocTime = sfx->iLastTimeUsed; /* ch = S_ChannelMalloc(); // entityNum, entchannel); if (!ch) { ch = s_channels; oldest = sfx->lastTimeUsed; chosen = -1; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { oldest = ch->allocTime; chosen = i; } } if (chosen == -1) { ch = s_channels; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { oldest = ch->allocTime; chosen = i; } } if (chosen == -1) { Com_Printf("S_StartSound(): dropping sound \"%s\"\n",sfx->soundName); return; } } ch = &s_channels[chosen]; ch->allocTime = sfx->lastTimeUsed; } */ //--------------------------------- if (origin) { VectorCopy (origin, ch->origin); ch->fixed_origin = qtrue; } else { ch->fixed_origin = qfalse; } if (s_UseOpenAL) ch->master_vol = 255; else ch->master_vol = 240; ch->entnum = entityNum; ch->thesfx = sfx; ch->startSample = START_SAMPLE_IMMEDIATE; ch->entchannel = entchannel; ch->leftvol = ch->master_vol; // these will get calced at next spatialize ch->rightvol = ch->master_vol; // unless the game isn't running ch->doppler = qfalse; // EF1 also had this, do we want it? /* if (entchannel < CHAN_AMBIENT && entityNum == listener_number) { //only do it for body sounds not local sounds ch->master_vol = SOUND_MAXVOL * SOUND_FMAXVOL; //this won't be attenuated so let it scale down } (SOUND_FMAXVOL = 0.75f) (SOUND_MAXVOL = 255) */ // if ( entchannel == CHAN_VOICE )//|| entchannel == CHAN_VOICE_ATTEN ) // { // s_entityWavVol[ ch->entnum ] = -1; //we've started the sound but it's silent for now // } if (sfx->pMP3StreamHeader) // -ste { memcpy(&ch->MP3StreamHeader,sfx->pMP3StreamHeader, sizeof(ch->MP3StreamHeader)); ch->iMP3SlidingDecodeWritePos = 0; ch->iMP3SlidingDecodeWindowPos= 0; } else { memset(&ch->MP3StreamHeader,0, sizeof(ch->MP3StreamHeader)); } } /* ================== S_StartLocalSound ================== */ void S_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Printf( S_COLOR_YELLOW, "S_StartLocalSound: handle %i out of range\n", sfxHandle ); return; } S_StartSound (NULL, listener_number, channelNum, sfxHandle ); } /* ================== S_StartLocalLoopingSound ================== */ void S_StartLocalLoopingSound( sfxHandle_t sfxHandle) { vec3_t nullVec = {0,0,0}; if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Error( ERR_DROP, "S_StartLocalLoopingSound: handle %i out of range", sfxHandle ); } S_AddLoopingSound( listener_number, nullVec, nullVec, sfxHandle);//, CHAN_AUTO ); } /* ================== S_ClearSoundBuffer If we are about to perform file access, clear the buffer so sound doesn't stutter. ================== */ void S_ClearSoundBuffer( void ) { int clear; if (!s_soundStarted) return; // stop looping sounds Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); numLoopChannels = 0; S_ChannelSetup(); s_rawend = 0; if (!s_UseOpenAL) { if (dma.samplebits == 8) clear = 0x80; else clear = 0; SNDDMA_BeginPainting (); if (dma.buffer) Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); SNDDMA_Submit (); } } /* ================== S_StopAllSounds ================== */ void S_StopAllSounds(void) { channel_t *ch; int i; if ( !s_soundStarted ) { return; } // stop the background music S_StopBackgroundTrack(); if (s_UseOpenAL) { ch = s_channels; for (i = 0; i < s_numChannels; i++, ch++) { alSourceStop(s_channels[i].alSource); ch->thesfx = NULL; memset(&ch->MP3StreamHeader, 0, sizeof(MP3STREAM)); ch->bLooping = false; ch->bProcessed = false; ch->bPlaying = false; ch->bStreaming = false; } } S_ClearSoundBuffer (); } /* ============================================================== continuous looping sounds are added each frame ============================================================== */ void S_StopLoopingSound(int entityNum) { loopSounds[entityNum].active = qfalse; // loopSounds[entityNum].sfx = 0; loopSounds[entityNum].kill = qfalse; } /* ================== S_ClearLoopingSounds ================== */ void S_ClearLoopingSounds( qboolean killall ) { int i; for ( i = 0 ; i < MAX_GENTITIES ; i++) { if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->iSoundLengthInSamples == 0)) { loopSounds[i].kill = qfalse; S_StopLoopingSound(i); } } numLoopChannels = 0; } /* ================== S_AddLoopingSound Called during entity generation for a frame Include velocity in case I get around to doing doppler... ================== */ void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle) { sfx_t *sfx; if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Error( ERR_DROP, "S_AddLoopingSound: handle %i out of range", sfxHandle ); } sfx = &s_knownSfx[ sfxHandle ]; if (sfx->bInMemory == qfalse) { S_memoryLoad(sfx); } SND_TouchSFX(sfx); if ( !sfx->iSoundLengthInSamples ) { Com_Error( ERR_DROP, "%s has length 0", sfx->sSoundName ); } VectorCopy( origin, loopSounds[entityNum].origin ); VectorCopy( velocity, loopSounds[entityNum].velocity ); loopSounds[entityNum].active = qtrue; loopSounds[entityNum].kill = qtrue; loopSounds[entityNum].doppler = qfalse; loopSounds[entityNum].oldDopplerScale = 1.0; loopSounds[entityNum].dopplerScale = 1.0; if (s_UseOpenAL) { if ((loopSounds[entityNum].bPlaying) && (loopSounds[entityNum].sfx != sfx)) { // Find the channel that is playing this sound, and stop it channel_t *ch; ch = s_channels + 1; for (int i = 1; i < s_numChannels; i++, ch++) { if ((ch->bLooping) && (ch->entnum == entityNum)) { alSourceStop(ch->alSource); ch->bPlaying = false; ch->thesfx = NULL; loopSounds[entityNum].bPlaying = false; break; } } } } loopSounds[entityNum].sfx = sfx; /* if (VectorLengthSquared(velocity)>0.0) { vec3_t out; float lena, lenb; loopSounds[entityNum].doppler = qtrue; lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); lenb = DistanceSquared(loopSounds[listener_number].origin, out); if ((loopSounds[entityNum].framenum+1) != cls.framecount) { loopSounds[entityNum].oldDopplerScale = 1.0; } else { loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; } loopSounds[entityNum].dopplerScale = lenb/(lena*100); if (loopSounds[entityNum].dopplerScale<0.5) { loopSounds[entityNum].dopplerScale = 0.5; } } */ loopSounds[entityNum].framenum = cls.framecount; } /* ================== S_AddLoopingSound Called during entity generation for a frame Include velocity in case I get around to doing doppler... ================== */ void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { sfx_t *sfx; if ( !s_soundStarted || s_soundMuted ) { return; } if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { Com_Printf( S_COLOR_YELLOW, "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); return; } sfx = &s_knownSfx[ sfxHandle ]; if (sfx->bInMemory == qfalse) { S_memoryLoad(sfx); } SND_TouchSFX(sfx); if ( !sfx->iSoundLengthInSamples ) { Com_Error( ERR_DROP, "%s has length 0", sfx->sSoundName ); } VectorCopy( origin, loopSounds[entityNum].origin ); VectorCopy( velocity, loopSounds[entityNum].velocity ); loopSounds[entityNum].sfx = sfx; loopSounds[entityNum].active = qtrue; loopSounds[entityNum].kill = qfalse; loopSounds[entityNum].doppler = qfalse; } // returns qtrue if ok to continue, else qfalse if all channels filled up this frame... // static qboolean LoopSound_ChannelInit(loopSound_t *pLoopSound, int iLeftVol, int iRightVol) { // allocate a channel // channel_t *ch = &loop_channels[numLoopChannels]; if (iLeftVol > 255) { iLeftVol = 255; } if (iRightVol > 255) { iRightVol = 255; } ch->master_vol = 255; ch->leftvol = iLeftVol; ch->rightvol = iRightVol; ch->thesfx = pLoopSound->sfx; ch->doppler = pLoopSound->doppler; ch->dopplerScale = pLoopSound->dopplerScale; ch->oldDopplerScale = pLoopSound->oldDopplerScale; // you cannot use MP3 files here because they offer only streaming access, not random // if (pLoopSound->sfx->pMP3StreamHeader) { Com_Error( ERR_DROP, "LoopSound_ChannelInit(): Cannot use streamed MP3 files here for random access (%s)\n",pLoopSound->sfx->sSoundName ); } else { memset( &ch->MP3StreamHeader, 0, sizeof(ch->MP3StreamHeader) ); } numLoopChannels++; if (s_UseOpenAL) { if (numLoopChannels == s_numChannels) return qfalse; } else { if (numLoopChannels == MAX_CHANNELS) { return qfalse; } } return qtrue; } // returns qfalse if sound would be inaudible, else qtrue for go ahead and play it this frame... // static qboolean LoopSound_SetupVolume(loopSound_t *pLoopSound, int time, int &iLeftTotal, int &iRightTotal) { if (pLoopSound->kill) { S_SpatializeOrigin( pLoopSound->origin, 240, &iLeftTotal, &iRightTotal); // 3d } else { S_SpatializeOrigin( pLoopSound->origin, 180/*90*/, &iLeftTotal, &iRightTotal); // sphere } pLoopSound->sfx->iLastTimeUsed = time; if (iLeftTotal == 0 && iRightTotal == 0) return qfalse; // not audible return qtrue; } /* ================== S_AddLoopSounds Spatialize all of the looping sounds. All sounds are on the same cycle, so any duplicates can just sum up the channel multipliers. ================== */ void S_AddLoopSounds (void) { int i, time; int left_total, right_total; loopSound_t *loop; static int loopFrame; numLoopChannels = 0; time = Com_Milliseconds(); loopFrame++; // now do the standard ones... // for ( i = 0 ; i < MAX_GENTITIES ; i++) { loop = &loopSounds[i]; if ( !loop->active // this next test is pointless, since ->mergeFrame is never set - remove field? -ste. // || loop->mergeFrame == loopFrame // already merged into an earlier sound ) { continue; } if (LoopSound_SetupVolume(loop, time, left_total, right_total)) { if (!LoopSound_ChannelInit(loop, left_total, right_total)) return; // all looping channels occupied } } } //============================================================================= /* ================= S_ByteSwapRawSamples If raw data has been loaded in little endien binary form, this must be done. If raw data was calculated, as with ADPCM, this should not be called. ================= */ void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { int i; if ( width != 2 ) { return; } if ( LittleShort( 256 ) == 256 ) { return; } if ( s_channels == 2 ) { samples <<= 1; } for ( i = 0 ; i < samples ; i++ ) { ((short *)data)[i] = LittleShort( ((short *)data)[i] ); } } portable_samplepair_t *S_GetRawSamplePointer() { return s_rawsamples; } /* ============ S_RawSamples Music streaming ============ */ void S_RawSamples( int samples, int rate, int width, int s_channels, const byte *data, float volume ) { int i; int src, dst; float scale; int intVolume; if ( !s_soundStarted || s_soundMuted ) { return; } intVolume = 256 * volume; if ( s_rawend < s_soundtime ) { Com_DPrintf( "S_RawSamples: resetting minimum: %i < %i\n", s_rawend, s_soundtime ); s_rawend = s_soundtime; } scale = (float)rate / dma.speed; //Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend); if (s_channels == 2 && width == 2) { if (scale == 1.0) { // optimized case for (i=0 ; i= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[src*2] * intVolume; s_rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; } } } else if (s_channels == 1 && width == 2) { for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[src] * intVolume; s_rawsamples[dst].right = ((short *)data)[src] * intVolume; } } else if (s_channels == 2 && width == 1) { intVolume *= 256; for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((char *)data)[src*2] * intVolume; s_rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; } } else if (s_channels == 1 && width == 1) { intVolume *= 256; for (i=0 ; ; i++) { src = i*scale; if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; s_rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; } } if ( s_rawend > s_soundtime + MAX_RAW_SAMPLES ) { Com_DPrintf( "S_RawSamples: overflowed %i > %i\n", s_rawend, s_soundtime ); } } //============================================================================= /* ===================== S_UpdateEntityPosition let the sound system know where an entity currently is ====================== */ void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { ALfloat pos[3]; channel_t *ch; int i; if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); } if (s_UseOpenAL) { if (entityNum == listener_number) return; ch = s_channels; for (i = 0; i < s_numChannels; i++, ch++) { if ((s_channels[i].bPlaying) & (s_channels[i].entnum == entityNum)) { pos[0] = origin[0]; pos[1] = origin[2]; pos[2] = -origin[1]; alSourcefv(s_channels[i].alSource, AL_POSITION, pos); if (s_bEALFileLoaded) { UpdateEAXBuffer(ch); } } } } VectorCopy( origin, loopSounds[entityNum].origin ); } /* ============ 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], int inwater ) { EAXOCCLUSIONPROPERTIES eaxOCProp; unsigned int ulEnvironment; int i; channel_t *ch; vec3_t origin; char *mapname; if ( !s_soundStarted || s_soundMuted ) { return; } if (s_UseOpenAL) { // Check if a new level has been loaded - if so, try and load the appropriate EAL file mapname = cl.mapname; if ((mapname) && (strcmp(mapname, s_LevelName) != 0)) { EALFileInit(mapname); strcpy(s_LevelName, mapname); } listener_number = entityNum; listener_pos[0] = head[0]; listener_pos[1] = head[2]; listener_pos[2] = -head[1]; alListenerfv(AL_POSITION, listener_pos); listener_ori[0] = axis[0][0]; listener_ori[1] = axis[0][2]; listener_ori[2] = -axis[0][1]; listener_ori[3] = axis[2][0]; listener_ori[4] = axis[2][2]; listener_ori[5] = -axis[2][1]; alListenerfv(AL_ORIENTATION, listener_ori); // Update EAX effects here if (s_bEALFileLoaded) { // Check if the Listener is underwater if (inwater) { // Check if we have already applied Underwater effect if (!s_bInWater) { // Apply Underwater Reverb effect, and occlude *all* Sources ulEnvironment = EAX_ENVIRONMENT_UNDERWATER; s_eaxSet(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT, NULL, &ulEnvironment, sizeof(unsigned int)); s_EnvironmentID = 999; eaxOCProp.lOcclusion = -3000; eaxOCProp.flOcclusionLFRatio = 0.0f; eaxOCProp.flOcclusionRoomRatio = 1.37f; eaxOCProp.flOcclusionDirectRatio = 1.0f; ch = s_channels + 1; for (i = 1; i < s_numChannels; i++, ch++) { s_eaxSet(&DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONPARAMETERS, ch->alSource, &eaxOCProp, sizeof(EAXOCCLUSIONPROPERTIES)); } s_bInWater = true; } } else { // Not underwater ... check if the underwater effect is still present if (s_bInWater) { // Remove underwater Reverb effect, and reset Occlusion / Obstruction amount on all Sources UpdateEAXListener(false, false); ch = s_channels + 1; for (i = 1; i < s_numChannels; i++, ch++) { UpdateEAXBuffer(ch); } s_bInWater = false; } else { UpdateEAXListener(false, true); } } } } else { listener_number = entityNum; VectorCopy(head, listener_origin); VectorCopy(axis[0], listener_axis[0]); VectorCopy(axis[1], listener_axis[1]); VectorCopy(axis[2], listener_axis[2]); // update spatialization for dynamic sounds ch = s_channels; for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { if ( !ch->thesfx ) { continue; } // anything coming from the view entity will always be full volume if (ch->entnum == listener_number) { ch->leftvol = ch->master_vol; ch->rightvol = ch->master_vol; } else { if (ch->fixed_origin) { VectorCopy( ch->origin, origin ); } else { VectorCopy( loopSounds[ ch->entnum ].origin, origin ); } S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); } } // add loopsounds S_AddLoopSounds (); } } /* ======================== S_ScanChannelStarts Returns qtrue if any new sounds were started since the last mix ======================== */ qboolean S_ScanChannelStarts( void ) { channel_t *ch; int i; qboolean newSamples; newSamples = qfalse; ch = s_channels; for (i=0; ithesfx ) { continue; } // if this channel was just started this frame, // set the sample count to it begins mixing // into the very first sample if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { ch->startSample = s_paintedtime; newSamples = qtrue; continue; } // if it is completely finished by now, clear it if ( ch->startSample + (ch->thesfx->iSoundLengthInSamples) <= s_paintedtime ) { S_ChannelFree(ch); } } return newSamples; } /* ============ S_Update Called once each time through the main loop ============ */ void S_Update( void ) { int i; int total; channel_t *ch; if ( !s_soundStarted || s_soundMuted ) { Com_DPrintf ("not started or muted\n"); return; } if (s_UseOpenAL) { // // debugging output // if ( s_show->integer == 2 ) { total = 0; ch = s_channels + 1; for (i=1 ; ithesfx && (ch->leftvol || ch->rightvol) ) { Com_Printf ("%s\n", ch->thesfx->sSoundName); total++; } } } } else { // // debugging output // if ( s_show->integer == 2 ) { total = 0; ch = s_channels; for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { Com_Printf ("%f %f %s\n", ch->leftvol, ch->rightvol, ch->thesfx->sSoundName); total++; } } Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); } } // The Open AL code, handles background music in the S_UpdateRawSamples function if (!s_UseOpenAL) { // add raw data from streamed samples S_UpdateBackgroundTrack(); } // mix some sound S_Update_(); } void S_GetSoundtime(void) { int samplepos; static int buffers; static int oldsamplepos; int fullsamples; fullsamples = dma.samples / dma.channels; // it is possible to miscount buffers if it has wrapped twice between // calls to S_Update. Oh well. samplepos = SNDDMA_GetDMAPos(); if (samplepos < oldsamplepos) { buffers++; // buffer wrapped if (s_paintedtime > 0x40000000) { // time to chop things off to avoid 32 bit limits buffers = 0; s_paintedtime = fullsamples; S_StopAllSounds (); } } oldsamplepos = samplepos; s_soundtime = buffers*fullsamples + samplepos/dma.channels; #if 0 // check to make sure that we haven't overshot if (s_paintedtime < s_soundtime) { Com_DPrintf ("S_Update_ : overflow\n"); s_paintedtime = s_soundtime; } #endif if ( dma.submission_chunk < 256 ) { s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; } else { s_paintedtime = s_soundtime + dma.submission_chunk; } } void S_Update_(void) { unsigned endtime; int samps; static float lastTime = 0.0f; float ma, op; float thisTime, sane; static int ot = -1; channel_t *ch; int i, j; int source; float pos[3]; if ( !s_soundStarted || s_soundMuted ) { return; } if (s_UseOpenAL) { UpdateSingleShotSounds(); ch = s_channels + 1; for ( i = 1; i < s_numChannels; i++, ch++ ) { if ( !ch->thesfx || (ch->bPlaying)) continue; source = ch - s_channels; // Get position of source if (ch->fixed_origin) { pos[0] = ch->origin[0]; pos[1] = ch->origin[2]; pos[2] = -ch->origin[1]; alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_FALSE); } else { if (ch->entnum == listener_number) { pos[0] = 0.0f; pos[1] = 0.0f; pos[2] = 0.0f; alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_TRUE); } else { // Get position of Entity pos[0] = loopSounds[ ch->entnum ].origin[0]; pos[1] = loopSounds[ ch->entnum ].origin[2]; pos[2] = -loopSounds[ ch->entnum ].origin[1]; alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_FALSE); } } alSourcefv(s_channels[source].alSource, AL_POSITION, pos); alSourcei(s_channels[source].alSource, AL_LOOPING, AL_FALSE); alSourcef(s_channels[source].alSource, AL_GAIN, ((float)(ch->master_vol) * s_volume->value) / 255.0f); if (s_bEALFileLoaded) UpdateEAXBuffer(ch); int nBytesDecoded = 0; int nTotalBytesDecoded = 0; int nBuffersToAdd = 0; if (ch->thesfx->pMP3StreamHeader) { memcpy(&ch->MP3StreamHeader, ch->thesfx->pMP3StreamHeader, sizeof(ch->MP3StreamHeader)); ch->iMP3SlidingDecodeWritePos = 0; ch->iMP3SlidingDecodeWindowPos= 0; // Reset streaming buffers status's for (i = 0; i < NUM_STREAMING_BUFFERS; i++) ch->buffers[i].Status = UNQUEUED; // Decode (STREAMING_BUFFER_SIZE / 1152) MP3 frames for each of the NUM_STREAMING_BUFFERS AL Buffers for (i = 0; i < NUM_STREAMING_BUFFERS; i++) { nTotalBytesDecoded = 0; for (j = 0; j < (STREAMING_BUFFER_SIZE / 1152); j++) { nBytesDecoded = C_MP3Stream_Decode(&ch->MP3StreamHeader); memcpy(ch->buffers[i].Data + nTotalBytesDecoded, ch->MP3StreamHeader.bDecodeBuffer, nBytesDecoded); nTotalBytesDecoded += nBytesDecoded; } if (nTotalBytesDecoded != STREAMING_BUFFER_SIZE) { memset(ch->buffers[i].Data + nTotalBytesDecoded, 0, (STREAMING_BUFFER_SIZE - nTotalBytesDecoded)); break; } } if (i >= NUM_STREAMING_BUFFERS) nBuffersToAdd = NUM_STREAMING_BUFFERS; else nBuffersToAdd = i + 1; // Make sure queue is empty first alSourcei(s_channels[source].alSource, AL_BUFFER, NULL); for (i = 0; i < nBuffersToAdd; i++) { // Copy decoded data to AL Buffer alBufferData(ch->buffers[i].BufferID, AL_FORMAT_MONO16, ch->buffers[i].Data, STREAMING_BUFFER_SIZE, 22050); // Queue AL Buffer on Source alSourceQueueBuffers(s_channels[source].alSource, 1, &(ch->buffers[i].BufferID)); if (alGetError() == AL_NO_ERROR) { ch->buffers[i].Status = QUEUED; } } // Clear error state, and check for successful Play call alGetError(); alSourcePlay(s_channels[source].alSource); if (alGetError() == AL_NO_ERROR) s_channels[source].bPlaying = true; // Record start time for Lip-syncing s_channels[source].iStartTime = Com_Milliseconds(); ch->bStreaming = true; return; } else { // Attach buffer to source alSourcei(s_channels[source].alSource, AL_BUFFER, ch->thesfx->Buffer); ch->bStreaming = false; // Clear error state, and check for successful Play call alGetError(); alSourcePlay(s_channels[source].alSource); if (alGetError() == AL_NO_ERROR) s_channels[source].bPlaying = true; } } UpdateLoopingSounds(); UpdateRawSamples(); EAXMorph(); } else { thisTime = Com_Milliseconds(); // Updates s_soundtime S_GetSoundtime(); if (s_soundtime == ot) { return; } ot = s_soundtime; // clear any sound effects that end before the current time, // and start any new sounds S_ScanChannelStarts(); sane = thisTime - lastTime; if (sane<11) { sane = 11; // 85hz } ma = s_mixahead->value * dma.speed; op = s_mixPreStep->value + sane*dma.speed*0.01; if (op < ma) { ma = op; } // mix ahead of current position endtime = s_soundtime + ma; // mix to an even submission block size endtime = (endtime + dma.submission_chunk-1) & ~(dma.submission_chunk-1); // never mix more than the complete buffer samps = dma.samples >> (dma.channels-1); if (endtime - s_soundtime > samps) endtime = s_soundtime + samps; SNDDMA_BeginPainting (); S_PaintChannels (endtime); SNDDMA_Submit (); lastTime = thisTime; } } void UpdateSingleShotSounds() { int i, j, k; ALint state; ALint processed; channel_t *ch; // Firstly, check if any single-shot sounds have completed, or if they need more data (for streaming Sources), // and/or if any of the currently playing (non-Ambient) looping sounds need to be stopped ch = s_channels ; for (i = 0; i < s_numChannels; i++, ch++) { ch->bProcessed = false; if (s_channels[i].bPlaying) { if (ch->bLooping) { // Looping Sound if (loopSounds[ch->entnum].active == false) { alSourceStop(s_channels[i].alSource); s_channels[i].bPlaying = false; s_channels[i].thesfx = NULL; loopSounds[ch->entnum].bPlaying = false; } } else { // Single-shot if (s_channels[i].bStreaming == false) { alGetSourcei(s_channels[i].alSource, AL_SOURCE_STATE, &state); if (state == AL_STOPPED) { s_channels[i].thesfx = NULL; s_channels[i].bPlaying = false; } } else { // Process streaming sample // Procedure :- // if more data to play // if any UNQUEUED Buffers // fill them with data // (else ?) // get number of buffers processed // fill them with data // restart playback if it has stopped (buffer underrun) // else // free channel int nBytesDecoded; if (ch->thesfx->pMP3StreamHeader) { if (ch->MP3StreamHeader.iSourceBytesRemaining == 0) { // Finished decoding data - if the source has finished playing then we're done alGetSourcei(ch->alSource, AL_SOURCE_STATE, &state); if (state == AL_STOPPED) { // Attach NULL buffer to Source to remove any buffers left in the queue alSourcei(ch->alSource, AL_BUFFER, NULL); ch->thesfx = NULL; ch->bPlaying = false; } // Move on to next channel ... continue; } // Check to see if any Buffers have been processed alGetSourcei(ch->alSource, AL_BUFFERS_PROCESSED, &processed); ALuint buffer; while (processed) { alSourceUnqueueBuffers(ch->alSource, 1, &buffer); for (j = 0; j < NUM_STREAMING_BUFFERS; j++) { if (ch->buffers[j].BufferID == buffer) { ch->buffers[j].Status = UNQUEUED; break; } } processed--; } int nTotalBytesDecoded = 0; for (j = 0; j < NUM_STREAMING_BUFFERS; j++) { if ((ch->buffers[j].Status == UNQUEUED) & (ch->MP3StreamHeader.iSourceBytesRemaining > 0)) { nTotalBytesDecoded = 0; for (k = 0; k < (STREAMING_BUFFER_SIZE / 1152); k++) { nBytesDecoded = C_MP3Stream_Decode(&ch->MP3StreamHeader); if (nBytesDecoded > 0) { memcpy(ch->buffers[j].Data + nTotalBytesDecoded, ch->MP3StreamHeader.bDecodeBuffer, nBytesDecoded); nTotalBytesDecoded += nBytesDecoded; } else { // Make sure that iSourceBytesRemaining is 0 if (ch->MP3StreamHeader.iSourceBytesRemaining != 0) { ch->MP3StreamHeader.iSourceBytesRemaining = 0; break; } } } if (nTotalBytesDecoded != STREAMING_BUFFER_SIZE) { memset(ch->buffers[j].Data + nTotalBytesDecoded, 0, (STREAMING_BUFFER_SIZE - nTotalBytesDecoded)); // Move data to buffer alBufferData(ch->buffers[j].BufferID, AL_FORMAT_MONO16, ch->buffers[j].Data, STREAMING_BUFFER_SIZE, 22050); // Queue Buffer on Source alSourceQueueBuffers(ch->alSource, 1, &(ch->buffers[j].BufferID)); // Update status of Buffer ch->buffers[j].Status = QUEUED; break; } else { // Move data to buffer alBufferData(ch->buffers[j].BufferID, AL_FORMAT_MONO16, ch->buffers[j].Data, STREAMING_BUFFER_SIZE, 22050); // Queue Buffer on Source alSourceQueueBuffers(ch->alSource, 1, &(ch->buffers[j].BufferID)); // Update status of Buffer ch->buffers[j].Status = QUEUED; } } } // Get state of Buffer alGetSourcei(ch->alSource, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { alSourcePlay(ch->alSource); #ifdef _DEBUG char szString[256]; sprintf(szString,"[%d] Restarting playback of single-shot streaming MP3 sample - still have %d bytes to decode\n", i, ch->MP3StreamHeader.iSourceBytesRemaining); OutputDebugString(szString); #endif } } } } } } } void UpdateLoopingSounds() { int i; ALuint source; channel_t *ch; loopSound_t *loop; float pos[3]; float fVolume = 0.003922; // 1.f / 255.f #ifdef _DEBUG // Clear AL Error State alGetError(); #endif for ( i = 0 ; i < MAX_GENTITIES ; i++) { loop = &loopSounds[i]; if ((loop->bPlaying)|(!loop->active)) continue; ch = S_PickChannel(i, CHAN_AUTO); // Play sound on channel ch->master_vol = 255; ch->entnum = i; ch->thesfx = loop->sfx; ch->entchannel = CHAN_AUTO; ch->fixed_origin = qfalse; ch->origin[0] = 0.f; ch->origin[1] = 0.f; ch->origin[2] = 0.f; ch->bLooping = true; source = ch - s_channels; alSourcei(s_channels[source].alSource, AL_BUFFER, ch->thesfx->Buffer); if (ch->entnum == listener_number) { // Make Source Head Relative and set position to 0,0,0 (on top of the listener) alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_TRUE); alSourcefv(s_channels[source].alSource, AL_POSITION, ch->origin); } else { pos[0] = loop->origin[0]; pos[1] = loop->origin[2]; pos[2] = -loop->origin[1]; alSourcefv(s_channels[source].alSource, AL_POSITION, pos); alSourcei(s_channels[source].alSource, AL_SOURCE_RELATIVE, AL_FALSE); } alSourcei(s_channels[source].alSource, AL_LOOPING, AL_TRUE); alSourcef(s_channels[source].alSource, AL_GAIN, (float)(ch->master_vol) * s_volume->value * fVolume); if (s_bEALFileLoaded) UpdateEAXBuffer(ch); alGetError(); alSourcePlay(s_channels[source].alSource); if (alGetError() == AL_NO_ERROR) { ch->bPlaying = true; loop->bPlaying = true; } } } void UpdateRawSamples() { ALuint buffer; ALint size; ALint processed; ALint state; int i,j,src; #ifdef _DEBUG char szString[256]; // Clear Open AL Error alGetError(); #endif S_UpdateBackgroundTrack(); // Find out how many buffers have been processed (played) by the Source alGetSourcei(s_channels[0].alSource, AL_BUFFERS_PROCESSED, &processed); while (processed) { // Unqueue each buffer, determine the length of the buffer, and then delete it alSourceUnqueueBuffers(s_channels[0].alSource, 1, &buffer); alGetBufferi(buffer, AL_SIZE, &size); alDeleteBuffers(1, &buffer); // Update sg.soundtime (+= number of samples played (number of bytes / 4)) s_soundtime += (size >> 2); processed--; } // S_UpdateBackgroundTrack(); // Add new data to a new Buffer and queue it on the Source if (s_rawend > s_paintedtime) { size = (s_rawend - s_paintedtime)<<2; if (size > (MAX_RAW_SAMPLES<<2)) { OutputDebugString("UpdateRawSamples :- Raw Sample buffer has overflowed !!!\n"); // s_rawend = s_paintedtime + MAX_RAW_SAMPLES; // size = MAX_RAW_SAMPLES<<2; size = MAX_RAW_SAMPLES<<2; s_paintedtime = s_rawend - MAX_RAW_SAMPLES; } // Copy samples from RawSamples to audio buffer (sg.rawdata) for (i = s_paintedtime, j = 0; i < s_rawend; i++, j+=2) { src = i & (MAX_RAW_SAMPLES - 1); s_rawdata[j] = (short)(s_rawsamples[src].left>>8); s_rawdata[j+1] = (short)(s_rawsamples[src].right>>8); } // Need to generate more than 1 buffer for music playback // iterations = 0; // largestBufferSize = (MAX_RAW_SAMPLES / 4) * 4 // while (size) // generate a buffer // if size > largestBufferSize // copy sg.rawdata + ((iterations * largestBufferSize)>>1) to buffer // size -= largestBufferSize // else // copy remainder // size = 0 // queue the buffer // iterations++; int iterations = 0; int largestBufferSize = MAX_RAW_SAMPLES; // in bytes (== quarter of Raw Samples data) while (size) { alGenBuffers(1, &buffer); if (size > largestBufferSize) { alBufferData(buffer, AL_FORMAT_STEREO16, (char*)(s_rawdata + ((iterations * largestBufferSize)>>1)), largestBufferSize, 22050); size -= largestBufferSize; } else { alBufferData(buffer, AL_FORMAT_STEREO16, (char*)(s_rawdata + ((iterations * largestBufferSize)>>1)), size, 22050); size = 0; } alSourceQueueBuffers(s_channels[0].alSource, 1, &buffer); iterations++; } // Update paintedtime s_paintedtime = s_rawend; // Check that the Source is actually playing alGetSourcei(s_channels[0].alSource, AL_SOURCE_STATE, &state); if (state != AL_PLAYING) { // Stopped playing ... due to buffer underrun // Unqueue any buffers still on the Source (they will be PROCESSED), and restart playback alGetSourcei(s_channels[0].alSource, AL_BUFFERS_PROCESSED, &processed); #ifdef _DEBUG sprintf(szString, "RawSamples Source stopped with %d buffer processed\n", processed); OutputDebugString(szString); #endif while (processed) { alSourceUnqueueBuffers(s_channels[0].alSource, 1, &buffer); processed--; alGetBufferi(buffer, AL_SIZE, &size); alDeleteBuffers(1, &buffer); // Update sg.soundtime (+= number of samples played (number of bytes / 4)) s_soundtime += (size >> 2); } #ifdef _DEBUG OutputDebugString("Restarting / Starting playback of Raw Samples\n"); #endif alSourcePlay(s_channels[0].alSource); } } #ifdef _DEBUG if (alGetError() != AL_NO_ERROR) OutputDebugString("OAL Error : UpdateRawSamples\n"); #endif } /* =============================================================================== console functions =============================================================================== */ void S_Play_f( void ) { int i; sfxHandle_t h; char name[256]; i = 1; while ( i [loopfile]\n"); return; } } // this table needs to be in-sync with the typedef'd enum "SoundCompressionMethod_t"... -ste // static const char *sSoundCompressionMethodStrings[ct_NUMBEROF] = { "16b", // ct_16 "mp3" // ct_MP3 }; void S_SoundList_f( void ) { int i; sfx_t *sfx; int size, total; total = 0; Com_Printf("\n"); Com_Printf(" InMemory?\n"); Com_Printf(" |\n"); Com_Printf(" | LevelLastUsedOn\n"); Com_Printf(" | |\n"); Com_Printf(" | |\n"); Com_Printf(" Slot Bytes Type | | Name\n"); // Com_Printf(" Slot Bytes Type InMem? Name\n"); for (sfx=s_knownSfx, i=0 ; iiSoundLengthInSamples; total += sfx->bInMemory ? size : 0; Com_Printf("%5d %7i [%s] %s %2d %s\n", i, size, sSoundCompressionMethodStrings[sfx->eSoundCompressionMethod], sfx->bInMemory?"y":"n", sfx->iLastLevelUsedOn, sfx->sSoundName ); } Com_Printf ("Total resident samples: %i ( not mem usage, see 'meminfo' ).\n", total); Com_Printf ("%d out of %d sfx_t slots used\n", s_numSfx, MAX_SFX); S_DisplayFreeMemory(); } /* =============================================================================== background music functions =============================================================================== */ int FGetLittleLong( fileHandle_t f ) { int v; FS_Read( &v, sizeof(v), f ); return LittleLong( v); } int FGetLittleShort( fileHandle_t f ) { short v; FS_Read( &v, sizeof(v), f ); return LittleShort( v); } // returns the length of the data in the chunk, or 0 if not found int S_FindWavChunk( fileHandle_t f, char *chunk ) { char name[5]; int len; int r; name[4] = 0; len = 0; r = FS_Read( name, 4, f ); if ( r != 4 ) { return 0; } len = FGetLittleLong( f ); if ( len < 0 || len > 0xfffffff ) { len = 0; return 0; } len = (len + 1 ) & ~1; // pad to word boundary // s_nextWavChunk += len + 8; if ( strcmp( name, chunk ) ) { return 0; } return len; } // 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_FileExists( const char *psFilename ) { fileHandle_t fhTemp; FS_FOpenFileRead (psFilename, &fhTemp, qtrue); // qtrue so I can fclose the handle without closing a PAK if (!fhTemp) return qfalse; FS_FCloseFile(fhTemp); return qtrue; } // some stuff for streaming MP3 files from disk (not pleasant, but nothing about MP3 is, other than compression ratios...) // static void MP3MusicStream_Reset(MusicInfo_t *pMusicInfo) { pMusicInfo->iMP3MusicStream_DiskReadPos = 0; pMusicInfo->iMP3MusicStream_DiskWindowPos = 0; } // // return is where the decoder should read from... // static byte *MP3MusicStream_ReadFromDisk(MusicInfo_t *pMusicInfo, int iReadOffset, int iReadBytesNeeded) { if (iReadOffset < pMusicInfo->iMP3MusicStream_DiskWindowPos) { assert(0); // should never happen return pMusicInfo->byMP3MusicStream_DiskBuffer; // ...but return something safe anyway } while (iReadOffset + iReadBytesNeeded > pMusicInfo->iMP3MusicStream_DiskReadPos) { int iBytesRead = FS_Read( pMusicInfo->byMP3MusicStream_DiskBuffer + (pMusicInfo->iMP3MusicStream_DiskReadPos - pMusicInfo->iMP3MusicStream_DiskWindowPos), iMP3MusicStream_DiskBytesToRead, pMusicInfo->s_backgroundFile ); pMusicInfo->iMP3MusicStream_DiskReadPos += iBytesRead; if (iBytesRead != iMP3MusicStream_DiskBytesToRead) // quietly ignore any requests to read past file end { break; // we need to do this because the disk read code can't know how much source data we need to // read for a given number of requested output bytes, so we'll always be asking for too many } } // if reached halfway point in buffer (approx 20k), backscroll it... // if (pMusicInfo->iMP3MusicStream_DiskReadPos - pMusicInfo->iMP3MusicStream_DiskWindowPos > iMP3MusicStream_DiskBufferSize/2) { int iMoveSrcOffset = iReadOffset - pMusicInfo->iMP3MusicStream_DiskWindowPos; int iMoveCount = (pMusicInfo->iMP3MusicStream_DiskReadPos - pMusicInfo->iMP3MusicStream_DiskWindowPos ) - iMoveSrcOffset; memmove( &pMusicInfo->byMP3MusicStream_DiskBuffer, &pMusicInfo->byMP3MusicStream_DiskBuffer[iMoveSrcOffset], iMoveCount); pMusicInfo->iMP3MusicStream_DiskWindowPos += iMoveSrcOffset; } return pMusicInfo->byMP3MusicStream_DiskBuffer + (iReadOffset - pMusicInfo->iMP3MusicStream_DiskWindowPos); } // does NOT set s_rawend!... // static void S_StopBackgroundTrack_Actual( MusicInfo_t *pMusicInfo ) { if ( pMusicInfo->s_backgroundFile ) { if ( pMusicInfo->s_backgroundFile != -1) { Sys_EndStreamedFile( pMusicInfo->s_backgroundFile ); FS_FCloseFile( pMusicInfo->s_backgroundFile ); } pMusicInfo->s_backgroundFile = 0; } } static void FreeMusic( MusicInfo_t *pMusicInfo ) { if (pMusicInfo->pLoadedData) { Z_Free(pMusicInfo->pLoadedData); pMusicInfo->pLoadedData = NULL; pMusicInfo->iLoadedDataLen = 0; pMusicInfo->sLoadedDataName[0]= '\0'; } } // called only by snd_restart // void S_UnCacheDynamicMusic( void ) { FreeMusic( &tMusic_Info[eBGRNDTRACK_SLOW] ); } static void S_StartBackgroundTrack_Actual( MusicInfo_t *pMusicInfo, const char *intro, const char *loop ) { int len; char dump[16]; char name[MAX_QPATH]; Q_strncpyz( sMusic_BackgroundLoop, loop, sizeof( sMusic_BackgroundLoop )); Q_strncpyz( name, intro, sizeof( name ) - 4 ); // this seems to be so that if the filename hasn't got an extension // but doesn't have the room to append on either then you'll just // get the "soft" fopen() error, rather than the ERR_DROP you'd get // if COM_DefaultExtension didn't have room to add it on. COM_DefaultExtension( name, sizeof( name ), ".wav" ); // close the background track, but DON'T reset s_rawend (or remaining music bits that haven't been output yet will be cut off) // #if 0 /* if ( pMusicInfo->s_backgroundFile ) { Sys_EndStreamedFile( pMusicInfo->s_backgroundFile ); FS_FCloseFile( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundFile = 0; } */ #else S_StopBackgroundTrack_Actual( pMusicInfo ); #endif pMusicInfo->bIsMP3 = qfalse; if ( !intro[0] ) { return; } // 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 ); } if (!Q_stricmpn(name+(strlen(name)-4),".mp3",4)) { int iMP3Filelen = FS_FOpenFileRead( name, &pMusicInfo->s_backgroundFile, qtrue ); if (!pMusicInfo->s_backgroundFile) { Com_Printf( S_COLOR_RED"Couldn't open music file %s\n", name ); return; } MP3MusicStream_Reset( pMusicInfo ); byte *pbMP3DataSegment = NULL; int iInitialMP3ReadSize = 8192; // fairly arbitrary, whatever size this is then the decoder is allowed to // scan up to halfway of it to find floating headers, so don't make it // too small. 8k works fine. pbMP3DataSegment = MP3MusicStream_ReadFromDisk(pMusicInfo, 0, iInitialMP3ReadSize); if (MP3_IsValid(name, pbMP3DataSegment, iInitialMP3ReadSize, qtrue /*bStereoDesired*/)) { // init stream struct... // memset(&pMusicInfo->streamMP3_Bgrnd,0,sizeof(pMusicInfo->streamMP3_Bgrnd)); char *psError = C_MP3Stream_DecodeInit( &pMusicInfo->streamMP3_Bgrnd, pbMP3DataSegment, iMP3Filelen, dma.speed, 16, // sfx->width * 8, qtrue // bStereoDesired ); if (psError == NULL) { // init sfx struct & setup the few fields I actually need... // memset( &pMusicInfo->sfxMP3_Bgrnd,0,sizeof(pMusicInfo->sfxMP3_Bgrnd)); // pMusicInfo->sfxMP3_Bgrnd.width = 2; // read by MP3_GetSamples() pMusicInfo->sfxMP3_Bgrnd.iSoundLengthInSamples = 0x7FFFFFFF; // max possible +ve int, since music finishes when decoder stops pMusicInfo->sfxMP3_Bgrnd.pMP3StreamHeader = &pMusicInfo->streamMP3_Bgrnd; Q_strncpyz( pMusicInfo->sfxMP3_Bgrnd.sSoundName, name, sizeof(pMusicInfo->sfxMP3_Bgrnd.sSoundName) ); pMusicInfo->s_backgroundInfo.format = WAV_FORMAT_MP3; // not actually used this way, but just ensures we don't match one of the legit formats pMusicInfo->s_backgroundInfo.channels = 2; // always, for our MP3s when used for music (else 1 for FX) pMusicInfo->s_backgroundInfo.rate = dma.speed; pMusicInfo->s_backgroundInfo.width = 2; // always, for our MP3s pMusicInfo->s_backgroundInfo.samples = pMusicInfo->sfxMP3_Bgrnd.iSoundLengthInSamples; pMusicInfo->s_backgroundSamples = pMusicInfo->sfxMP3_Bgrnd.iSoundLengthInSamples; memset(&pMusicInfo->chMP3_Bgrnd,0,sizeof(pMusicInfo->chMP3_Bgrnd)); pMusicInfo->chMP3_Bgrnd.thesfx = &pMusicInfo->sfxMP3_Bgrnd; memcpy(&pMusicInfo->chMP3_Bgrnd.MP3StreamHeader, pMusicInfo->sfxMP3_Bgrnd.pMP3StreamHeader, sizeof(*pMusicInfo->sfxMP3_Bgrnd.pMP3StreamHeader)); pMusicInfo->bIsMP3 = qtrue; } else { Com_Printf(S_COLOR_RED"Error streaming file %s: %s\n", name, psError); FS_FCloseFile( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundFile = 0; } } else { // MP3_IsValid() will already have printed any errors via Com_Printf at this point... // FS_FCloseFile( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundFile = 0; } return; } else // not an mp3 file { // // open up a wav file and get all the info // FS_FOpenFileRead( name, &pMusicInfo->s_backgroundFile, qtrue ); if ( !pMusicInfo->s_backgroundFile ) { Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", name ); return; } // skip the riff wav header FS_Read(dump, 12, pMusicInfo->s_backgroundFile); if ( !S_FindWavChunk( pMusicInfo->s_backgroundFile, "fmt " ) ) { Com_Printf( S_COLOR_YELLOW "WARNING: No fmt chunk in %s\n", name ); FS_FCloseFile( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundFile = 0; return; } // save name for soundinfo pMusicInfo->s_backgroundInfo.format = FGetLittleShort( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundInfo.channels = FGetLittleShort( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundInfo.rate = FGetLittleLong( pMusicInfo->s_backgroundFile ); FGetLittleLong( pMusicInfo->s_backgroundFile ); FGetLittleShort( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundInfo.width = FGetLittleShort( pMusicInfo->s_backgroundFile ) / 8; if ( pMusicInfo->s_backgroundInfo.format != WAV_FORMAT_PCM ) { FS_FCloseFile( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundFile = 0; Com_Printf(S_COLOR_YELLOW "WARNING: Not a microsoft PCM format wav: %s\n", name); return; } if ( pMusicInfo->s_backgroundInfo.channels != 2 || pMusicInfo->s_backgroundInfo.rate != 22050 ) { Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", name ); } if ( ( len = S_FindWavChunk( pMusicInfo->s_backgroundFile, "data" ) ) == 0 ) { FS_FCloseFile( pMusicInfo->s_backgroundFile ); pMusicInfo->s_backgroundFile = 0; Com_Printf(S_COLOR_YELLOW "WARNING: No data chunk in %s\n", name); return; } pMusicInfo->s_backgroundInfo.samples = len / (pMusicInfo->s_backgroundInfo.width * pMusicInfo->s_backgroundInfo.channels); pMusicInfo->s_backgroundSamples = pMusicInfo->s_backgroundInfo.samples; // // start the background streaming // Sys_BeginStreamedFile( pMusicInfo->s_backgroundFile, 0x10000 ); } } static char gsIntroMusic[MAX_QPATH]={0}; static char gsLoopMusic [MAX_QPATH]={0}; void S_RestartMusic( void ) { if (s_soundStarted && !s_soundMuted ) { if (gsIntroMusic[0] || gsLoopMusic[0]) { S_StartBackgroundTrack( gsIntroMusic, gsLoopMusic, qfalse ); // ( default music start will set the state to SLOW ) } } } // Basic logic here is to see if the intro file specified actually exists, and if so, then it's not dynamic music, // but if it doesn't, and it DOES exist by strcat()ing "_fast" and "_slow" (.mp3) onto 2 copies of it, then it's dynamic. // // In either case, open it if it exits, and just set some vars that'll inhibit dynamicness if needed. // void S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting ) { if ( !intro ) { intro = ""; } if ( !loop || !loop[0] ) { loop = intro; } Q_strncpyz(gsIntroMusic,intro, sizeof(gsIntroMusic)); Q_strncpyz(gsLoopMusic, loop, sizeof(gsLoopMusic)); // do NOT start music from cgame now, the functions Com_TouchMemory() and RegisterMedia_LevelLoadEnd() // can take some time to execute (after music start), and can stutter it. So now, RegisterMedia_LevelLoadEnd() // will call S_RestartMusic now that the strings are set... // if ( bReturnWithoutStarting ) return; char sName[MAX_QPATH]; Q_strncpyz(sName,intro,sizeof(sName)); COM_DefaultExtension( sName, sizeof( sName ), ".mp3" ); // conceptually we always play the 'intro'[/sName] track, intro-to-loop transition is handled in UpdateBackGroundTrack(). // if (S_FileExists( sName )) { Com_DPrintf("S_StartBackgroundTrack: Found/using music track '%s'\n", sName); S_StartBackgroundTrack_Actual( &tMusic_Info[eBGRNDTRACK_SLOW], sName, loop ); } else { Com_Printf( S_COLOR_RED "ERROR: Unable to find music file:\n( %s )\n", sName ); S_StopBackgroundTrack(); } } void S_StopBackgroundTrack( void ) { S_StopBackgroundTrack_Actual( &tMusic_Info[eBGRNDTRACK_SLOW] ); s_rawend = 0; } // 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 ) { int bufferSamples; int fileSamples; byte raw[30000]; // just enough to fit in a mac stack frame (note that MP3 doesn't use full size of it) int fileBytes; int r; float fMasterVol = (s_musicVolume->value*s_musicMult->value); static float musicVolume = 0.25f; // this is to work around an obscure issue to do with sliding decoder windows and amounts being requested, since the // original MP3 stream-decoder wrapper was designed to work with audio-paintbuffer sized pieces... Basically 30000 // is far too big for the window decoder to handle in one request because of the time-travel issue associated with // normal sfx buffer painting, and allowing sufficient sliding room, even though the music file never goes back in time. // #define SIZEOF_RAW_BUFFER_FOR_MP3 4096 #define RAWSIZE (pMusicInfo->bIsMP3?SIZEOF_RAW_BUFFER_FOR_MP3:sizeof(raw)) if ( !pMusicInfo->s_backgroundFile ) { return qfalse; } musicVolume = (musicVolume + fMasterVol)/2.0f; // don't bother playing anything if musicvolume is 0 if ( musicVolume <= 0 ) { return qfalse; } // see how many samples should be copied into the raw buffer if ( s_rawend < s_soundtime ) { s_rawend = s_soundtime; } while ( s_rawend < s_soundtime + MAX_RAW_SAMPLES ) { bufferSamples = MAX_RAW_SAMPLES - (s_rawend - s_soundtime); // decide how much data needs to be read from the file fileSamples = bufferSamples * pMusicInfo->s_backgroundInfo.rate / dma.speed; // don't try and read past the end of the file if ( fileSamples > pMusicInfo->s_backgroundSamples ) { fileSamples = pMusicInfo->s_backgroundSamples; } // our max buffer size fileBytes = fileSamples * (pMusicInfo->s_backgroundInfo.width * pMusicInfo->s_backgroundInfo.channels); if (fileBytes > RAWSIZE ) { fileBytes = RAWSIZE; fileSamples = fileBytes / (pMusicInfo->s_backgroundInfo.width * pMusicInfo->s_backgroundInfo.channels); } qboolean qbForceFinish = qfalse; if (pMusicInfo->bIsMP3) { int iStartingSampleNum = pMusicInfo->chMP3_Bgrnd.thesfx->iSoundLengthInSamples - pMusicInfo->s_backgroundSamples; // but this IS relevant // Com_Printf(S_COLOR_YELLOW "Requesting MP3 samples: sample %d\n",iStartingSampleNum); if (pMusicInfo->s_backgroundFile == -1) { // in-mem... // qbForceFinish = (MP3Stream_GetSamples( &pMusicInfo->chMP3_Bgrnd, iStartingSampleNum, fileBytes/2, (short*) raw, qtrue ))?qfalse:qtrue; //Com_Printf(S_COLOR_YELLOW "Music time remaining: %f seconds\n", MP3Stream_GetRemainingTimeInSeconds( &pMusicInfo->chMP3_Bgrnd.MP3StreamHeader )); } else { // streaming an MP3 file instead... (note that the 'fileBytes' request size isn't that relevant for MP3s, // since code here can't know how much the MP3 needs to decompress) // byte *pbScrolledStreamData = MP3MusicStream_ReadFromDisk(pMusicInfo, pMusicInfo->chMP3_Bgrnd.MP3StreamHeader.iSourceReadIndex, fileBytes); pMusicInfo->chMP3_Bgrnd.MP3StreamHeader.pbSourceData = pbScrolledStreamData - pMusicInfo->chMP3_Bgrnd.MP3StreamHeader.iSourceReadIndex; qbForceFinish = (MP3Stream_GetSamples( &pMusicInfo->chMP3_Bgrnd, iStartingSampleNum, fileBytes/2, (short*) raw, qtrue ))?qfalse:qtrue; } } else { // streaming a WAV off disk... // r = Sys_StreamedRead( raw, 1, fileBytes, pMusicInfo->s_backgroundFile ); if ( r != fileBytes ) { Com_Printf(S_COLOR_RED"StreamedRead failure on music track\n"); S_StopBackgroundTrack(); return qfalse; } // byte swap if needed (do NOT do for MP3 decoder, that has an internal big/little endian handler) // S_ByteSwapRawSamples( fileSamples, pMusicInfo->s_backgroundInfo.width, pMusicInfo->s_backgroundInfo.channels, raw ); } // add to raw buffer S_RawSamples( fileSamples, pMusicInfo->s_backgroundInfo.rate, pMusicInfo->s_backgroundInfo.width, pMusicInfo->s_backgroundInfo.channels, raw, musicVolume ); pMusicInfo->s_backgroundSamples -= fileSamples; if ( !pMusicInfo->s_backgroundSamples || qbForceFinish ) { // 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) // // 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), ".wav"); if (S_FileExists( sTestName )) { S_StartBackgroundTrack_Actual( pMusicInfo, sMusic_BackgroundLoop, sMusic_BackgroundLoop ); } 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; } if ( !pMusicInfo->s_backgroundFile ) { return qfalse; // loop failed to restart } } } #undef SIZEOF_RAW_BUFFER_FOR_MP3 #undef RAWSIZE return qfalse; } static void S_UpdateBackgroundTrack( void ) { qboolean bNewTrackDesired = S_UpdateBackgroundTrack_Actual(&tMusic_Info[eBGRNDTRACK_SLOW]); if (bNewTrackDesired) { S_StartBackgroundTrack( sMusic_BackgroundLoop, sMusic_BackgroundLoop, qfalse ); } } // currently passing in sfx as a param in case I want to do something with it later. // byte *SND_malloc(int iSize, sfx_t *sfx) { byte *pData = (byte *) Z_Malloc(iSize, TAG_SND_RAWDATA); // don't bother asking for zeroed mem return pData; } cvar_t *s_soundpoolmegs = NULL; // called once-only in EXE lifetime... // void SND_setup() { s_soundpoolmegs = Cvar_Get("s_soundpoolmegs", "25", CVAR_ARCHIVE); if (Sys_LowPhysicalMemory() ) { Cvar_Set("s_soundpoolmegs", "0"); } Com_Printf("Sound memory manager started\n"); } // ask how much mem an sfx has allocated... // static int SND_MemUsed(sfx_t *sfx) { int iSize = 0; if (sfx->pSoundData){ iSize += Z_Size(sfx->pSoundData); } if (sfx->pMP3StreamHeader) { iSize += Z_Size(sfx->pMP3StreamHeader); } return iSize; } // free any allocated sfx mem... // // now returns # bytes freed to help with z_malloc()-fail recovery // static int SND_FreeSFXMem(sfx_t *sfx) { int iBytesFreed = 0; if ( sfx->pSoundData) { iBytesFreed += Z_Size( sfx->pSoundData); Z_Free( sfx->pSoundData ); sfx->pSoundData = NULL; } sfx->bInMemory = qfalse; if ( sfx->pMP3StreamHeader) { iBytesFreed += Z_Size( sfx->pMP3StreamHeader); Z_Free( sfx->pMP3StreamHeader ); sfx->pMP3StreamHeader = NULL; } return iBytesFreed; } void S_DisplayFreeMemory() { // Com_Printf("%.2fMB total sound bytes, %.2fMB used this map\n", // (float)iSNDBytes_Total /1024.0f/1024.0f, // (float)iSNDBytes_ThisMap /1024.0f/1024.0f // ); int iSoundDataSize = Z_MemSize ( TAG_SND_RAWDATA ) + Z_MemSize( TAG_SND_MP3STREAMHDR ); int iMusicDataSize = Z_MemSize ( TAG_SND_DYNAMICMUSIC ); if (iSoundDataSize || iMusicDataSize) { Com_Printf("\n%.2fMB audio data: ( %.2fMB WAV/MP3 ) + ( %.2fMB Music )\n", ((float)(iSoundDataSize+iMusicDataSize))/1024.0f/1024.0f, ((float)(iSoundDataSize))/1024.0f/1024.0f, ((float)(iMusicDataSize))/1024.0f/1024.0f ); // now count up amount used on this level... // iSoundDataSize = 0; for (int i=1; iiLastLevelUsedOn == RE_RegisterMedia_GetLevel()){ iSoundDataSize += SND_MemUsed(sfx); } } Com_Printf("%.2fMB in sfx_t alloc data (WAV/MP3) loaded this level\n",(float)iSoundDataSize/1024.0f/1024.0f); } } void SND_TouchSFX(sfx_t *sfx) { sfx->iLastTimeUsed = Com_Milliseconds(); // ditch this field sometime? sfx->iLastLevelUsedOn = RE_RegisterMedia_GetLevel(); } // currently this is only called during snd_shutdown or snd_restart // void S_FreeAllSFXMem(void) { for (int i=1 ; i < s_numSfx ; i++) // start @ 1 to skip freeing default sound { SND_FreeSFXMem(&s_knownSfx[i]); } } // returns number of bytes freed up... // int SND_FreeOldestSound() { int iBytesFreed = 0; sfx_t *sfx; int iOldest = Com_Milliseconds(); int iUsed = 0; // start on 1 so we never dump the default sound... // for (int i=1 ; i < s_numSfx ; i++) { sfx = &s_knownSfx[i]; if (!sfx->bDefaultSound && sfx->bInMemory && sfx->iLastTimeUsed < iOldest) { if (sfx->pSoundData) { iUsed = i; iOldest = sfx->iLastTimeUsed; } else { assert(0); // shouldn't need this, but I'm sure I saw one once then had bInMemory but !pSoundData.... ?!?!? sfx->bInMemory = qfalse; } } } if (iUsed) { sfx = &s_knownSfx[ iUsed ]; Com_DPrintf("SND_FreeOldestSound: freeing sound %s\n", sfx->sSoundName); iBytesFreed = SND_FreeSFXMem(sfx); } return iBytesFreed; } // 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 // extern qboolean gbInsideLoadSound; qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* 99% qfalse */) { qboolean bAtLeastOneSoundDropped = qfalse; Com_DPrintf( "SND_RegisterAudio_LevelLoadEnd():\n"); if (gbInsideLoadSound) { Com_DPrintf( "(Inside S_LoadSound (z_malloc recovery?), exiting...\n"); } else { int iLoadedAudioBytes = Z_MemSize ( TAG_SND_RAWDATA ) + Z_MemSize( TAG_SND_MP3STREAMHDR ); const int iMaxAudioBytes = s_soundpoolmegs->integer * 1024 * 1024; for (int i=1; i iMaxAudioBytes || bDeleteEverythingNotUsedThisLevel) ; i++) // i=1 so we never page out default sound { sfx_t *sfx = &s_knownSfx[i]; if (sfx->bInMemory) { qboolean bDeleteThis = qfalse; if (bDeleteEverythingNotUsedThisLevel) { bDeleteThis = (sfx->iLastLevelUsedOn != RE_RegisterMedia_GetLevel() ) ? qtrue : qfalse; } else { bDeleteThis = (sfx->iLastLevelUsedOn < RE_RegisterMedia_GetLevel() ) ? qtrue : qfalse; } if (bDeleteThis) { Com_DPrintf( "Dumping sfx_t \"%s\"\n",sfx->sSoundName); if (SND_FreeSFXMem(sfx)) { bAtLeastOneSoundDropped = qtrue; } iLoadedAudioBytes = Z_MemSize ( TAG_SND_RAWDATA ) + Z_MemSize( TAG_SND_MP3STREAMHDR ); } } } } Com_DPrintf( "SND_RegisterAudio_LevelLoadEnd(): Ok\n"); return bAtLeastOneSoundDropped; } /****************************************************************************************************\ * * EAX Related * \****************************************************************************************************/ /* Initialize the EAX Manager */ void InitEAXManager() { LPEAXMANAGERCREATE lpEAXManagerCreateFn; HRESULT hr; // Check for EAX 3.0 support s_bEAX = alIsExtensionPresent((ALubyte*)"EAX3.0"); if (s_bEAX) { s_eaxSet = (EAXSet)alGetProcAddress((ALubyte*)"EAXSet"); if (s_eaxSet == NULL) s_bEAX = false; s_eaxGet = (EAXGet)alGetProcAddress((ALubyte*)"EAXGet"); if (s_eaxGet == NULL) s_bEAX = false; } // If we have detected EAX support, then try and load the EAX Manager DLL if (s_bEAX) { s_hEAXManInst = LoadLibrary("EAXMan.dll"); if (s_hEAXManInst) { lpEAXManagerCreateFn = (LPEAXMANAGERCREATE)GetProcAddress(s_hEAXManInst, "EaxManagerCreate"); if (lpEAXManagerCreateFn) { hr = lpEAXManagerCreateFn(&s_lpEAXManager); if (hr == EM_OK) return; } } } // If the EAXManager library was loaded (and there was a problem), then unload it if (s_hEAXManInst) { FreeLibrary(s_hEAXManInst); s_hEAXManInst = NULL; } s_lpEAXManager = NULL; s_bEAX = false; return; } /* Release the EAX Manager */ void ReleaseEAXManager() { s_bEAX = false; if (s_lpEAXManager) { s_lpEAXManager->Release(); s_lpEAXManager = NULL; } if (s_hEAXManInst) { FreeLibrary(s_hEAXManInst); s_hEAXManInst = NULL; } } /* Try to load the given .eal file */ bool LoadEALFile(char *szEALFilename) { char *ealData = NULL; int result; HRESULT hr; char szFullEALFilename[MAX_QPATH]; if ((!s_lpEAXManager) || (!s_bEAX)) { return false; } s_EnvironmentID = 0xFFFFFFFF; // Load EAL file from PAK file result = FS_ReadFile(szEALFilename, (void **)&ealData); if ((ealData) && (result != -1)) { hr = s_lpEAXManager->LoadDataSet(ealData, EMFLAG_LOADFROMMEMORY); // Unload EAL file FS_FreeFile (ealData); if (hr == EM_OK) return true; } else { // Failed to load via Quake loader, try manually Com_sprintf(szFullEALFilename, MAX_QPATH, "base/%s", szEALFilename); hr = s_lpEAXManager->LoadDataSet(szFullEALFilename, 0); if (hr == EM_OK) return true; } return false; } /* Unload current .eal file */ void UnloadEALFile() { HRESULT hr; if ((!s_lpEAXManager) || (!s_bEAX)) return; hr = s_lpEAXManager->FreeDataSet(0); return; } /* Updates the current EAX Reverb setting, based on the location of the listener */ void UpdateEAXListener(bool bUseDefault, bool bUseMorphing) { HRESULT hr; EMPOINT EMPoint; long lID; if ((!s_lpEAXManager) || (!s_bEAX)) return; if (bUseDefault) { // Get Default EAX Listener attributes hr = s_lpEAXManager->GetEnvironmentAttributes(EMFLAG_IDDEFAULT, &s_eaxLPSource); if (hr == EM_OK) { s_eaxLPSource.flAirAbsorptionHF = 0.0f; s_eaxSet(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ALLPARAMETERS, NULL, &s_eaxLPSource, sizeof(EAXLISTENERPROPERTIES)); s_eaxLPCur = s_eaxLPSource; s_eaxLPDest = s_eaxLPSource; s_EnvironmentID = EMFLAG_IDDEFAULT; s_eaxMorphStartTime = 0; s_eaxMorphCount = 0; return; } return; } // Convert Listener co-ordinate to left-handed system EMPoint.fX = listener_pos[0]; EMPoint.fY = listener_pos[1]; EMPoint.fZ = -listener_pos[2]; hr = s_lpEAXManager->GetListenerDynamicAttributes(0, &EMPoint, &lID, EMFLAG_LOCKPOSITION); if (hr == EM_OK) { if (lID != s_EnvironmentID) { // Get EAX Preset info. hr = s_lpEAXManager->GetEnvironmentAttributes(lID, &s_eaxLPDest); s_eaxLPDest.flAirAbsorptionHF = 0.0f; if (hr == EM_OK) { if (bUseMorphing) { // Morph to the new Destination from the Current Settings s_eaxLPSource = s_eaxLPCur; s_eaxMorphCount = 0; s_eaxMorphStartTime = Com_Milliseconds(); s_eaxMorphing = true; } else { // Set Environment s_eaxSet(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ALLPARAMETERS, NULL, &s_eaxLPDest, sizeof(EAXLISTENERPROPERTIES)); s_eaxLPSource = s_eaxLPCur = s_eaxLPDest; s_eaxMorphing = false; } s_EnvironmentID = lID; } } } return; } /* Updates the EAX Buffer related effects on the given Source */ void UpdateEAXBuffer(channel_t *ch) { HRESULT hr; EMPOINT EMSourcePoint; EMPOINT EMVirtualSourcePoint; EAXOBSTRUCTIONPROPERTIES eaxOBProp; EAXOCCLUSIONPROPERTIES eaxOCProp; // If EAX Manager is not initialized, or there is no EAX support, or the listener // is underwater, return if ((!s_lpEAXManager) || (!s_bEAX) || (s_bInWater)) return; // Set Occlusion Direct Ratio to the default value (it won't get set by the current version of // EAX Manager) eaxOCProp.flOcclusionDirectRatio = EAXBUFFER_DEFAULTOCCLUSIONDIRECTRATIO; // Convert Source co-ordinate to left-handed system if (ch->fixed_origin) { // Converting from Quake -> DS3D (for EAGLE) ... swap Y and Z EMSourcePoint.fX = ch->origin[0]; EMSourcePoint.fY = ch->origin[2]; EMSourcePoint.fZ = ch->origin[1]; } else { if (ch->entnum == listener_number) { // Source at same position as listener // Probably won't be any Occlusion / Obstruction effect -- unless the listener is underwater // Converting from Open AL -> DS3D (for EAGLE) ... invert Z EMSourcePoint.fX = listener_pos[0]; EMSourcePoint.fY = listener_pos[1]; EMSourcePoint.fZ = -listener_pos[2]; } else { // Get position of Entity // Converting from Quake -> DS3D (for EAGLE) ... swap Y and Z EMSourcePoint.fX = loopSounds[ ch->entnum ].origin[0]; EMSourcePoint.fY = loopSounds[ ch->entnum ].origin[2]; EMSourcePoint.fZ = loopSounds[ ch->entnum ].origin[1]; } } hr = s_lpEAXManager->GetSourceDynamicAttributes(0, &EMSourcePoint, &eaxOBProp.lObstruction, &eaxOBProp.flObstructionLFRatio, &eaxOCProp.lOcclusion, &eaxOCProp.flOcclusionLFRatio, &eaxOCProp.flOcclusionRoomRatio, &EMVirtualSourcePoint, 0); if (hr == EM_OK) { // Set EAX effect ! s_eaxSet(&DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OBSTRUCTIONPARAMETERS, ch->alSource, &eaxOBProp, sizeof(EAXOBSTRUCTIONPROPERTIES)); s_eaxSet(&DSPROPSETID_EAX_BufferProperties, DSPROPERTY_EAXBUFFER_OCCLUSIONPARAMETERS, ch->alSource, &eaxOCProp, sizeof(EAXOCCLUSIONPROPERTIES)); } return; } void EAXMorph() { int curPos; int curTime; float flRatio; if ((!s_bEAX) || (!s_eaxMorphing)) return; // Get current time curTime = Com_Milliseconds(); curPos = ((curTime - s_eaxMorphStartTime) / 100); if (curPos >= 10) { // Finished morphing s_eaxMorphing = false; s_eaxLPSource = s_eaxLPDest; s_eaxLPCur = s_eaxLPDest; s_eaxSet(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ALLPARAMETERS, NULL, &s_eaxLPSource, sizeof(EAXLISTENERPROPERTIES)); } else { if (curPos > s_eaxMorphCount) { // Next morph step flRatio = (float)curPos / 10.f; EAX3ListenerInterpolate(&s_eaxLPSource, &s_eaxLPDest, flRatio, &s_eaxLPCur); s_eaxSet(&DSPROPSETID_EAX_ListenerProperties, DSPROPERTY_EAXLISTENER_ALLPARAMETERS, NULL, &s_eaxLPCur, sizeof(EAXLISTENERPROPERTIES)); s_eaxMorphCount = curPos; } } } /***********************************************************************************************\ * * Definition of the EAXMorph function - EAX3ListenerInterpolate * \***********************************************************************************************/ /* EAX3ListenerInterpolate lpStart - Initial EAX 3 Listener parameters lpFinish - Final EAX 3 Listener parameters flRatio - Ratio Destination : Source (0.0 == Source, 1.0 == Destination) lpResult - Interpolated EAX 3 Listener parameters bCheckValues - Check EAX 3.0 parameters are in range, default = false (no checking) */ bool EAX3ListenerInterpolate(LPEAXLISTENERPROPERTIES lpStart, LPEAXLISTENERPROPERTIES lpFinish, float flRatio, LPEAXLISTENERPROPERTIES lpResult, bool bCheckValues) { EAXVECTOR StartVector, FinalVector; float flInvRatio; if (bCheckValues) { if (!CheckEAX3LP(lpStart)) return false; if (!CheckEAX3LP(lpFinish)) return false; } if (flRatio >= 1.0f) { memcpy(lpResult, lpFinish, sizeof(EAXLISTENERPROPERTIES)); return true; } else if (flRatio <= 0.0f) { memcpy(lpResult, lpStart, sizeof(EAXLISTENERPROPERTIES)); return true; } flInvRatio = (1.0f - flRatio); // Environment lpResult->ulEnvironment = 26; // (UNDEFINED environment) // Environment Size if (lpStart->flEnvironmentSize == lpFinish->flEnvironmentSize) lpResult->flEnvironmentSize = lpStart->flEnvironmentSize; else lpResult->flEnvironmentSize = (float)exp( (log(lpStart->flEnvironmentSize) * flInvRatio) + (log(lpFinish->flEnvironmentSize) * flRatio) ); // Environment Diffusion if (lpStart->flEnvironmentDiffusion == lpFinish->flEnvironmentDiffusion) lpResult->flEnvironmentDiffusion = lpStart->flEnvironmentDiffusion; else lpResult->flEnvironmentDiffusion = (lpStart->flEnvironmentDiffusion * flInvRatio) + (lpFinish->flEnvironmentDiffusion * flRatio); // Room if (lpStart->lRoom == lpFinish->lRoom) lpResult->lRoom = lpStart->lRoom; else lpResult->lRoom = (int)( ((float)lpStart->lRoom * flInvRatio) + ((float)lpFinish->lRoom * flRatio) ); // Room HF if (lpStart->lRoomHF == lpFinish->lRoomHF) lpResult->lRoomHF = lpStart->lRoomHF; else lpResult->lRoomHF = (int)( ((float)lpStart->lRoomHF * flInvRatio) + ((float)lpFinish->lRoomHF * flRatio) ); // Room LF if (lpStart->lRoomLF == lpFinish->lRoomLF) lpResult->lRoomLF = lpStart->lRoomLF; else lpResult->lRoomLF = (int)( ((float)lpStart->lRoomLF * flInvRatio) + ((float)lpFinish->lRoomLF * flRatio) ); // Decay Time if (lpStart->flDecayTime == lpFinish->flDecayTime) lpResult->flDecayTime = lpStart->flDecayTime; else lpResult->flDecayTime = (float)exp( (log(lpStart->flDecayTime) * flInvRatio) + (log(lpFinish->flDecayTime) * flRatio) ); // Decay HF Ratio if (lpStart->flDecayHFRatio == lpFinish->flDecayHFRatio) lpResult->flDecayHFRatio = lpStart->flDecayHFRatio; else lpResult->flDecayHFRatio = (float)exp( (log(lpStart->flDecayHFRatio) * flInvRatio) + (log(lpFinish->flDecayHFRatio) * flRatio) ); // Decay LF Ratio if (lpStart->flDecayLFRatio == lpFinish->flDecayLFRatio) lpResult->flDecayLFRatio = lpStart->flDecayLFRatio; else lpResult->flDecayLFRatio = (float)exp( (log(lpStart->flDecayLFRatio) * flInvRatio) + (log(lpFinish->flDecayLFRatio) * flRatio) ); // Reflections if (lpStart->lReflections == lpFinish->lReflections) lpResult->lReflections = lpStart->lReflections; else lpResult->lReflections = (int)( ((float)lpStart->lReflections * flInvRatio) + ((float)lpFinish->lReflections * flRatio) ); // Reflections Delay if (lpStart->flReflectionsDelay == lpFinish->flReflectionsDelay) lpResult->flReflectionsDelay = lpStart->flReflectionsDelay; else lpResult->flReflectionsDelay = (float)exp( (log(lpStart->flReflectionsDelay+0.0001) * flInvRatio) + (log(lpFinish->flReflectionsDelay+0.0001) * flRatio) ); // Reflections Pan // To interpolate the vector correctly we need to ensure that both the initial and final vectors vectors are clamped to a length of 1.0f StartVector = lpStart->vReflectionsPan; FinalVector = lpFinish->vReflectionsPan; Clamp(&StartVector); Clamp(&FinalVector); if (lpStart->vReflectionsPan.x == lpFinish->vReflectionsPan.x) lpResult->vReflectionsPan.x = lpStart->vReflectionsPan.x; else lpResult->vReflectionsPan.x = FinalVector.x + (flInvRatio * (StartVector.x - FinalVector.x)); if (lpStart->vReflectionsPan.y == lpFinish->vReflectionsPan.y) lpResult->vReflectionsPan.y = lpStart->vReflectionsPan.y; else lpResult->vReflectionsPan.y = FinalVector.y + (flInvRatio * (StartVector.y - FinalVector.y)); if (lpStart->vReflectionsPan.z == lpFinish->vReflectionsPan.z) lpResult->vReflectionsPan.z = lpStart->vReflectionsPan.z; else lpResult->vReflectionsPan.z = FinalVector.z + (flInvRatio * (StartVector.z - FinalVector.z)); // Reverb if (lpStart->lReverb == lpFinish->lReverb) lpResult->lReverb = lpStart->lReverb; else lpResult->lReverb = (int)( ((float)lpStart->lReverb * flInvRatio) + ((float)lpFinish->lReverb * flRatio) ); // Reverb Delay if (lpStart->flReverbDelay == lpFinish->flReverbDelay) lpResult->flReverbDelay = lpStart->flReverbDelay; else lpResult->flReverbDelay = (float)exp( (log(lpStart->flReverbDelay+0.0001) * flInvRatio) + (log(lpFinish->flReverbDelay+0.0001) * flRatio) ); // Reverb Pan // To interpolate the vector correctly we need to ensure that both the initial and final vectors are clamped to a length of 1.0f StartVector = lpStart->vReverbPan; FinalVector = lpFinish->vReverbPan; Clamp(&StartVector); Clamp(&FinalVector); if (lpStart->vReverbPan.x == lpFinish->vReverbPan.x) lpResult->vReverbPan.x = lpStart->vReverbPan.x; else lpResult->vReverbPan.x = FinalVector.x + (flInvRatio * (StartVector.x - FinalVector.x)); if (lpStart->vReverbPan.y == lpFinish->vReverbPan.y) lpResult->vReverbPan.y = lpStart->vReverbPan.y; else lpResult->vReverbPan.y = FinalVector.y + (flInvRatio * (StartVector.y - FinalVector.y)); if (lpStart->vReverbPan.z == lpFinish->vReverbPan.z) lpResult->vReverbPan.z = lpStart->vReverbPan.z; else lpResult->vReverbPan.z = FinalVector.z + (flInvRatio * (StartVector.z - FinalVector.z)); // Echo Time if (lpStart->flEchoTime == lpFinish->flEchoTime) lpResult->flEchoTime = lpStart->flEchoTime; else lpResult->flEchoTime = (float)exp( (log(lpStart->flEchoTime) * flInvRatio) + (log(lpFinish->flEchoTime) * flRatio) ); // Echo Depth if (lpStart->flEchoDepth == lpFinish->flEchoDepth) lpResult->flEchoDepth = lpStart->flEchoDepth; else lpResult->flEchoDepth = (lpStart->flEchoDepth * flInvRatio) + (lpFinish->flEchoDepth * flRatio); // Modulation Time if (lpStart->flModulationTime == lpFinish->flModulationTime) lpResult->flModulationTime = lpStart->flModulationTime; else lpResult->flModulationTime = (float)exp( (log(lpStart->flModulationTime) * flInvRatio) + (log(lpFinish->flModulationTime) * flRatio) ); // Modulation Depth if (lpStart->flModulationDepth == lpFinish->flModulationDepth) lpResult->flModulationDepth = lpStart->flModulationDepth; else lpResult->flModulationDepth = (lpStart->flModulationDepth * flInvRatio) + (lpFinish->flModulationDepth * flRatio); // Air Absorption HF if (lpStart->flAirAbsorptionHF == lpFinish->flAirAbsorptionHF) lpResult->flAirAbsorptionHF = lpStart->flAirAbsorptionHF; else lpResult->flAirAbsorptionHF = (lpStart->flAirAbsorptionHF * flInvRatio) + (lpFinish->flAirAbsorptionHF * flRatio); // HF Reference if (lpStart->flHFReference == lpFinish->flHFReference) lpResult->flHFReference = lpStart->flHFReference; else lpResult->flHFReference = (float)exp( (log(lpStart->flHFReference) * flInvRatio) + (log(lpFinish->flHFReference) * flRatio) ); // LF Reference if (lpStart->flLFReference == lpFinish->flLFReference) lpResult->flLFReference = lpStart->flLFReference; else lpResult->flLFReference = (float)exp( (log(lpStart->flLFReference) * flInvRatio) + (log(lpFinish->flLFReference) * flRatio) ); // Room Rolloff Factor if (lpStart->flRoomRolloffFactor == lpFinish->flRoomRolloffFactor) lpResult->flRoomRolloffFactor = lpStart->flRoomRolloffFactor; else lpResult->flRoomRolloffFactor = (lpStart->flRoomRolloffFactor * flInvRatio) + (lpFinish->flRoomRolloffFactor * flRatio); // Flags lpResult->ulFlags = (lpStart->ulFlags & lpFinish->ulFlags); // Clamp Delays if (lpResult->flReflectionsDelay > EAXLISTENER_MAXREFLECTIONSDELAY) lpResult->flReflectionsDelay = EAXLISTENER_MAXREFLECTIONSDELAY; if (lpResult->flReverbDelay > EAXLISTENER_MAXREVERBDELAY) lpResult->flReverbDelay = EAXLISTENER_MAXREVERBDELAY; return true; } /* CheckEAX3LP Checks that the parameters in the EAX 3 Listener Properties structure are in-range */ bool CheckEAX3LP(LPEAXLISTENERPROPERTIES lpEAX3LP) { if ( (lpEAX3LP->lRoom < EAXLISTENER_MINROOM) || (lpEAX3LP->lRoom > EAXLISTENER_MAXROOM) ) return false; if ( (lpEAX3LP->lRoomHF < EAXLISTENER_MINROOMHF) || (lpEAX3LP->lRoomHF > EAXLISTENER_MAXROOMHF) ) return false; if ( (lpEAX3LP->lRoomLF < EAXLISTENER_MINROOMLF) || (lpEAX3LP->lRoomLF > EAXLISTENER_MAXROOMLF) ) return false; if ( (lpEAX3LP->ulEnvironment < EAXLISTENER_MINENVIRONMENT) || (lpEAX3LP->ulEnvironment > EAXLISTENER_MAXENVIRONMENT) ) return false; if ( (lpEAX3LP->flEnvironmentSize < EAXLISTENER_MINENVIRONMENTSIZE) || (lpEAX3LP->flEnvironmentSize > EAXLISTENER_MAXENVIRONMENTSIZE) ) return false; if ( (lpEAX3LP->flEnvironmentDiffusion < EAXLISTENER_MINENVIRONMENTDIFFUSION) || (lpEAX3LP->flEnvironmentDiffusion > EAXLISTENER_MAXENVIRONMENTDIFFUSION) ) return false; if ( (lpEAX3LP->flDecayTime < EAXLISTENER_MINDECAYTIME) || (lpEAX3LP->flDecayTime > EAXLISTENER_MAXDECAYTIME) ) return false; if ( (lpEAX3LP->flDecayHFRatio < EAXLISTENER_MINDECAYHFRATIO) || (lpEAX3LP->flDecayHFRatio > EAXLISTENER_MAXDECAYHFRATIO) ) return false; if ( (lpEAX3LP->flDecayLFRatio < EAXLISTENER_MINDECAYLFRATIO) || (lpEAX3LP->flDecayLFRatio > EAXLISTENER_MAXDECAYLFRATIO) ) return false; if ( (lpEAX3LP->lReflections < EAXLISTENER_MINREFLECTIONS) || (lpEAX3LP->lReflections > EAXLISTENER_MAXREFLECTIONS) ) return false; if ( (lpEAX3LP->flReflectionsDelay < EAXLISTENER_MINREFLECTIONSDELAY) || (lpEAX3LP->flReflectionsDelay > EAXLISTENER_MAXREFLECTIONSDELAY) ) return false; if ( (lpEAX3LP->lReverb < EAXLISTENER_MINREVERB) || (lpEAX3LP->lReverb > EAXLISTENER_MAXREVERB) ) return false; if ( (lpEAX3LP->flReverbDelay < EAXLISTENER_MINREVERBDELAY) || (lpEAX3LP->flReverbDelay > EAXLISTENER_MAXREVERBDELAY) ) return false; if ( (lpEAX3LP->flEchoTime < EAXLISTENER_MINECHOTIME) || (lpEAX3LP->flEchoTime > EAXLISTENER_MAXECHOTIME) ) return false; if ( (lpEAX3LP->flEchoDepth < EAXLISTENER_MINECHODEPTH) || (lpEAX3LP->flEchoDepth > EAXLISTENER_MAXECHODEPTH) ) return false; if ( (lpEAX3LP->flModulationTime < EAXLISTENER_MINMODULATIONTIME) || (lpEAX3LP->flModulationTime > EAXLISTENER_MAXMODULATIONTIME) ) return false; if ( (lpEAX3LP->flModulationDepth < EAXLISTENER_MINMODULATIONDEPTH) || (lpEAX3LP->flModulationDepth > EAXLISTENER_MAXMODULATIONDEPTH) ) return false; if ( (lpEAX3LP->flAirAbsorptionHF < EAXLISTENER_MINAIRABSORPTIONHF) || (lpEAX3LP->flAirAbsorptionHF > EAXLISTENER_MAXAIRABSORPTIONHF) ) return false; if ( (lpEAX3LP->flHFReference < EAXLISTENER_MINHFREFERENCE) || (lpEAX3LP->flHFReference > EAXLISTENER_MAXHFREFERENCE) ) return false; if ( (lpEAX3LP->flLFReference < EAXLISTENER_MINLFREFERENCE) || (lpEAX3LP->flLFReference > EAXLISTENER_MAXLFREFERENCE) ) return false; if ( (lpEAX3LP->flRoomRolloffFactor < EAXLISTENER_MINROOMROLLOFFFACTOR) || (lpEAX3LP->flRoomRolloffFactor > EAXLISTENER_MAXROOMROLLOFFFACTOR) ) return false; if (lpEAX3LP->ulFlags & EAXLISTENERFLAGS_RESERVED) return false; return true; } /* Clamp Clamps the length of the vector to 1.0f */ void Clamp(EAXVECTOR *eaxVector) { float flMagnitude; float flInvMagnitude; flMagnitude = (float)sqrt((eaxVector->x*eaxVector->x) + (eaxVector->y*eaxVector->y) + (eaxVector->z*eaxVector->z)); if (flMagnitude <= 1.0f) return; flInvMagnitude = 1.0f / flMagnitude; eaxVector->x *= flInvMagnitude; eaxVector->y *= flInvMagnitude; eaxVector->z *= flInvMagnitude; }