diff options
author | tma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea> | 2005-11-13 18:58:14 +0000 |
---|---|---|
committer | tma <tma@edf5b092-35ff-0310-97b2-ce42778d08ea> | 2005-11-13 18:58:14 +0000 |
commit | 38baaa2274a458f03f576dea5a6d3cd9e25f8063 (patch) | |
tree | 64500262c0756817893a96b4d6556e6a31d2ce8f /code/client/snd_openal.c | |
parent | c37663e2d7c564e85af9d17afed0ee1dd283bfb5 (diff) | |
download | ioquake3-aero-38baaa2274a458f03f576dea5a6d3cd9e25f8063.tar.gz ioquake3-aero-38baaa2274a458f03f576dea5a6d3cd9e25f8063.zip |
* OpenAL support, from BlackAura aka Stuart Dalton <badcdev@gmail.com>
+ An abstract codec system, simplifying support for new formats
+ Changes versus BlackAura's patch:
o Consolidated the OpenAL parts into one file
o Changed the function naming scheme to more closely resemble Q3
o Changed the interface to fall back on the "base" sound system
if loading OpenAL fails
+ This is enabled on Linux and MinGW for now, but should work on the
other *nixs with appropriate additions to the Makefile
+ NOT enabled on OS X or MSVC Windows builds
+ Probably breaks the Windows build again
* Retabulated sdl_snd.c and made the messages less verbose since
there do not seem to be many having problems with SDL sound now
git-svn-id: svn://svn.icculus.org/quake3/trunk@343 edf5b092-35ff-0310-97b2-ce42778d08ea
Diffstat (limited to 'code/client/snd_openal.c')
-rw-r--r-- | code/client/snd_openal.c | 1567 |
1 files changed, 1567 insertions, 0 deletions
diff --git a/code/client/snd_openal.c b/code/client/snd_openal.c new file mode 100644 index 0000000..16a8bf9 --- /dev/null +++ b/code/client/snd_openal.c @@ -0,0 +1,1567 @@ +/* +=========================================================================== +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 +================= +*/ +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 +================= +*/ +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 used; // Time last used + struct alSfx_t *next; // Next entry in hash list +} alSfx_t; + +static qboolean alBuffersInitialised = qfalse; + +// Sound effect storage, data structures +#define MAX_SFX 4096 +static alSfx_t knownSfx[MAX_SFX]; +static int numSfx; + +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 hash 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 hash 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( "Warning: Using default sound for %s\n", knownSfx[sfx].filename); + knownSfx[sfx].isDefault = qtrue; + knownSfx[sfx].buffer = knownSfx[default_sfx].buffer; +} + +/* +================= +S_AL_BufferEvict + +Doesn't work yet, so if OpenAL reports that you're out of memory, you'll just +get "Catastrophic sound memory exhaustion". Whoops. +================= +*/ +static qboolean S_AL_BufferEvict( void ) +{ + 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) + { + Com_Printf( "Can't load %s\n", knownSfx[sfx].filename); + 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( "Can't create a sound buffer for %s - %s\n", knownSfx[sfx].filename, S_AL_ErrorMsg(error)); + return; + } + + // Fill the buffer + qalGetError(); + 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) + { + qboolean rv = S_AL_BufferEvict(); + if(!rv) + { + S_AL_BufferUseDefault(sfx); + Z_Free(data); + Com_Printf( "Out of memory loading %s\n", knownSfx[sfx].filename); + return; + } + + // Try load it again + qalGetError(); + 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( "Can't fill sound buffer for %s - %s", knownSfx[sfx].filename, S_AL_ErrorMsg(error)); + return; + } + + // Free the memory + Z_Free(data); + + // Woo! + knownSfx[sfx].inMemory = qtrue; +} + +/* +================= +S_AL_BufferUse +================= +*/ +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].used = Com_Milliseconds(); +} + +/* +================= +S_AL_BufferInit +================= +*/ +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_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( "Can't delete sound buffer for %s", knownSfx[sfx].filename); + + knownSfx[sfx].inMemory = qfalse; +} + +/* +================= +S_AL_BufferShutdown +================= +*/ +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 +================= +*/ +sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed ) +{ + sfxHandle_t sfx = S_AL_BufferFind(sample); + + if((s_alPrecache->integer == 1) && (!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx); + knownSfx[sfx].used = Com_Milliseconds(); + + return sfx; +} + +/* +================= +S_AL_BufferGet + +Return's an sfx's buffer +================= +*/ +ALuint S_AL_BufferGet(sfxHandle_t sfx) +{ + return knownSfx[sfx].buffer; +} + + +//=========================================================================== + + +typedef struct src_s +{ + ALuint source; // OpenAL source object + sfxHandle_t sfx; // Sound effect in use + + int lastUse; // 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; + +static int ambientCount = 0; + +typedef struct sentity_s +{ + vec3_t origin; // Object position + + int has_sfx; // Associated sound source + int sfx; + int touched; // Sound present this update? +} sentity_t; + +static sentity_t entityList[MAX_GENTITIES]; + +/* +================= +S_AL_SrcInit +================= +*/ +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_sources 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].source); + 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 +================= +*/ +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("Warning: Source %d is locked\n", i); + + qalSourceStop(srcList[i].source); + qalDeleteSources(1, &srcList[i].source); + } + + 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].lastUse = 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].source, AL_BUFFER, buffer); + qalSourcef(srcList[src].source, AL_PITCH, 1.0f); + qalSourcef(srcList[src].source, AL_GAIN, s_alGain->value * s_volume->value); + qalSourcefv(srcList[src].source, AL_POSITION, null_vector); + qalSourcefv(srcList[src].source, AL_VELOCITY, null_vector); + qalSourcei(srcList[src].source, AL_LOOPING, AL_FALSE); + qalSourcef(srcList[src].source, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(local) + { + qalSourcei(srcList[src].source, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(srcList[src].source, AL_ROLLOFF_FACTOR, 0); + } + else + { + qalSourcei(srcList[src].source, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(srcList[src].source, 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].source); + + // Remove the entity association + if((srcList[src].isLooping) && (srcList[src].entity != -1)) + { + int ent = srcList[src].entity; + entityList[ent].has_sfx = 0; + entityList[ent].sfx = -1; + entityList[ent].touched = qfalse; + } + + // Remove the buffer + qalSourcei(srcList[src].source, AL_BUFFER, 0); + + srcList[src].sfx = 0; + srcList[src].lastUse = 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 +================= +*/ +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].lastUse < weakest_time)) + { + weakest_pri = srcList[i].priority; + weakest_time = srcList[i].lastUse; + weakest = i; + } + } + + // 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; + } + } + + // Do we have an empty one? + if(empty != -1) + 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 +================= +*/ +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; +} + +/* +================= +S_AL_SrcLock + +Locked sources will not be automatically reallocated or managed +================= +*/ +void S_AL_SrcLock(srcHandle_t src) +{ + srcList[src].isLocked = qtrue; +} + +/* +================= +S_AL_SrcUnlock + +Once unlocked, the source may be reallocated again +================= +*/ +void S_AL_SrcUnlock(srcHandle_t src) +{ + srcList[src].isLocked = qfalse; +} + +/* +================= +S_AL_UpdateEntityPosition +================= +*/ +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 +================= +*/ +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].source); +} + +#define POSITION_SCALE 1.0f + +/* +================= +S_AL_StartSound + +Play a one-shot sound effect +================= +*/ +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; + VectorScale(entityList[entnum].origin, POSITION_SCALE, sorigin); + } + else + VectorScale(origin, POSITION_SCALE, sorigin); + qalSourcefv(srcList[src].source, AL_POSITION, sorigin); + + // Start it playing + qalSourcePlay(srcList[src].source); +} + +/* +================= +S_AL_ClearLoopingSounds +================= +*/ +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].touched = qfalse; + } +} + +/* +================= +S_AL_SrcLoop +================= +*/ +static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, + const vec3_t origin, const vec3_t velocity, int entnum) +{ + int src; + qboolean need_to_play = qfalse; + vec3_t sorigin; + + // Do we need to start a new sound playing? + if(!entityList[entnum].has_sfx) + { + // Try to get a channel + ambientCount++; + src = S_AL_SrcAlloc(priority, entnum, -1); + if(src == -1) + return; + need_to_play = qtrue; + } + else if(srcList[entityList[entnum].sfx].sfx != sfx) + { + // Need to restart. Just re-use this channel + src = entityList[entnum].sfx; + S_AL_SrcKill(src); + need_to_play = qtrue; + } + else + src = entityList[entnum].sfx; + + if(need_to_play) + { + // Set up the effect + S_AL_SrcSetup(src, sfx, priority, entnum, -1, qfalse); + qalSourcei(srcList[src].source, AL_LOOPING, AL_TRUE); + srcList[src].isLooping = qtrue; + + // Set up the entity + entityList[entnum].has_sfx = qtrue; + entityList[entnum].sfx = src; + need_to_play = qtrue; + } + + // Set up the position and velocity + VectorScale(entityList[entnum].origin, POSITION_SCALE, sorigin); + qalSourcefv(srcList[src].source, AL_POSITION, sorigin); + qalSourcefv(srcList[src].source, AL_VELOCITY, velocity); + + // Flag it + entityList[entnum].touched = qtrue; + + // Play if need be + if(need_to_play) + qalSourcePlay(srcList[src].source); +} + +/* +================= +S_AL_AddLoopingSound +================= +*/ +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 +================= +*/ +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 +================= +*/ +void S_AL_StopLoopingSound(int entityNum ) +{ + if(entityList[entityNum].has_sfx) + S_AL_SrcKill(entityList[entityNum].sfx); +} + +/* +================= +S_AL_SrcUpdate + +Update state (move things around, manage sources, and so on) +================= +*/ +void S_AL_SrcUpdate( void ) +{ + int i; + int ent; + ALint state; + + for(i = 0; i < srcCount; i++) + { + if(srcList[i].isLocked) + continue; + + if(!srcList[i].isActive) + continue; + + // Check if it's done, and flag it + qalGetSourcei(srcList[i].source, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + S_AL_SrcKill(i); + continue; + } + + // Update source parameters + if((s_alGain->modified)||(s_volume->modified)) + qalSourcef(srcList[i].source, AL_GAIN, s_alGain->value * s_volume->value); + if((s_alRolloff->modified)&&(!srcList[i].local)) + qalSourcef(srcList[i].source, AL_ROLLOFF_FACTOR, s_alRolloff->value); + if(s_alMinDistance->modified) + qalSourcef(srcList[i].source, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + ent = srcList[i].entity; + + // If a looping effect hasn't been touched this frame, kill it + if(srcList[i].isLooping) + { + if(!entityList[ent].touched) + { + ambientCount--; + S_AL_SrcKill(i); + } + continue; + } + + // See if it needs to be moved + if(srcList[i].isTracking) + { + vec3_t sorigin; + VectorScale(entityList[ent].origin, POSITION_SCALE, sorigin); + qalSourcefv(srcList[i].source, AL_POSITION, entityList[ent].origin); + } + } +} + +/* +================= +S_AL_SrcShutup +================= +*/ +void S_AL_SrcShutup( void ) +{ + int i; + for(i = 0; i < srcCount; i++) + S_AL_SrcKill(i); +} + +/* +================= +S_AL_SrcGet +================= +*/ +ALuint S_AL_SrcGet(srcHandle_t src) +{ + return srcList[src].source; +} + + +//=========================================================================== + + +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 +================= +*/ +void S_AL_RawSamples(int samples, int rate, int width, int channels, const byte *data, float volume) +{ + ALuint buffer; + ALuint format = AL_FORMAT_STEREO16; + ALint state; + + // Work out AL 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; + } + + // Create the streamSource if necessary + if(streamSourceHandle == -1) + { + S_AL_AllocateStreamChannel(); + + // Failed? + if(streamSourceHandle == -1) + { + Com_Printf( "Can't allocate streaming streamSource\n"); + return; + } + } + + // Create a buffer, and stuff the data into it + qalGenBuffers(1, &buffer); + qalBufferData(buffer, format, data, (samples * width * channels), rate); + + // Shove the data onto the streamSource + qalSourceQueueBuffers(streamSource, 1, &buffer); + + // Start the streamSource playing if necessary + qalGetSourcei(streamSource, AL_SOURCE_STATE, &state); + + // Volume + qalSourcef (streamSource, AL_GAIN, volume * s_volume->value * s_alGain->value); + + if(!streamPlaying) + { + qalSourcePlay(streamSource); + streamPlaying = qtrue; + } +} + +/* +================= +S_AL_StreamUpdate +================= +*/ +void S_AL_StreamUpdate( void ) +{ + int processed; + ALint state; + + if(streamSourceHandle == -1) + return; + + // Un-queue any buffers, and delete them + qalGetSourcei(streamSource, AL_BUFFERS_PROCESSED, &processed); + if(processed) + { + while(processed--) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSource, 1, &buffer); + qalDeleteBuffers(1, &buffer); + } + } + + // If it's stopped, release the streamSource + qalGetSourcei(streamSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + streamPlaying = qfalse; + qalSourceStop(streamSource); + S_AL_FreeStreamChannel(); + } +} + +/* +================= +S_AL_StreamDie +================= +*/ +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 +================= +*/ +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 +================= +*/ +void S_AL_MusicProcess(ALuint b) +{ + int l; + ALuint format; + + l = S_CodecReadStream(mus_stream, MUSIC_BUFFER_SIZE, decode_buffer); + + 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); + qalBufferData(b, format, decode_buffer, l, mus_stream->info.rate); +} + +/* +================= +S_AL_StartBackgroundTrack +================= +*/ +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); + + // Start playing + qalSourcePlay(musicSource); + + musicPlaying = qtrue; +} + +/* +================= +S_AL_MusicUpdate +================= +*/ +void S_AL_MusicUpdate( void ) +{ + int processed; + ALint state; + + if(!musicPlaying) + return; + + qalGetSourcei(musicSource, AL_BUFFERS_PROCESSED, &processed); + if(processed) + { + while(processed--) + { + ALuint b; + qalSourceUnqueueBuffers(musicSource, 1, &b); + S_AL_MusicProcess(b); + qalSourceQueueBuffers(musicSource, 1, &b); + } + } + + // If it's not still playing, give it a kick + qalGetSourcei(musicSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + Com_DPrintf( "Restarted OpenAL music musicSource\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" +#endif + +/* +================= +S_AL_StopAllSounds +================= +*/ +void S_AL_StopAllSounds( void ) +{ + S_AL_SrcShutup(); + S_AL_StopBackgroundTrack(); +} + +/* +================= +S_AL_Respatialize +================= +*/ +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]}; + vec3_t sorigin; + + // Set OpenAL listener paramaters + VectorScale(origin, POSITION_SCALE, sorigin); + qalListenerfv(AL_POSITION, origin); + qalListenerfv(AL_VELOCITY, velocity); + qalListenerfv(AL_ORIENTATION, orientation); +} + +/* +================= +S_AL_Update +================= +*/ +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 +================= +*/ +void S_AL_DisableSounds( void ) +{ + S_AL_StopAllSounds(); +} + +/* +================= +S_AL_BeginRegistration +================= +*/ +void S_AL_BeginRegistration( void ) +{ +} + +/* +================= +S_AL_ClearSoundBuffer +================= +*/ +void S_AL_ClearSoundBuffer( void ) +{ +} + +/* +================= +S_AL_SoundList +================= +*/ +void S_AL_SoundList( void ) +{ +} + +/* +================= +S_AL_SoundInfo +================= +*/ +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 +================= +*/ +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( 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", "0", 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 +} + |