lilium-voyager/code/client/snd_openal.c
Zack Middleton c2ce1c2f51 Make client for Windows x86_64 use OpenAL64.dll by default
ioquake3.x86_64.exe can't load x86 OpenAL32.dll. Using separate
library names allows shipping OpenAL for both architectures.

Though since the dll name is saved in the config file, using both
clients on the same computer will cause one client arch to always
try to load the wrong OpenAL dll and then fallback to the correct
default OpenAL dll. I guess it could be fixed by using separate
cvar names for s_alDriver.
2017-09-04 20:34:55 -05:00

2733 lines
62 KiB
C

/*
===========================================================================
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"
#ifdef 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_alMaxDistance;
cvar_t *s_alRolloff;
cvar_t *s_alGraceDistance;
cvar_t *s_alDriver;
cvar_t *s_alDevice;
cvar_t *s_alInputDevice;
cvar_t *s_alAvailableDevices;
cvar_t *s_alAvailableInputDevices;
static qboolean enumeration_ext = qfalse;
static qboolean enumeration_all_ext = qfalse;
#ifdef USE_VOIP
static qboolean capture_ext = qfalse;
#endif
/*
=================
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";
}
}
/*
=================
S_AL_ClearError
=================
*/
static void S_AL_ClearError( qboolean quiet )
{
int error = qalGetError();
if( quiet )
return;
if(error != AL_NO_ERROR)
{
Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n",
S_AL_ErrorMsg(error));
}
}
//===========================================================================
typedef struct alSfx_s
{
char filename[MAX_QPATH];
ALuint buffer; // OpenAL buffer
snd_info_t info; // information for this sound like rate, sample count..
qboolean isDefault; // Couldn't be loaded - use default FX
qboolean isDefaultChecked; // Sound has been check if it isDefault
qboolean inMemory; // Sound is stored in memory
qboolean isLocked; // Sound is locked (can not be unloaded)
int lastUsedTime; // Time last used
int loopCnt; // number of loops using this sfx
int loopActiveCnt; // number of playing loops using this sfx
int masterLoopSrc; // All other sources looping this buffer are synced to this master src
} alSfx_t;
static qboolean alBuffersInitialised = qfalse;
// Sound effect storage, data structures
#define MAX_SFX 4096
static alSfx_t knownSfx[MAX_SFX];
static sfxHandle_t 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')
{
if(i >= numSfx)
numSfx = i + 1;
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;
if ( !filename ) {
Com_Error( ERR_FATAL, "Sound name is NULL" );
}
if ( !filename[0] ) {
Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is empty\n" );
return 0;
}
if ( strlen( filename ) >= MAX_QPATH ) {
Com_Printf( S_COLOR_YELLOW "WARNING: Sound name is too long: %s\n", filename );
return 0;
}
if ( filename[0] == '*' ) {
Com_Printf( S_COLOR_YELLOW "WARNING: Tried to load player sound directly: %s\n", filename );
return 0;
}
for(i = 0; i < numSfx; 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));
ptr->masterLoopSrc = -1;
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", 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)
{
if(knownSfx[sfx].filename[0] == '\0')
return;
if(!knownSfx[sfx].inMemory)
return;
// Delete it
S_AL_ClearError( qfalse );
qalDeleteBuffers(1, &knownSfx[sfx].buffer);
if(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 < numSfx; 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_GenBuffers
=================
*/
static qboolean S_AL_GenBuffers(ALsizei numBuffers, ALuint *buffers, const char *name)
{
ALenum error;
S_AL_ClearError( qfalse );
qalGenBuffers( numBuffers, buffers );
error = qalGetError();
// If we ran out of buffers, start evicting the least recently used sounds
while( error == AL_INVALID_VALUE )
{
if( !S_AL_BufferEvict( ) )
{
Com_Printf( S_COLOR_RED "ERROR: Out of audio buffers\n");
return qfalse;
}
// Try again
S_AL_ClearError( qfalse );
qalGenBuffers( numBuffers, buffers );
error = qalGetError();
}
if( error != AL_NO_ERROR )
{
Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n",
name, S_AL_ErrorMsg(error));
return qfalse;
}
return qtrue;
}
/*
=================
S_AL_BufferLoad
=================
*/
static void S_AL_BufferLoad(sfxHandle_t sfx, qboolean cache)
{
ALenum error;
ALuint format;
void *data;
snd_info_t info;
alSfx_t *curSfx = &knownSfx[sfx];
// Nothing?
if(curSfx->filename[0] == '\0')
return;
// Already done?
if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked))
return;
// Try to load
data = S_CodecLoad(curSfx->filename, &info);
if(!data)
{
S_AL_BufferUseDefault(sfx);
return;
}
curSfx->isDefaultChecked = qtrue;
if (!cache)
{
// Don't create AL cache
Hunk_FreeTempMemory(data);
return;
}
format = S_AL_Format(info.width, info.channels);
// Create a buffer
if (!S_AL_GenBuffers(1, &curSfx->buffer, curSfx->filename))
{
S_AL_BufferUseDefault(sfx);
Hunk_FreeTempMemory(data);
return;
}
// Fill the buffer
if( info.size == 0 )
{
// We have no data to buffer, so buffer silence
byte dummyData[ 2 ] = { 0 };
qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050);
}
else
qalBufferData(curSfx->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( ) )
{
qalDeleteBuffers(1, &curSfx->buffer);
S_AL_BufferUseDefault(sfx);
Hunk_FreeTempMemory(data);
Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename);
return;
}
// Try load it again
qalBufferData(curSfx->buffer, format, data, info.size, info.rate);
error = qalGetError();
}
// Some other error condition
if(error != AL_NO_ERROR)
{
qalDeleteBuffers(1, &curSfx->buffer);
S_AL_BufferUseDefault(sfx);
Hunk_FreeTempMemory(data);
Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n",
curSfx->filename, S_AL_ErrorMsg(error));
return;
}
curSfx->info = info;
// Free the memory
Hunk_FreeTempMemory(data);
// Woo!
curSfx->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, qtrue);
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 < numSfx; i++)
S_AL_BufferUnload(i);
// Clear the tables
numSfx = 0;
// 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((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault))
S_AL_BufferLoad(sfx, s_alPrecache->integer);
knownSfx[sfx].lastUsedTime = Com_Milliseconds();
if (knownSfx[sfx].isDefault) {
return 0;
}
return sfx;
}
/*
=================
S_AL_BufferGet
Return's a 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)
qboolean isActive; // Is this source currently in use?
qboolean isPlaying; // Is this source currently playing, or stopped?
qboolean isLocked; // This is locked (un-allocatable)
qboolean isLooping; // Is this a looping effect (attached to an entity)
qboolean isTracking; // Is this object tracking its owner
qboolean isStream; // Is this source a stream
float curGain; // gain employed if source is within maxdistance.
float scaleGain; // Last gain value for this source. 0 if muted.
float lastTimePos; // On stopped loops, the last position in the buffer
int lastSampleTime; // Time when this was stopped
vec3_t loopSpeakerPos; // Origin of the loop speaker
qboolean local; // Is this local (relative to the cam)
} src_t;
#ifdef __APPLE__
#define MAX_SRC 64
#else
#define MAX_SRC 128
#endif
static src_t srcList[MAX_SRC];
static int srcCount = 0;
static int srcActiveCnt = 0;
static qboolean alSourcesInitialised = qfalse;
static int lastListenerNumber = -1;
static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f };
typedef struct sentity_s
{
vec3_t origin;
qboolean 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_SanitiseVector
=================
*/
#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__)
static void _S_AL_SanitiseVector( vec3_t v, int line )
{
if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) )
{
Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components "
"being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line );
VectorClear( v );
}
}
/*
=================
S_AL_Gain
Set gain to 0 if muted, otherwise set it to given value.
=================
*/
static void S_AL_Gain(ALuint source, float gainval)
{
if(s_muted->integer)
qalSourcef(source, AL_GAIN, 0.0f);
else
qalSourcef(source, AL_GAIN, gainval);
}
/*
=================
S_AL_ScaleGain
Adapt the gain if necessary to get a quicker fadeout when the source is too far away.
=================
*/
static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin)
{
float distance;
if(!chksrc->local)
distance = Distance(origin, lastListenerOrigin);
// If we exceed a certain distance, scale the gain linearly until the sound
// vanishes into nothingness.
if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0)
{
float scaleFactor;
if(distance >= s_alGraceDistance->value)
scaleFactor = 0.0f;
else
scaleFactor = 1.0f - distance / s_alGraceDistance->value;
scaleFactor *= chksrc->curGain;
if(chksrc->scaleGain != scaleFactor)
{
chksrc->scaleGain = scaleFactor;
S_AL_Gain(chksrc->alSource, chksrc->scaleGain);
}
}
else if(chksrc->scaleGain != chksrc->curGain)
{
chksrc->scaleGain = chksrc->curGain;
S_AL_Gain(chksrc->alSource, chksrc->scaleGain);
}
}
/*
=================
S_AL_HearingThroughEntity
Also see S_Base_HearingThroughEntity
=================
*/
static qboolean S_AL_HearingThroughEntity( int entityNum )
{
float distanceSq;
if( lastListenerNumber == entityNum )
{
// This is an outrageous hack to detect
// whether or not the player is rendering in third person or not. We can't
// ask the renderer because the renderer has no notion of entities and we
// can't ask cgame since that would involve changing the API and hence mod
// compatibility. I don't think there is any way around this, but I'll leave
// the FIXME just in case anyone has a bright idea.
distanceSq = DistanceSquared(
entityList[ entityNum ].origin,
lastListenerOrigin );
if( distanceSq > THIRD_PERSON_THRESHOLD_SQ )
return qfalse; //we're the player, but third person
else
return qtrue; //we're the player
}
else
return qfalse; //not the player
}
/*
=================
S_AL_SrcInit
=================
*/
static
qboolean S_AL_SrcInit( void )
{
int i;
int limit;
// Clear the sources data structure
memset(srcList, 0, sizeof(srcList));
srcCount = 0;
srcActiveCnt = 0;
// Cap s_alSources to MAX_SRC
limit = s_alSources->integer;
if(limit > MAX_SRC)
limit = MAX_SRC;
else if(limit < 16)
limit = 16;
S_AL_ClearError( qfalse );
// Allocate as many sources as possible
for(i = 0; i < limit; i++)
{
qalGenSources(1, &srcList[i].alSource);
if(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;
src_t *curSource;
if(!alSourcesInitialised)
return;
// Destroy all the sources
for(i = 0; i < srcCount; i++)
{
curSource = &srcList[i];
if(curSource->isLocked)
Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i);
if(curSource->entity > 0)
entityList[curSource->entity].srcAllocated = qfalse;
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)
{
src_t *curSource;
// Set up src struct
curSource = &srcList[src];
curSource->lastUsedTime = Sys_Milliseconds();
curSource->sfx = sfx;
curSource->priority = priority;
curSource->entity = entity;
curSource->channel = channel;
curSource->isPlaying = qfalse;
curSource->isLocked = qfalse;
curSource->isLooping = qfalse;
curSource->isTracking = qfalse;
curSource->isStream = qfalse;
curSource->curGain = s_alGain->value * s_volume->value;
curSource->scaleGain = curSource->curGain;
curSource->local = local;
// Set up OpenAL source
if(sfx >= 0)
{
// Mark the SFX as used, and grab the raw AL buffer
S_AL_BufferUse(sfx);
qalSourcei(curSource->alSource, AL_BUFFER, S_AL_BufferGet(sfx));
}
qalSourcef(curSource->alSource, AL_PITCH, 1.0f);
S_AL_Gain(curSource->alSource, curSource->curGain);
qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin);
qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin);
qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE);
qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value);
if(local)
{
qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f);
}
else
{
qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE);
qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
}
}
/*
=================
S_AL_SaveLoopPos
Remove given source as loop master if it is the master and hand off master status to another source in this case.
=================
*/
static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource)
{
int error;
S_AL_ClearError(qfalse);
qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos);
if((error = qalGetError()) != AL_NO_ERROR)
{
// Old OpenAL implementations don't support AL_SEC_OFFSET
if(error != AL_INVALID_ENUM)
{
Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n",
alSource, S_AL_ErrorMsg(error));
}
dest->lastTimePos = -1;
}
else
dest->lastSampleTime = Sys_Milliseconds();
}
/*
=================
S_AL_NewLoopMaster
Remove given source as loop master if it is the master and hand off master status to another source in this case.
=================
*/
static void S_AL_NewLoopMaster(src_t *rmSource, qboolean iskilled)
{
int index;
src_t *curSource = NULL;
alSfx_t *curSfx;
curSfx = &knownSfx[rmSource->sfx];
if(rmSource->isPlaying)
curSfx->loopActiveCnt--;
if(iskilled)
curSfx->loopCnt--;
if(curSfx->loopCnt)
{
if(rmSource->priority == SRCPRI_ENTITY)
{
if(!iskilled && rmSource->isPlaying)
{
// only sync ambient loops...
// It makes more sense to have sounds for weapons/projectiles unsynced
S_AL_SaveLoopPos(rmSource, rmSource->alSource);
}
}
else if(rmSource == &srcList[curSfx->masterLoopSrc])
{
int firstInactive = -1;
// Only if rmSource was the master and if there are still playing loops for
// this sound will we need to find a new master.
if(iskilled || curSfx->loopActiveCnt)
{
for(index = 0; index < srcCount; index++)
{
curSource = &srcList[index];
if(curSource->sfx == rmSource->sfx && curSource != rmSource &&
curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT)
{
if(curSource->isPlaying)
{
curSfx->masterLoopSrc = index;
break;
}
else if(firstInactive < 0)
firstInactive = index;
}
}
}
if(!curSfx->loopActiveCnt)
{
if(firstInactive < 0)
{
if(iskilled)
{
curSfx->masterLoopSrc = -1;
return;
}
else
curSource = rmSource;
}
else
curSource = &srcList[firstInactive];
if(rmSource->isPlaying)
{
// this was the last not stopped source, save last sample position + time
S_AL_SaveLoopPos(curSource, rmSource->alSource);
}
else
{
// second case: all loops using this sound have stopped due to listener being of of range,
// and now the inactive master gets deleted. Just move over the soundpos settings to the
// new master.
curSource->lastTimePos = rmSource->lastTimePos;
curSource->lastSampleTime = rmSource->lastSampleTime;
}
}
}
}
else
curSfx->masterLoopSrc = -1;
}
/*
=================
S_AL_SrcKill
=================
*/
static void S_AL_SrcKill(srcHandle_t src)
{
src_t *curSource = &srcList[src];
// I'm not touching it. Unlock it first.
if(curSource->isLocked)
return;
// Remove the entity association and loop master status
if(curSource->isLooping)
{
curSource->isLooping = qfalse;
if(curSource->entity != -1)
{
sentity_t *curEnt = &entityList[curSource->entity];
curEnt->srcAllocated = qfalse;
curEnt->srcIndex = -1;
curEnt->loopAddedThisFrame = qfalse;
curEnt->startLoopingSound = qfalse;
}
S_AL_NewLoopMaster(curSource, qtrue);
}
// Stop it if it's playing
if(curSource->isPlaying)
{
qalSourceStop(curSource->alSource);
curSource->isPlaying = qfalse;
}
// Detach any buffers
qalSourcei(curSource->alSource, AL_BUFFER, 0);
curSource->sfx = 0;
curSource->lastUsedTime = 0;
curSource->priority = 0;
curSource->entity = -1;
curSource->channel = -1;
if(curSource->isActive)
{
curSource->isActive = qfalse;
srcActiveCnt--;
}
curSource->isLocked = qfalse;
curSource->isTracking = qfalse;
curSource->local = 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;
float weakest_gain = 1000.0;
qboolean weakest_isplaying = qtrue;
int weakest_numloops = 0;
src_t *curSource;
for(i = 0; i < srcCount; i++)
{
curSource = &srcList[i];
// If it's locked, we aren't even going to look at it
if(curSource->isLocked)
continue;
// Is it empty or not?
if(!curSource->isActive)
{
empty = i;
break;
}
if(curSource->isPlaying)
{
if(weakest_isplaying && curSource->priority < priority &&
(curSource->priority < weakest_pri ||
(!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time))))
{
// If it has lower priority, is fainter or older, flag it as weak
// the last two values are only compared if it's not a looping sound, because we want to prevent two
// loops (loops are added EVERY frame) fighting for a slot
weakest_pri = curSource->priority;
weakest_time = curSource->lastUsedTime;
weakest_gain = curSource->scaleGain;
weakest = i;
}
}
else
{
weakest_isplaying = qfalse;
if(weakest < 0 ||
knownSfx[curSource->sfx].loopCnt > weakest_numloops ||
curSource->priority < weakest_pri ||
curSource->lastUsedTime < weakest_time)
{
// Sources currently not playing of course have lowest priority
// also try to always keep at least one loop master for every loop sound
weakest_pri = curSource->priority;
weakest_time = curSource->lastUsedTime;
weakest_numloops = knownSfx[curSource->sfx].loopCnt;
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((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0))
{
S_AL_SrcKill(i);
return i;
}
#endif
}
if(empty == -1)
empty = weakest;
if(empty >= 0)
{
S_AL_SrcKill(empty);
srcList[empty].isActive = qtrue;
srcActiveCnt++;
}
return empty;
}
/*
=================
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 )
{
vec3_t sanOrigin;
VectorCopy( origin, sanOrigin );
S_AL_SanitiseVector( sanOrigin );
if ( entityNum < 0 || entityNum >= MAX_GENTITIES )
Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum );
VectorCopy( sanOrigin, entityList[entityNum].origin );
}
/*
=================
S_AL_CheckInput
Check whether input values from mods are out of range.
Necessary for i.g. Western Quake3 mod which is buggy.
=================
*/
static qboolean S_AL_CheckInput(int entityNum, sfxHandle_t sfx)
{
if (entityNum < 0 || entityNum >= MAX_GENTITIES)
Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum);
if (sfx < 0 || sfx >= numSfx)
{
Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx);
return qtrue;
}
return qfalse;
}
/*
=================
S_AL_StartLocalSound
Play a local (non-spatialized) sound effect
=================
*/
static
void S_AL_StartLocalSound(sfxHandle_t sfx, int channel)
{
srcHandle_t src;
if(S_AL_CheckInput(0, sfx))
return;
// Try to grab a source
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
srcList[src].isPlaying = qtrue;
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;
srcHandle_t src;
src_t *curSource;
if(origin)
{
if(S_AL_CheckInput(0, sfx))
return;
VectorCopy(origin, sorigin);
}
else
{
if(S_AL_CheckInput(entnum, sfx))
return;
if(S_AL_HearingThroughEntity(entnum))
{
S_AL_StartLocalSound(sfx, entchannel);
return;
}
VectorCopy(entityList[entnum].origin, sorigin);
}
S_AL_SanitiseVector(sorigin);
if((srcActiveCnt > 5 * srcCount / 3) &&
(DistanceSquared(sorigin, lastListenerOrigin) >=
(s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value)))
{
// We're getting tight on sources and source is not within hearing distance so don't add it
return;
}
// Try to grab a source
src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel);
if(src == -1)
return;
S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, qfalse);
curSource = &srcList[src];
if(!origin)
curSource->isTracking = qtrue;
qalSourcefv(curSource->alSource, AL_POSITION, sorigin );
S_AL_ScaleGain(curSource, sorigin);
// Start it playing
curSource->isPlaying = qtrue;
qalSourcePlay(curSource->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 ];
src_t *curSource;
vec3_t sorigin, svelocity;
if(S_AL_CheckInput(entityNum, sfx))
return;
// 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_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source "
"for loop sfx %d on entity %d\n", sfx, entityNum );
return;
}
curSource = &srcList[src];
sent->startLoopingSound = qtrue;
curSource->lastTimePos = -1.0;
curSource->lastSampleTime = Sys_Milliseconds();
}
else
{
src = sent->srcIndex;
curSource = &srcList[src];
}
sent->srcAllocated = qtrue;
sent->srcIndex = src;
sent->loopPriority = priority;
sent->loopSfx = sfx;
// If this is not set then the looping sound is stopped.
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
curSource->entity = entityNum;
curSource->isLooping = qtrue;
if( S_AL_HearingThroughEntity( entityNum ) )
{
curSource->local = qtrue;
VectorClear(sorigin);
qalSourcefv(curSource->alSource, AL_POSITION, sorigin);
qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin);
}
else
{
curSource->local = qfalse;
if(origin)
VectorCopy(origin, sorigin);
else
VectorCopy(sent->origin, sorigin);
S_AL_SanitiseVector(sorigin);
VectorCopy(sorigin, curSource->loopSpeakerPos);
if(velocity)
{
VectorCopy(velocity, svelocity);
S_AL_SanitiseVector(svelocity);
}
else
VectorClear(svelocity);
qalSourcefv(curSource->alSource, AL_POSITION, (ALfloat *) sorigin);
qalSourcefv(curSource->alSource, AL_VELOCITY, (ALfloat *) svelocity);
}
}
/*
=================
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_ENTITY, 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_AMBIENT, 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;
src_t *curSource;
for(i = 0; i < srcCount; i++)
{
entityNum = srcList[i].entity;
curSource = &srcList[i];
if(curSource->isLocked)
continue;
if(!curSource->isActive)
continue;
// Update source parameters
if((s_alGain->modified) || (s_volume->modified))
curSource->curGain = s_alGain->value * s_volume->value;
if((s_alRolloff->modified) && (!curSource->local))
qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
if(s_alMinDistance->modified)
qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value);
if(curSource->isLooping)
{
sentity_t *sent = &entityList[ entityNum ];
// If a looping effect hasn't been touched this frame, pause or kill it
if(sent->loopAddedThisFrame)
{
alSfx_t *curSfx;
// The sound has changed without an intervening removal
if(curSource->isActive && !sent->startLoopingSound &&
curSource->sfx != sent->loopSfx)
{
S_AL_NewLoopMaster(curSource, qtrue);
curSource->isPlaying = qfalse;
qalSourceStop(curSource->alSource);
qalSourcei(curSource->alSource, AL_BUFFER, 0);
sent->startLoopingSound = qtrue;
}
// The sound hasn't been started yet
if(sent->startLoopingSound)
{
S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority,
entityNum, -1, curSource->local);
curSource->isLooping = qtrue;
knownSfx[curSource->sfx].loopCnt++;
sent->startLoopingSound = qfalse;
}
curSfx = &knownSfx[curSource->sfx];
S_AL_ScaleGain(curSource, curSource->loopSpeakerPos);
if(!curSource->scaleGain)
{
if(curSource->isPlaying)
{
// Sound is mute, stop playback until we are in range again
S_AL_NewLoopMaster(curSource, qfalse);
qalSourceStop(curSource->alSource);
curSource->isPlaying = qfalse;
}
else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0)
curSfx->masterLoopSrc = i;
continue;
}
if(!curSource->isPlaying)
{
qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE);
curSource->isPlaying = qtrue;
qalSourcePlay(curSource->alSource);
if(curSource->priority == SRCPRI_AMBIENT)
{
// If there are other ambient looping sources with the same sound,
// make sure the sound of these sources are in sync.
if(curSfx->loopActiveCnt)
{
int offset, error;
// we already have a master loop playing, get buffer position.
S_AL_ClearError(qfalse);
qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset);
if((error = qalGetError()) != AL_NO_ERROR)
{
if(error != AL_INVALID_ENUM)
{
Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: "
"%s\n", i, S_AL_ErrorMsg(error));
}
}
else
qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset);
}
else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0)
{
float secofs;
src_t *master = &srcList[curSfx->masterLoopSrc];
// This loop sound used to be played, but all sources are stopped. Use last sample position/time
// to calculate offset so the player thinks the sources continued playing while they were inaudible.
if(master->lastTimePos >= 0)
{
secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f;
secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate);
qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs);
}
// I be the master now
curSfx->masterLoopSrc = i;
}
else
curSfx->masterLoopSrc = i;
}
else if(curSource->lastTimePos >= 0)
{
float secofs;
// For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped
secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f;
secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate);
qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs);
}
curSfx->loopActiveCnt++;
}
// Update locality
if(curSource->local)
{
qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE);
qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f);
}
else
{
qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE);
qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value);
}
}
else if(curSource->priority == SRCPRI_AMBIENT)
{
if(curSource->isPlaying)
{
S_AL_NewLoopMaster(curSource, qfalse);
qalSourceStop(curSource->alSource);
curSource->isPlaying = qfalse;
}
}
else
S_AL_SrcKill(i);
continue;
}
if(!curSource->isStream)
{
// Check if it's done, and flag it
qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state);
if(state == AL_STOPPED)
{
curSource->isPlaying = qfalse;
S_AL_SrcKill(i);
continue;
}
}
// Query relativity of source, don't move if it's true
qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state);
// See if it needs to be moved
if(curSource->isTracking && !state)
{
qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin);
S_AL_ScaleGain(curSource, entityList[entityNum].origin);
}
}
}
/*
=================
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;
}
//===========================================================================
// Q3A cinematics use up to 12 buffers at once
#define MAX_STREAM_BUFFERS 20
static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS];
static qboolean streamPlaying[MAX_RAW_STREAMS];
static ALuint streamSources[MAX_RAW_STREAMS];
static ALuint streamBuffers[MAX_RAW_STREAMS][MAX_STREAM_BUFFERS];
static int streamNumBuffers[MAX_RAW_STREAMS];
static int streamBufIndex[MAX_RAW_STREAMS];
/*
=================
S_AL_AllocateStreamChannel
=================
*/
static void S_AL_AllocateStreamChannel(int stream, int entityNum)
{
srcHandle_t cursrc;
ALuint alsrc;
if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
return;
if(entityNum >= 0)
{
// This is a stream that tracks an entity
// Allocate a streamSource at normal priority
cursrc = S_AL_SrcAlloc(SRCPRI_ENTITY, entityNum, 0);
if(cursrc < 0)
return;
S_AL_SrcSetup(cursrc, -1, SRCPRI_ENTITY, entityNum, 0, qfalse);
alsrc = S_AL_SrcGet(cursrc);
srcList[cursrc].isTracking = qtrue;
srcList[cursrc].isStream = qtrue;
}
else
{
// Unspatialized stream source
// Allocate a streamSource at high priority
cursrc = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0);
if(cursrc < 0)
return;
alsrc = S_AL_SrcGet(cursrc);
// Lock the streamSource so nobody else can use it, and get the raw streamSource
S_AL_SrcLock(cursrc);
// make sure that after unmuting the S_AL_Gain in S_Update() does not turn
// volume up prematurely for this source
srcList[cursrc].scaleGain = 0.0f;
// Set some streamSource parameters
qalSourcei (alsrc, AL_BUFFER, 0 );
qalSourcei (alsrc, AL_LOOPING, AL_FALSE );
qalSource3f(alsrc, AL_POSITION, 0.0, 0.0, 0.0);
qalSource3f(alsrc, AL_VELOCITY, 0.0, 0.0, 0.0);
qalSource3f(alsrc, AL_DIRECTION, 0.0, 0.0, 0.0);
qalSourcef (alsrc, AL_ROLLOFF_FACTOR, 0.0 );
qalSourcei (alsrc, AL_SOURCE_RELATIVE, AL_TRUE );
}
streamSourceHandles[stream] = cursrc;
streamSources[stream] = alsrc;
streamNumBuffers[stream] = 0;
streamBufIndex[stream] = 0;
}
/*
=================
S_AL_FreeStreamChannel
=================
*/
static void S_AL_FreeStreamChannel( int stream )
{
if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
return;
// Detach any buffers
qalSourcei(streamSources[stream], AL_BUFFER, 0);
// Delete the buffers
if (streamNumBuffers[stream] > 0) {
qalDeleteBuffers(streamNumBuffers[stream], streamBuffers[stream]);
streamNumBuffers[stream] = 0;
}
// Release the output streamSource
S_AL_SrcUnlock(streamSourceHandles[stream]);
S_AL_SrcKill(streamSourceHandles[stream]);
streamSources[stream] = 0;
streamSourceHandles[stream] = -1;
}
/*
=================
S_AL_RawSamples
=================
*/
static
void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum)
{
int numBuffers;
ALuint buffer;
ALuint format;
if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
return;
format = S_AL_Format( width, channels );
// Create the streamSource if necessary
if(streamSourceHandles[stream] == -1)
{
S_AL_AllocateStreamChannel(stream, entityNum);
// Failed?
if(streamSourceHandles[stream] == -1)
{
Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n");
return;
}
}
qalGetSourcei(streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers);
if (numBuffers == MAX_STREAM_BUFFERS)
{
Com_DPrintf(S_COLOR_RED"WARNING: Steam dropping raw samples, reached MAX_STREAM_BUFFERS\n");
return;
}
// Allocate a new AL buffer if needed
if (numBuffers == streamNumBuffers[stream])
{
ALuint oldBuffers[MAX_STREAM_BUFFERS];
int i;
if (!S_AL_GenBuffers(1, &buffer, "stream"))
return;
Com_Memcpy(oldBuffers, &streamBuffers[stream], sizeof (oldBuffers));
// Reorder buffer array in order of oldest to newest
for ( i = 0; i < streamNumBuffers[stream]; ++i )
streamBuffers[stream][i] = oldBuffers[(streamBufIndex[stream] + i) % streamNumBuffers[stream]];
// Add the new buffer to end
streamBuffers[stream][streamNumBuffers[stream]] = buffer;
streamBufIndex[stream] = streamNumBuffers[stream];
streamNumBuffers[stream]++;
}
// Select next buffer in loop
buffer = streamBuffers[stream][ streamBufIndex[stream] ];
streamBufIndex[stream] = (streamBufIndex[stream] + 1) % streamNumBuffers[stream];
// Fill buffer
qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate);
// Shove the data onto the streamSource
qalSourceQueueBuffers(streamSources[stream], 1, &buffer);
if(entityNum < 0)
{
// Volume
S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value);
}
// Start stream
if(!streamPlaying[stream])
{
qalSourcePlay( streamSources[stream] );
streamPlaying[stream] = qtrue;
}
}
/*
=================
S_AL_StreamUpdate
=================
*/
static
void S_AL_StreamUpdate( int stream )
{
int numBuffers;
ALint state;
if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
return;
if(streamSourceHandles[stream] == -1)
return;
// Un-queue any buffers
qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers );
while( numBuffers-- )
{
ALuint buffer;
qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer);
}
// Start the streamSource playing if necessary
qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers );
qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state);
if(state == AL_STOPPED)
{
streamPlaying[stream] = qfalse;
// If there are no buffers queued up, release the streamSource
if( !numBuffers )
S_AL_FreeStreamChannel( stream );
}
if( !streamPlaying[stream] && numBuffers )
{
qalSourcePlay( streamSources[stream] );
streamPlaying[stream] = qtrue;
}
}
/*
=================
S_AL_StreamDie
=================
*/
static
void S_AL_StreamDie( int stream )
{
if ((stream < 0) || (stream >= MAX_RAW_STREAMS))
return;
if(streamSourceHandles[stream] == -1)
return;
streamPlaying[stream] = qfalse;
qalSourceStop(streamSources[stream]);
S_AL_FreeStreamChannel(stream);
}
//===========================================================================
#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 snd_stream_t *intro_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);
// make sure that after unmuting the S_AL_Gain in S_Update() does not turn
// volume up prematurely for this source
srcList[musicSourceHandle].scaleGain = 0.0f;
// 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);
S_AL_SrcKill(musicSourceHandle);
musicSource = 0;
musicSourceHandle = -1;
}
/*
=================
S_AL_CloseMusicFiles
=================
*/
static void S_AL_CloseMusicFiles(void)
{
if(intro_stream)
{
S_CodecCloseStream(intro_stream);
intro_stream = NULL;
}
if(mus_stream)
{
S_CodecCloseStream(mus_stream);
mus_stream = NULL;
}
}
/*
=================
S_AL_StopBackgroundTrack
=================
*/
static
void S_AL_StopBackgroundTrack( void )
{
if(!musicPlaying)
return;
// Stop playing
qalSourceStop(musicSource);
// Detach any buffers
qalSourcei(musicSource, AL_BUFFER, 0);
// Delete the buffers
qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers);
// Free the musicSource
S_AL_MusicSourceFree();
// Unload the stream
S_AL_CloseMusicFiles();
musicPlaying = qfalse;
}
/*
=================
S_AL_MusicProcess
=================
*/
static
void S_AL_MusicProcess(ALuint b)
{
ALenum error;
int l;
ALuint format;
snd_stream_t *curstream;
S_AL_ClearError( qfalse );
if(intro_stream)
curstream = intro_stream;
else
curstream = mus_stream;
if(!curstream)
return;
l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer);
// Run out data to read, start at the beginning again
if(l == 0)
{
S_CodecCloseStream(curstream);
// the intro stream just finished playing so we don't need to reopen
// the music stream.
if(intro_stream)
intro_stream = NULL;
else
mus_stream = S_CodecOpenStream(s_backgroundLoop);
curstream = mus_stream;
if(!curstream)
{
S_AL_StopBackgroundTrack();
return;
}
l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer);
}
format = S_AL_Format(curstream->info.width, curstream->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, curstream->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;
qboolean issame;
// Stop any existing music that might be playing
S_AL_StopBackgroundTrack();
if((!intro || !*intro) && (!loop || !*loop))
return;
// Allocate a musicSource
S_AL_MusicSourceGet();
if(musicSourceHandle == -1)
return;
if (!loop || !*loop)
{
loop = intro;
issame = qtrue;
}
else if(intro && *intro && !strcmp(intro, loop))
issame = qtrue;
else
issame = qfalse;
// Copy the loop over
Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) );
if(!issame)
{
// Open the intro and don't mind whether it succeeds.
// The important part is the loop.
intro_stream = S_CodecOpenStream(intro);
}
else
intro_stream = NULL;
mus_stream = S_CodecOpenStream(s_backgroundLoop);
if(!mus_stream)
{
S_AL_CloseMusicFiles();
S_AL_MusicSourceFree();
return;
}
// Generate the musicBuffers
if (!S_AL_GenBuffers(NUM_MUSIC_BUFFERS, musicBuffers, "music"))
return;
// 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
S_AL_Gain(musicSource, 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
S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value);
}
//===========================================================================
// Local state variables
static ALCdevice *alDevice;
static ALCcontext *alContext;
#ifdef USE_VOIP
static ALCdevice *alCaptureDevice;
static cvar_t *s_alCapture;
#endif
#if defined(_WIN64)
#define ALDRIVER_DEFAULT "OpenAL64.dll"
#elif defined(_WIN32)
#define ALDRIVER_DEFAULT "OpenAL32.dll"
#elif defined(__APPLE__)
#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL"
#elif defined(__OpenBSD__)
#define ALDRIVER_DEFAULT "libopenal.so"
#else
#define ALDRIVER_DEFAULT "libopenal.so.1"
#endif
/*
=================
S_AL_StopAllSounds
=================
*/
static
void S_AL_StopAllSounds( void )
{
int i;
S_AL_SrcShutup();
S_AL_StopBackgroundTrack();
for (i = 0; i < MAX_RAW_STREAMS; i++)
S_AL_StreamDie(i);
}
/*
=================
S_AL_Respatialize
=================
*/
static
void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater )
{
float orientation[6];
vec3_t sorigin;
VectorCopy( origin, sorigin );
S_AL_SanitiseVector( sorigin );
S_AL_SanitiseVector( axis[ 0 ] );
S_AL_SanitiseVector( axis[ 1 ] );
S_AL_SanitiseVector( axis[ 2 ] );
orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2];
orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2];
lastListenerNumber = entityNum;
VectorCopy( sorigin, lastListenerOrigin );
// Set OpenAL listener paramaters
qalListenerfv(AL_POSITION, (ALfloat *)sorigin);
qalListenerfv(AL_VELOCITY, vec3_origin);
qalListenerfv(AL_ORIENTATION, orientation);
}
/*
=================
S_AL_Update
=================
*/
static
void S_AL_Update( void )
{
int i;
if(s_muted->modified)
{
// muted state changed. Let S_AL_Gain turn up all sources again.
for(i = 0; i < srcCount; i++)
{
if(srcList[i].isActive)
S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain);
}
s_muted->modified = qfalse;
}
// Update SFX channels
S_AL_SrcUpdate();
// Update streams
for (i = 0; i < MAX_RAW_STREAMS; i++)
S_AL_StreamUpdate(i);
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)
{
qalSpeedOfSound(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 )
{
if(!numSfx)
S_AL_BufferInit();
}
/*
=================
S_AL_ClearSoundBuffer
=================
*/
static
void S_AL_ClearSoundBuffer( void )
{
}
/*
=================
S_AL_SoundList
=================
*/
static
void S_AL_SoundList( void )
{
}
#ifdef USE_VOIP
static
void S_AL_StartCapture( void )
{
if (alCaptureDevice != NULL)
qalcCaptureStart(alCaptureDevice);
}
static
int S_AL_AvailableCaptureSamples( void )
{
int retval = 0;
if (alCaptureDevice != NULL)
{
ALint samples = 0;
qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples);
retval = (int) samples;
}
return retval;
}
static
void S_AL_Capture( int samples, byte *data )
{
if (alCaptureDevice != NULL)
qalcCaptureSamples(alCaptureDevice, data, samples);
}
void S_AL_StopCapture( void )
{
if (alCaptureDevice != NULL)
qalcCaptureStop(alCaptureDevice);
}
void S_AL_MasterGain( float gain )
{
qalListenerf(AL_GAIN, gain);
}
#endif
/*
=================
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( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) );
Com_Printf( " ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) );
if(enumeration_all_ext)
Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER));
else if(enumeration_ext)
Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER));
if(enumeration_all_ext || enumeration_ext)
Com_Printf(" Available Devices:\n%s", s_alAvailableDevices->string);
#ifdef USE_VOIP
if(capture_ext)
{
Com_Printf(" Input Device: %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER));
Com_Printf(" Available Input Devices:\n%s", s_alAvailableInputDevices->string);
}
#endif
}
/*
=================
S_AL_Shutdown
=================
*/
static
void S_AL_Shutdown( void )
{
// Shut down everything
int i;
for (i = 0; i < MAX_RAW_STREAMS; i++)
S_AL_StreamDie(i);
S_AL_StopBackgroundTrack( );
S_AL_SrcShutdown( );
S_AL_BufferShutdown( );
qalcDestroyContext(alContext);
qalcCloseDevice(alDevice);
#ifdef USE_VOIP
if (alCaptureDevice != NULL) {
qalcCaptureStop(alCaptureDevice);
qalcCaptureCloseDevice(alCaptureDevice);
alCaptureDevice = NULL;
Com_Printf( "OpenAL capture device closed.\n" );
}
#endif
for (i = 0; i < MAX_RAW_STREAMS; i++) {
streamSourceHandles[i] = -1;
streamPlaying[i] = qfalse;
streamSources[i] = 0;
}
QAL_Shutdown();
}
#endif
/*
=================
S_AL_Init
=================
*/
qboolean S_AL_Init( soundInterface_t *si )
{
#ifdef USE_OPENAL
const char* device = NULL;
const char* inputdevice = NULL;
int i;
if( !si ) {
return qfalse;
}
for (i = 0; i < MAX_RAW_STREAMS; i++) {
streamSourceHandles[i] = -1;
streamPlaying[i] = qfalse;
streamSources[i] = 0;
streamNumBuffers[i] = 0;
streamBufIndex[i] = 0;
}
// New console variables
s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE );
s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE );
s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE );
s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE );
s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "9000", CVAR_ARCHIVE );
s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT );
s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT);
s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT);
s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT);
s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH | CVAR_PROTECTED );
s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH );
s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH);
// Load QAL
if( !QAL_Init( s_alDriver->string ) )
{
Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string );
if( !Q_stricmp( s_alDriver->string, ALDRIVER_DEFAULT ) || !QAL_Init( ALDRIVER_DEFAULT ) ) {
return qfalse;
}
}
device = s_alDevice->string;
if(device && !*device)
device = NULL;
inputdevice = s_alInputDevice->string;
if(inputdevice && !*inputdevice)
inputdevice = NULL;
// Device enumeration support
enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT");
enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
if(enumeration_ext || enumeration_all_ext)
{
char devicenames[16384] = "";
const char *devicelist;
#ifdef _WIN32
const char *defaultdevice;
#endif
int curlen;
// get all available devices + the default device name.
if(enumeration_all_ext)
{
devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
#ifdef _WIN32
defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER);
#endif
}
else
{
// We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration.
devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER);
#ifdef _WIN32
defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
#endif
enumeration_ext = qtrue;
}
#ifdef _WIN32
// check whether the default device is generic hardware. If it is, change to
// Generic Software as that one works more reliably with various sound systems.
// If it's not, use OpenAL's default selection as we don't want to ignore
// native hardware acceleration.
if(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware"))
device = "Generic Software";
#endif
// dump a list of available devices to a cvar for the user to see.
if(devicelist)
{
while((curlen = strlen(devicelist)))
{
Q_strcat(devicenames, sizeof(devicenames), devicelist);
Q_strcat(devicenames, sizeof(devicenames), "\n");
devicelist += curlen + 1;
}
}
s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART);
}
alDevice = qalcOpenDevice(device);
if( !alDevice && device )
{
Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", 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)
qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
qalDopplerFactor( s_alDopplerFactor->value );
qalSpeedOfSound( s_alDopplerSpeed->value );
#ifdef USE_VOIP
// !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars.
// !!! FIXME: add support for capture device enumeration.
// !!! FIXME: add some better error reporting.
s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH );
if (!s_alCapture->integer)
{
Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n");
}
#if USE_MUMBLE
else if (cl_useMumble->integer)
{
Com_Printf("OpenAL capture support disabled for Mumble support\n");
}
#endif
else
{
#ifdef __APPLE__
// !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes
// !!! FIXME: capture support, but they don't list it in the
// !!! FIXME: extension string. We need to check the version string,
// !!! FIXME: then the extension string, but that's too much trouble,
// !!! FIXME: so we'll just check the function pointer for now.
if (qalcCaptureOpenDevice == NULL)
#else
if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture"))
#endif
{
Com_Printf("No ALC_EXT_capture support, can't record audio.\n");
}
else
{
char inputdevicenames[16384] = "";
const char *inputdevicelist;
const char *defaultinputdevice;
int curlen;
capture_ext = qtrue;
// get all available input devices + the default input device name.
inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
// dump a list of available devices to a cvar for the user to see.
if (inputdevicelist)
{
while((curlen = strlen(inputdevicelist)))
{
Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist);
Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n");
inputdevicelist += curlen + 1;
}
}
s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART);
Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice ? defaultinputdevice : "none");
alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
if( !alCaptureDevice && inputdevice )
{
Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice );
alCaptureDevice = qalcCaptureOpenDevice(NULL, 48000, AL_FORMAT_MONO16, VOIP_MAX_PACKET_SAMPLES*4);
}
Com_Printf( "OpenAL capture device %s.\n",
(alCaptureDevice == NULL) ? "failed to open" : "opened");
}
}
#endif
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;
#ifdef USE_VOIP
si->StartCapture = S_AL_StartCapture;
si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples;
si->Capture = S_AL_Capture;
si->StopCapture = S_AL_StopCapture;
si->MasterGain = S_AL_MasterGain;
#endif
return qtrue;
#else
return qfalse;
#endif
}