diff options
Diffstat (limited to 'src/client/snd_openal.c')
-rw-r--r-- | src/client/snd_openal.c | 1658 |
1 files changed, 1658 insertions, 0 deletions
diff --git a/src/client/snd_openal.c b/src/client/snd_openal.c new file mode 100644 index 00000000..cd266d0d --- /dev/null +++ b/src/client/snd_openal.c @@ -0,0 +1,1658 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +#if USE_OPENAL + +#include "qal.h" + +// Console variables specific to OpenAL +cvar_t *s_alPrecache; +cvar_t *s_alGain; +cvar_t *s_alSources; +cvar_t *s_alDopplerFactor; +cvar_t *s_alDopplerSpeed; +cvar_t *s_alMinDistance; +cvar_t *s_alRolloff; +cvar_t *s_alDriver; + +/* +================= +S_AL_Format +================= +*/ +static +ALuint S_AL_Format(int width, int channels) +{ + ALuint format = AL_FORMAT_MONO16; + + // Work out format + if(width == 1) + { + if(channels == 1) + format = AL_FORMAT_MONO8; + else if(channels == 2) + format = AL_FORMAT_STEREO8; + } + else if(width == 2) + { + if(channels == 1) + format = AL_FORMAT_MONO16; + else if(channels == 2) + format = AL_FORMAT_STEREO16; + } + + return format; +} + +/* +================= +S_AL_ErrorMsg +================= +*/ +static const char *S_AL_ErrorMsg(ALenum error) +{ + switch(error) + { + case AL_NO_ERROR: + return "No error"; + case AL_INVALID_NAME: + return "Invalid name"; + case AL_INVALID_ENUM: + return "Invalid enumerator"; + case AL_INVALID_VALUE: + return "Invalid value"; + case AL_INVALID_OPERATION: + return "Invalid operation"; + case AL_OUT_OF_MEMORY: + return "Out of memory"; + default: + return "Unknown error"; + } +} + + +//=========================================================================== + + +typedef struct alSfx_s +{ + char filename[MAX_QPATH]; + ALuint buffer; // OpenAL buffer + qboolean isDefault; // Couldn't be loaded - use default FX + qboolean inMemory; // Sound is stored in memory + qboolean isLocked; // Sound is locked (can not be unloaded) + int lastUsedTime; // Time last used +} alSfx_t; + +static qboolean alBuffersInitialised = qfalse; + +// Sound effect storage, data structures +#define MAX_SFX 4096 +static alSfx_t knownSfx[MAX_SFX]; +static int numSfx = 0; + +static sfxHandle_t default_sfx; + +/* +================= +S_AL_BufferFindFree + +Find a free handle +================= +*/ +static sfxHandle_t S_AL_BufferFindFree( void ) +{ + int i; + + for(i = 0; i < MAX_SFX; i++) + { + // Got one + if(knownSfx[i].filename[0] == '\0') + return i; + } + + // Shit... + Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles"); + return -1; +} + +/* +================= +S_AL_BufferFind + +Find a sound effect if loaded, set up a handle otherwise +================= +*/ +static sfxHandle_t S_AL_BufferFind(const char *filename) +{ + // Look it up in the table + sfxHandle_t sfx = -1; + int i; + + for(i = 0; i < MAX_SFX; i++) + { + if(!Q_stricmp(knownSfx[i].filename, filename)) + { + sfx = i; + break; + } + } + + // Not found in table? + if(sfx == -1) + { + alSfx_t *ptr; + + sfx = S_AL_BufferFindFree(); + + // Clear and copy the filename over + ptr = &knownSfx[sfx]; + memset(ptr, 0, sizeof(*ptr)); + strcpy(ptr->filename, filename); + } + + // Return the handle + return sfx; +} + +/* +================= +S_AL_BufferUseDefault +================= +*/ +static void S_AL_BufferUseDefault(sfxHandle_t sfx) +{ + if(sfx == default_sfx) + Com_Error(ERR_FATAL, "Can't load default sound effect %s\n", knownSfx[sfx].filename); + + Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename); + knownSfx[sfx].isDefault = qtrue; + knownSfx[sfx].buffer = knownSfx[default_sfx].buffer; +} + +/* +================= +S_AL_BufferUnload +================= +*/ +static void S_AL_BufferUnload(sfxHandle_t sfx) +{ + ALenum error; + + if(knownSfx[sfx].filename[0] == '\0') + return; + + if(!knownSfx[sfx].inMemory) + return; + + // Delete it + qalDeleteBuffers(1, &knownSfx[sfx].buffer); + if((error = qalGetError()) != AL_NO_ERROR) + Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n", + knownSfx[sfx].filename); + + knownSfx[sfx].inMemory = qfalse; +} + +/* +================= +S_AL_BufferEvict +================= +*/ +static qboolean S_AL_BufferEvict( void ) +{ + int i, oldestBuffer = -1; + int oldestTime = Sys_Milliseconds( ); + + for( i = 0; i < MAX_SFX; i++ ) + { + if( !knownSfx[ i ].filename[ 0 ] ) + continue; + + if( !knownSfx[ i ].inMemory ) + continue; + + if( knownSfx[ i ].lastUsedTime < oldestTime ) + { + oldestTime = knownSfx[ i ].lastUsedTime; + oldestBuffer = i; + } + } + + if( oldestBuffer >= 0 ) + { + S_AL_BufferUnload( oldestBuffer ); + return qtrue; + } + else + return qfalse; +} + +/* +================= +S_AL_BufferLoad +================= +*/ +static void S_AL_BufferLoad(sfxHandle_t sfx) +{ + ALenum error; + + void *data; + snd_info_t info; + ALuint format; + + // Nothing? + if(knownSfx[sfx].filename[0] == '\0') + return; + + // Player SFX + if(knownSfx[sfx].filename[0] == '*') + return; + + // Already done? + if((knownSfx[sfx].inMemory) || (knownSfx[sfx].isDefault)) + return; + + // Try to load + data = S_CodecLoad(knownSfx[sfx].filename, &info); + if(!data) + { + S_AL_BufferUseDefault(sfx); + return; + } + + format = S_AL_Format(info.width, info.channels); + + // Create a buffer + qalGenBuffers(1, &knownSfx[sfx].buffer); + if((error = qalGetError()) != AL_NO_ERROR) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n", + knownSfx[sfx].filename, S_AL_ErrorMsg(error)); + return; + } + + // Fill the buffer + if( info.size == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData(knownSfx[sfx].buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050); + } + else + qalBufferData(knownSfx[sfx].buffer, format, data, info.size, info.rate); + + error = qalGetError(); + + // If we ran out of memory, start evicting the least recently used sounds + while(error == AL_OUT_OF_MEMORY) + { + if( !S_AL_BufferEvict( ) ) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", knownSfx[sfx].filename); + return; + } + + // Try load it again + qalBufferData(knownSfx[sfx].buffer, format, data, info.size, info.rate); + error = qalGetError(); + } + + // Some other error condition + if(error != AL_NO_ERROR) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n", + knownSfx[sfx].filename, S_AL_ErrorMsg(error)); + return; + } + + // Free the memory + Z_Free(data); + + // Woo! + knownSfx[sfx].inMemory = qtrue; +} + +/* +================= +S_AL_BufferUse +================= +*/ +static +void S_AL_BufferUse(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx); + knownSfx[sfx].lastUsedTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_BufferInit +================= +*/ +static +qboolean S_AL_BufferInit( void ) +{ + if(alBuffersInitialised) + return qtrue; + + // Clear the hash table, and SFX table + memset(knownSfx, 0, sizeof(knownSfx)); + numSfx = 0; + + // Load the default sound, and lock it + default_sfx = S_AL_BufferFind("sound/feedback/hit.wav"); + S_AL_BufferUse(default_sfx); + knownSfx[default_sfx].isLocked = qtrue; + + // All done + alBuffersInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_BufferShutdown +================= +*/ +static +void S_AL_BufferShutdown( void ) +{ + int i; + + if(!alBuffersInitialised) + return; + + // Unlock the default sound effect + knownSfx[default_sfx].isLocked = qfalse; + + // Free all used effects + for(i = 0; i < MAX_SFX; i++) + S_AL_BufferUnload(i); + + // Clear the tables + memset(knownSfx, 0, sizeof(knownSfx)); + + // All undone + alBuffersInitialised = qfalse; +} + +/* +================= +S_AL_RegisterSound +================= +*/ +static +sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed ) +{ + sfxHandle_t sfx = S_AL_BufferFind(sample); + + if( s_alPrecache->integer && (!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx); + knownSfx[sfx].lastUsedTime = Com_Milliseconds(); + + return sfx; +} + +/* +================= +S_AL_BufferGet + +Return's an sfx's buffer +================= +*/ +static +ALuint S_AL_BufferGet(sfxHandle_t sfx) +{ + return knownSfx[sfx].buffer; +} + + +//=========================================================================== + + +typedef struct src_s +{ + ALuint alSource; // OpenAL source object + sfxHandle_t sfx; // Sound effect in use + + int lastUsedTime; // Last time used + alSrcPriority_t priority; // Priority + int entity; // Owning entity (-1 if none) + int channel; // Associated channel (-1 if none) + + int isActive; // Is this source currently in use? + int isLocked; // This is locked (un-allocatable) + int isLooping; // Is this a looping effect (attached to an entity) + int isTracking; // Is this object tracking it's owner + + qboolean local; // Is this local (relative to the cam) +} src_t; + +#define MAX_SRC 128 +static src_t srcList[MAX_SRC]; +static int srcCount = 0; +static qboolean alSourcesInitialised = qfalse; + +typedef struct sentity_s +{ + vec3_t origin; + + int srcAllocated; // If a src_t has been allocated to this entity + int srcIndex; + + qboolean loopAddedThisFrame; + alSrcPriority_t loopPriority; + sfxHandle_t loopSfx; + qboolean startLoopingSound; +} sentity_t; + +static sentity_t entityList[MAX_GENTITIES]; + +/* +================= +S_AL_SrcInit +================= +*/ +static +qboolean S_AL_SrcInit( void ) +{ + int i; + int limit; + ALenum error; + + // Clear the sources data structure + memset(srcList, 0, sizeof(srcList)); + srcCount = 0; + + // Cap s_alSources to MAX_SRC + limit = s_alSources->integer; + if(limit > MAX_SRC) + limit = MAX_SRC; + else if(limit < 16) + limit = 16; + + // Allocate as many sources as possible + for(i = 0; i < limit; i++) + { + qalGenSources(1, &srcList[i].alSource); + if((error = qalGetError()) != AL_NO_ERROR) + break; + srcCount++; + } + + // All done. Print this for informational purposes + Com_Printf( "Allocated %d sources.\n", srcCount); + alSourcesInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_SrcShutdown +================= +*/ +static +void S_AL_SrcShutdown( void ) +{ + int i; + + if(!alSourcesInitialised) + return; + + // Destroy all the sources + for(i = 0; i < srcCount; i++) + { + if(srcList[i].isLocked) + Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i); + + qalSourceStop(srcList[i].alSource); + qalDeleteSources(1, &srcList[i].alSource); + } + + memset(srcList, 0, sizeof(srcList)); + + alSourcesInitialised = qfalse; +} + +/* +================= +S_AL_SrcSetup +================= +*/ +static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, + int entity, int channel, qboolean local) +{ + ALuint buffer; + float null_vector[] = {0, 0, 0}; + + // Mark the SFX as used, and grab the raw AL buffer + S_AL_BufferUse(sfx); + buffer = S_AL_BufferGet(sfx); + + // Set up src struct + srcList[src].lastUsedTime = Sys_Milliseconds(); + srcList[src].sfx = sfx; + srcList[src].priority = priority; + srcList[src].entity = entity; + srcList[src].channel = channel; + srcList[src].isActive = qtrue; + srcList[src].isLocked = qfalse; + srcList[src].isLooping = qfalse; + srcList[src].isTracking = qfalse; + srcList[src].local = local; + + // Set up OpenAL source + qalSourcei(srcList[src].alSource, AL_BUFFER, buffer); + qalSourcef(srcList[src].alSource, AL_PITCH, 1.0f); + qalSourcef(srcList[src].alSource, AL_GAIN, s_alGain->value * s_volume->value); + qalSourcefv(srcList[src].alSource, AL_POSITION, null_vector); + qalSourcefv(srcList[src].alSource, AL_VELOCITY, null_vector); + qalSourcei(srcList[src].alSource, AL_LOOPING, AL_FALSE); + qalSourcef(srcList[src].alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(local) + { + qalSourcei(srcList[src].alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(srcList[src].alSource, AL_ROLLOFF_FACTOR, 0); + } + else + { + qalSourcei(srcList[src].alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(srcList[src].alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } +} + +/* +================= +S_AL_SrcKill +================= +*/ +static void S_AL_SrcKill(srcHandle_t src) +{ + // I'm not touching it. Unlock it first. + if(srcList[src].isLocked) + return; + + // Stop it if it's playing + if(srcList[src].isActive) + qalSourceStop(srcList[src].alSource); + + // Remove the entity association + if((srcList[src].isLooping) && (srcList[src].entity != -1)) + { + int ent = srcList[src].entity; + entityList[ent].srcAllocated = qfalse; + entityList[ent].srcIndex = -1; + entityList[ent].loopAddedThisFrame = qfalse; + entityList[ent].startLoopingSound = qfalse; + } + + // Remove the buffer + qalSourcei(srcList[src].alSource, AL_BUFFER, 0); + + srcList[src].sfx = 0; + srcList[src].lastUsedTime = 0; + srcList[src].priority = 0; + srcList[src].entity = -1; + srcList[src].channel = -1; + srcList[src].isActive = qfalse; + srcList[src].isLocked = qfalse; + srcList[src].isLooping = qfalse; + srcList[src].isTracking = qfalse; +} + +/* +================= +S_AL_SrcAlloc +================= +*/ +static +srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel ) +{ + int i; + int empty = -1; + int weakest = -1; + int weakest_time = Sys_Milliseconds(); + int weakest_pri = 999; + + for(i = 0; i < srcCount; i++) + { + // If it's locked, we aren't even going to look at it + if(srcList[i].isLocked) + continue; + + // Is it empty or not? + if((!srcList[i].isActive) && (empty == -1)) + empty = i; + else if(srcList[i].priority < priority) + { + // If it's older or has lower priority, flag it as weak + if((srcList[i].priority < weakest_pri) || + (srcList[i].lastUsedTime < weakest_time)) + { + weakest_pri = srcList[i].priority; + weakest_time = srcList[i].lastUsedTime; + weakest = i; + } + } + + // The channel system is not actually adhered to by baseq3, and not + // implemented in snd_dma.c, so while the following is strictly correct, it + // causes incorrect behaviour versus defacto baseq3 +#if 0 + // Is it an exact match, and not on channel 0? + if((srcList[i].entity == entnum) && (srcList[i].channel == channel) && (channel != 0)) + { + S_AL_SrcKill(i); + return i; + } +#endif + } + + // Do we have an empty one? + if(empty != -1) + { + S_AL_SrcKill( empty ); + return empty; + } + + // No. How about an overridable one? + if(weakest != -1) + { + S_AL_SrcKill(weakest); + return weakest; + } + + // Nothing. Return failure (cries...) + return -1; +} + +/* +================= +S_AL_SrcFind + +Finds an active source with matching entity and channel numbers +Returns -1 if there isn't one +================= +*/ +#if 0 +static +srcHandle_t S_AL_SrcFind(int entnum, int channel) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if(!srcList[i].isActive) + continue; + if((srcList[i].entity == entnum) && (srcList[i].channel == channel)) + return i; + } + return -1; +} +#endif + +/* +================= +S_AL_SrcLock + +Locked sources will not be automatically reallocated or managed +================= +*/ +static +void S_AL_SrcLock(srcHandle_t src) +{ + srcList[src].isLocked = qtrue; +} + +/* +================= +S_AL_SrcUnlock + +Once unlocked, the source may be reallocated again +================= +*/ +static +void S_AL_SrcUnlock(srcHandle_t src) +{ + srcList[src].isLocked = qfalse; +} + +/* +================= +S_AL_UpdateEntityPosition +================= +*/ +static +void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + VectorCopy( origin, entityList[entityNum].origin ); +} + +/* +================= +S_AL_StartLocalSound + +Play a local (non-spatialized) sound effect +================= +*/ +static +void S_AL_StartLocalSound(sfxHandle_t sfx, int channel) +{ + // Try to grab a source + srcHandle_t src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel); + if(src == -1) + return; + + // Set up the effect + S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, qtrue); + + // Start it playing + qalSourcePlay(srcList[src].alSource); +} + +/* +================= +S_AL_StartSound + +Play a one-shot sound effect +================= +*/ +static +void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + vec3_t sorigin; + + // Try to grab a source + srcHandle_t src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel); + if(src == -1) + return; + + // Set up the effect + S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, qfalse); + + if(origin == NULL) + { + srcList[src].isTracking = qtrue; + VectorCopy( entityList[entnum].origin, sorigin ); + } + else + VectorCopy( origin, sorigin ); + qalSourcefv(srcList[src].alSource, AL_POSITION, sorigin); + + // Start it playing + qalSourcePlay(srcList[src].alSource); +} + +/* +================= +S_AL_ClearLoopingSounds +================= +*/ +static +void S_AL_ClearLoopingSounds( qboolean killall ) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if((srcList[i].isLooping) && (srcList[i].entity != -1)) + entityList[srcList[i].entity].loopAddedThisFrame = qfalse; + } +} + +/* +================= +S_AL_SrcLoop +================= +*/ +static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, + const vec3_t origin, const vec3_t velocity, int entityNum ) +{ + int src; + sentity_t *sent = &entityList[ entityNum ]; + + // Do we need to allocate a new source for this entity + if( !sent->srcAllocated ) + { + // Try to get a channel + src = S_AL_SrcAlloc( priority, entityNum, -1 ); + if( src == -1 ) + { + Com_Printf( S_COLOR_RED "ERROR: Failed to allocate source " + "for loop sfx %d on entity %d\n", sfx, entityNum ); + return; + } + + sent->startLoopingSound = qtrue; + } + else + src = sent->srcIndex; + + sent->srcAllocated = qtrue; + sent->srcIndex = src; + + sent->loopPriority = priority; + sent->loopSfx = sfx; + + // If this is not set then the looping sound is removed + sent->loopAddedThisFrame = qtrue; + + // UGH + // These lines should be called via S_AL_SrcSetup, but we + // can't call that yet as it buffers sfxes that may change + // with subsequent calls to S_AL_SrcLoop + srcList[ src ].entity = entityNum; + srcList[ src ].isLooping = qtrue; + srcList[ src ].isActive = qtrue; + + // Set up the position and velocity + qalSourcefv( srcList[ src ].alSource, AL_POSITION, (ALfloat *)sent->origin ); + qalSourcefv( srcList[ src ].alSource, AL_VELOCITY, (ALfloat *)velocity ); +} + +/* +================= +S_AL_AddLoopingSound +================= +*/ +static +void S_AL_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +{ + S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_AddRealLoopingSound +================= +*/ +static +void S_AL_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) +{ + S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_StopLoopingSound +================= +*/ +static +void S_AL_StopLoopingSound(int entityNum ) +{ + if(entityList[entityNum].srcAllocated) + S_AL_SrcKill(entityList[entityNum].srcIndex); +} + +/* +================= +S_AL_SrcUpdate + +Update state (move things around, manage sources, and so on) +================= +*/ +static +void S_AL_SrcUpdate( void ) +{ + int i; + int entityNum; + ALint state; + + for(i = 0; i < srcCount; i++) + { + entityNum = srcList[i].entity; + + if(srcList[i].isLocked) + continue; + + if(!srcList[i].isActive) + continue; + + // Update source parameters + if((s_alGain->modified)||(s_volume->modified)) + qalSourcef(srcList[i].alSource, AL_GAIN, s_alGain->value * s_volume->value); + if((s_alRolloff->modified)&&(!srcList[i].local)) + qalSourcef(srcList[i].alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + if(s_alMinDistance->modified) + qalSourcef(srcList[i].alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if( srcList[ i ].isLooping ) + { + sentity_t *sent = &entityList[ entityNum ]; + + // If a looping effect hasn't been touched this frame, kill it + if( sent->loopAddedThisFrame ) + { + // The sound has changed without an intervening removal + if( srcList[ i ].isActive && !sent->startLoopingSound && + srcList[ i ].sfx != sent->loopSfx ) + { + qalSourceStop( srcList[ i ].alSource ); + qalSourcei( srcList[ i ].alSource, AL_BUFFER, 0 ); + sent->startLoopingSound = qtrue; + } + + // Ths sound hasn't been started yet + if( sent->startLoopingSound ) + { + S_AL_SrcSetup( i, sent->loopSfx, sent->loopPriority, + entityNum, -1, qfalse ); + srcList[ i ].isLooping = qtrue; + qalSourcei( srcList[ i ].alSource, AL_LOOPING, AL_TRUE ); + qalSourcePlay( srcList[ i ].alSource ); + + sent->startLoopingSound = qfalse; + } + } + else + S_AL_SrcKill( i ); + + continue; + } + + // See if it needs to be moved + if(srcList[i].isTracking) + qalSourcefv(srcList[i].alSource, AL_POSITION, entityList[entityNum].origin); + + // Check if it's done, and flag it + qalGetSourcei(srcList[i].alSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + S_AL_SrcKill(i); + continue; + } + } +} + +/* +================= +S_AL_SrcShutup +================= +*/ +static +void S_AL_SrcShutup( void ) +{ + int i; + for(i = 0; i < srcCount; i++) + S_AL_SrcKill(i); +} + +/* +================= +S_AL_SrcGet +================= +*/ +static +ALuint S_AL_SrcGet(srcHandle_t src) +{ + return srcList[src].alSource; +} + + +//=========================================================================== + + +static srcHandle_t streamSourceHandle = -1; +static qboolean streamPlaying = qfalse; +static ALuint streamSource; + +/* +================= +S_AL_AllocateStreamChannel +================= +*/ +static void S_AL_AllocateStreamChannel( void ) +{ + // Allocate a streamSource at high priority + streamSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(streamSourceHandle == -1) + return; + + // Lock the streamSource so nobody else can use it, and get the raw streamSource + S_AL_SrcLock(streamSourceHandle); + streamSource = S_AL_SrcGet(streamSourceHandle); + + // Set some streamSource parameters + qalSourcei (streamSource, AL_BUFFER, 0 ); + qalSourcei (streamSource, AL_LOOPING, AL_FALSE ); + qalSource3f(streamSource, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(streamSource, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(streamSource, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (streamSource, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (streamSource, AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_FreeStreamChannel +================= +*/ +static void S_AL_FreeStreamChannel( void ) +{ + // Release the output streamSource + S_AL_SrcUnlock(streamSourceHandle); + streamSource = 0; + streamSourceHandle = -1; +} + +/* +================= +S_AL_RawSamples +================= +*/ +static +void S_AL_RawSamples(int samples, int rate, int width, int channels, const byte *data, float volume) +{ + ALuint buffer; + ALuint format; + + format = S_AL_Format( width, channels ); + + // Create the streamSource if necessary + if(streamSourceHandle == -1) + { + S_AL_AllocateStreamChannel(); + + // Failed? + if(streamSourceHandle == -1) + { + Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n"); + return; + } + } + + // Create a buffer, and stuff the data into it + qalGenBuffers(1, &buffer); + qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate); + + // Shove the data onto the streamSource + qalSourceQueueBuffers(streamSource, 1, &buffer); + + // Volume + qalSourcef (streamSource, AL_GAIN, volume * s_volume->value * s_alGain->value); +} + +/* +================= +S_AL_StreamUpdate +================= +*/ +static +void S_AL_StreamUpdate( void ) +{ + int numBuffers; + ALint state; + + if(streamSourceHandle == -1) + return; + + // Un-queue any buffers, and delete them + qalGetSourcei( streamSource, AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSource, 1, &buffer); + qalDeleteBuffers(1, &buffer); + } + + // Start the streamSource playing if necessary + qalGetSourcei( streamSource, AL_BUFFERS_QUEUED, &numBuffers ); + + qalGetSourcei(streamSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + streamPlaying = qfalse; + + // If there are no buffers queued up, release the streamSource + if( !numBuffers ) + S_AL_FreeStreamChannel( ); + } + + if( !streamPlaying && numBuffers ) + { + qalSourcePlay( streamSource ); + streamPlaying = qtrue; + } +} + +/* +================= +S_AL_StreamDie +================= +*/ +static +void S_AL_StreamDie( void ) +{ + if(streamSourceHandle == -1) + return; + + streamPlaying = qfalse; + qalSourceStop(streamSource); + S_AL_FreeStreamChannel(); +} + + +//=========================================================================== + + +#define NUM_MUSIC_BUFFERS 4 +#define MUSIC_BUFFER_SIZE 4096 + +static qboolean musicPlaying = qfalse; +static srcHandle_t musicSourceHandle = -1; +static ALuint musicSource; +static ALuint musicBuffers[NUM_MUSIC_BUFFERS]; + +static snd_stream_t *mus_stream; +static char s_backgroundLoop[MAX_QPATH]; + +static byte decode_buffer[MUSIC_BUFFER_SIZE]; + +/* +================= +S_AL_MusicSourceGet +================= +*/ +static void S_AL_MusicSourceGet( void ) +{ + // Allocate a musicSource at high priority + musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(musicSourceHandle == -1) + return; + + // Lock the musicSource so nobody else can use it, and get the raw musicSource + S_AL_SrcLock(musicSourceHandle); + musicSource = S_AL_SrcGet(musicSourceHandle); + + // Set some musicSource parameters + qalSource3f(musicSource, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (musicSource, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_MusicSourceFree +================= +*/ +static void S_AL_MusicSourceFree( void ) +{ + // Release the output musicSource + S_AL_SrcUnlock(musicSourceHandle); + musicSource = 0; + musicSourceHandle = -1; +} + +/* +================= +S_AL_StopBackgroundTrack +================= +*/ +static +void S_AL_StopBackgroundTrack( void ) +{ + if(!musicPlaying) + return; + + // Stop playing + qalSourceStop(musicSource); + + // De-queue the musicBuffers + qalSourceUnqueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Destroy the musicBuffers + qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Free the musicSource + S_AL_MusicSourceFree(); + + // Unload the stream + if(mus_stream) + S_CodecCloseStream(mus_stream); + mus_stream = NULL; + + musicPlaying = qfalse; +} + +/* +================= +S_AL_MusicProcess +================= +*/ +static +void S_AL_MusicProcess(ALuint b) +{ + ALenum error; + int l; + ALuint format; + + l = S_CodecReadStream(mus_stream, MUSIC_BUFFER_SIZE, decode_buffer); + + // Run out data to read, start at the beginning again + if(l == 0) + { + S_CodecCloseStream(mus_stream); + mus_stream = S_CodecOpenStream(s_backgroundLoop); + if(!mus_stream) + { + S_AL_StopBackgroundTrack(); + return; + } + + l = S_CodecReadStream(mus_stream, MUSIC_BUFFER_SIZE, decode_buffer); + } + + format = S_AL_Format(mus_stream->info.width, mus_stream->info.channels); + + if( l == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 ); + } + else + qalBufferData(b, format, decode_buffer, l, mus_stream->info.rate); + + if( ( error = qalGetError( ) ) != AL_NO_ERROR ) + { + S_AL_StopBackgroundTrack( ); + Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n", + S_AL_ErrorMsg( error ) ); + return; + } +} + +/* +================= +S_AL_StartBackgroundTrack +================= +*/ +static +void S_AL_StartBackgroundTrack( const char *intro, const char *loop ) +{ + int i; + + // Stop any existing music that might be playing + S_AL_StopBackgroundTrack(); + + if ( !intro || !intro[0] ) { + intro = loop; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + + if((!intro || !intro[0]) && (!intro || !intro[0])) + return; + + // Copy the loop over + strncpy( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + // Open the intro + mus_stream = S_CodecOpenStream(intro); + + if(!mus_stream) + return; + + // Allocate a musicSource + S_AL_MusicSourceGet(); + if(musicSourceHandle == -1) + return; + + // Generate the musicBuffers + qalGenBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Queue the musicBuffers up + for(i = 0; i < NUM_MUSIC_BUFFERS; i++) + S_AL_MusicProcess(musicBuffers[i]); + qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Set the initial gain property + qalSourcef(musicSource, AL_GAIN, s_alGain->value * s_musicVolume->value); + + // Start playing + qalSourcePlay(musicSource); + + musicPlaying = qtrue; +} + +/* +================= +S_AL_MusicUpdate +================= +*/ +static +void S_AL_MusicUpdate( void ) +{ + int numBuffers; + ALint state; + + if(!musicPlaying) + return; + + qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint b; + qalSourceUnqueueBuffers(musicSource, 1, &b); + S_AL_MusicProcess(b); + qalSourceQueueBuffers(musicSource, 1, &b); + } + + // Hitches can cause OpenAL to be starved of buffers when streaming. + // If this happens, it will stop playback. This restarts the source if + // it is no longer playing, and if there are buffers available + qalGetSourcei( musicSource, AL_SOURCE_STATE, &state ); + qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers ); + if( state == AL_STOPPED && numBuffers ) + { + Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" ); + qalSourcePlay(musicSource); + } + + // Set the gain property + qalSourcef(musicSource, AL_GAIN, s_alGain->value * s_musicVolume->value); +} + + +//=========================================================================== + + +// Local state variables +static ALCdevice *alDevice; +static ALCcontext *alContext; + +#ifdef _WIN32 +#define ALDRIVER_DEFAULT "OpenAL32.dll" +#else +#define ALDRIVER_DEFAULT "libopenal.so.0" +#endif + +/* +================= +S_AL_StopAllSounds +================= +*/ +static +void S_AL_StopAllSounds( void ) +{ + S_AL_SrcShutup(); + S_AL_StopBackgroundTrack(); +} + +/* +================= +S_AL_Respatialize +================= +*/ +static +void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ + // Axis[0] = Forward + // Axis[2] = Up + float velocity[] = {0.0f, 0.0f, 0.0f}; + float orientation[] = {axis[0][0], axis[0][1], axis[0][2], + axis[2][0], axis[2][1], axis[2][2]}; + + // Set OpenAL listener paramaters + qalListenerfv(AL_POSITION, (ALfloat *)origin); + qalListenerfv(AL_VELOCITY, velocity); + qalListenerfv(AL_ORIENTATION, orientation); +} + +/* +================= +S_AL_Update +================= +*/ +static +void S_AL_Update( void ) +{ + // Update SFX channels + S_AL_SrcUpdate(); + + // Update streams + S_AL_StreamUpdate(); + S_AL_MusicUpdate(); + + // Doppler + if(s_doppler->modified) + { + s_alDopplerFactor->modified = qtrue; + s_doppler->modified = qfalse; + } + + // Doppler parameters + if(s_alDopplerFactor->modified) + { + if(s_doppler->integer) + qalDopplerFactor(s_alDopplerFactor->value); + else + qalDopplerFactor(0.0f); + s_alDopplerFactor->modified = qfalse; + } + if(s_alDopplerSpeed->modified) + { + qalDopplerVelocity(s_alDopplerSpeed->value); + s_alDopplerSpeed->modified = qfalse; + } + + // Clear the modified flags on the other cvars + s_alGain->modified = qfalse; + s_volume->modified = qfalse; + s_musicVolume->modified = qfalse; + s_alMinDistance->modified = qfalse; + s_alRolloff->modified = qfalse; +} + +/* +================= +S_AL_DisableSounds +================= +*/ +static +void S_AL_DisableSounds( void ) +{ + S_AL_StopAllSounds(); +} + +/* +================= +S_AL_BeginRegistration +================= +*/ +static +void S_AL_BeginRegistration( void ) +{ +} + +/* +================= +S_AL_ClearSoundBuffer +================= +*/ +static +void S_AL_ClearSoundBuffer( void ) +{ +} + +/* +================= +S_AL_SoundList +================= +*/ +static +void S_AL_SoundList( void ) +{ +} + +/* +================= +S_AL_SoundInfo +================= +*/ +static +void S_AL_SoundInfo( void ) +{ + Com_Printf( "OpenAL info:\n" ); + Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) ); + Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) ); + Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) ); + Com_Printf( " Extensions: %s\n", qalGetString( AL_EXTENSIONS ) ); + +} + +/* +================= +S_AL_Shutdown +================= +*/ +static +void S_AL_Shutdown( void ) +{ + // Shut down everything + S_AL_StreamDie( ); + S_AL_StopBackgroundTrack( ); + S_AL_SrcShutdown( ); + S_AL_BufferShutdown( ); + + // Check for Linux shutdown race condition + // FIXME: this will probably not be necessary once OpenAL CVS + // from 11/11/05 is released and prevelant + if( Q_stricmp((const char*)qalGetString( AL_VENDOR ), "J. Valenzuela" ) ) { + qalcMakeContextCurrent( NULL ); + } + + qalcDestroyContext(alContext); + qalcCloseDevice(alDevice); + + QAL_Shutdown(); +} + +#endif + +/* +================= +S_AL_Init +================= +*/ +qboolean S_AL_Init( soundInterface_t *si ) +{ +#if USE_OPENAL + if( !si ) { + return qfalse; + } + + // New console variables + s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE ); + s_alGain = Cvar_Get( "s_alGain", "0.4", CVAR_ARCHIVE ); + s_alSources = Cvar_Get( "s_alSources", "64", CVAR_ARCHIVE ); + s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE ); + s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "2200", CVAR_ARCHIVE ); + s_alMinDistance = Cvar_Get( "s_alMinDistance", "80", CVAR_ARCHIVE ); + s_alRolloff = Cvar_Get( "s_alRolloff", "0.25", CVAR_ARCHIVE ); + + s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE ); + + // Load QAL + if( !QAL_Init( s_alDriver->string ) ) + { + Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string ); + return qfalse; + } + + // Open default device + alDevice = qalcOpenDevice( NULL ); + if( !alDevice ) + { + QAL_Shutdown( ); + Com_Printf( "Failed to open OpenAL device.\n" ); + return qfalse; + } + + // Create OpenAL context + alContext = qalcCreateContext( alDevice, NULL ); + if( !alContext ) + { + QAL_Shutdown( ); + qalcCloseDevice( alDevice ); + Com_Printf( "Failed to create OpenAL context.\n" ); + return qfalse; + } + qalcMakeContextCurrent( alContext ); + + // Initialize sources, buffers, music + S_AL_BufferInit( ); + S_AL_SrcInit( ); + + // Set up OpenAL parameters (doppler, etc) + qalDopplerFactor( s_alDopplerFactor->value ); + qalDopplerVelocity( s_alDopplerSpeed->value ); + + si->Shutdown = S_AL_Shutdown; + si->StartSound = S_AL_StartSound; + si->StartLocalSound = S_AL_StartLocalSound; + si->StartBackgroundTrack = S_AL_StartBackgroundTrack; + si->StopBackgroundTrack = S_AL_StopBackgroundTrack; + si->RawSamples = S_AL_RawSamples; + si->StopAllSounds = S_AL_StopAllSounds; + si->ClearLoopingSounds = S_AL_ClearLoopingSounds; + si->AddLoopingSound = S_AL_AddLoopingSound; + si->AddRealLoopingSound = S_AL_AddRealLoopingSound; + si->StopLoopingSound = S_AL_StopLoopingSound; + si->Respatialize = S_AL_Respatialize; + si->UpdateEntityPosition = S_AL_UpdateEntityPosition; + si->Update = S_AL_Update; + si->DisableSounds = S_AL_DisableSounds; + si->BeginRegistration = S_AL_BeginRegistration; + si->RegisterSound = S_AL_RegisterSound; + si->ClearSoundBuffer = S_AL_ClearSoundBuffer; + si->SoundInfo = S_AL_SoundInfo; + si->SoundList = S_AL_SoundList; + + return qtrue; +#else + return qfalse; +#endif +} + |