engine/platformLinux/audio.cc
2024-01-07 04:36:33 +00:00

3575 lines
114 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/audio.h"
#include "audio/audioNet.h"
#include "Core/tVector.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "Test/gameConnection.h"
#ifndef __linux
#include "audio/audioMss.h"
#endif
#include "Core/fileStream.h"
#include "audio/audioThread.h"
// Comment the following line to enable the audio thread again
#define DISABLE_AUDIO_THREAD
// undef this to not include any immersion support
#ifdef IMMERSION_SUPPORT
#include "ImmSupport.h"
#endif
// miles openAL implementation does not handle 3dsource volumes through listener for all providers,
// so wrapper takes care of all this (undef if not needed)
// - scores for sources are attenuated by channel gain but not by listener gain
#define HANDLE_LISTENER_GAIN
#ifdef __linux
// redirect ALUT to our implementation that supports null fallbacks
#undef alutInit
#undef alutExit
#define alutInit alutInitFake
#define alutExit alutExitFake
#endif // __linux
//-------------------------------------------------------------------------
#ifndef __linux
namespace {
#endif
#define RECORD_SPEED 8000 // default rate for capturing
#define RECORD_FORMAT AL_FORMAT_MONO16 // capture format
#define RECORD_BUFFERSIZE 1024 // 1k capture buffer size
#define RECORD_LEN (4 * 1000) // 4seconds max capture time
#define MAX_AUDIOSOURCES 16 // maximum number of concurrent sources
#define MIN_GAIN 0.05f // anything with lower gain will not be started
#define MIN_CAPTURE_SCALE 0.1f // for scaling captured buffers
#define MAX_CAPTURE_SCALE 5.f
#define MIN_UNCULL_PERIOD 500 // time before buffer is checked to be unculled
#define MIN_UNCULL_GAIN 0.1f // min gain of source to be unculled
#define ALX_DEF_SAMPLE_RATE 44100 // default values for mixer
#define ALX_DEF_SAMPLE_BITS 16
#define ALX_DEF_CHANNELS 2
#define FORCED_OUTER_FALLOFF 10000.f // forced falloff distance
static bool mDisableOuterFalloffs = false; // forced max falloff?
static F32 mInnerFalloffScale = 1.f; // amount to scale inner falloffs
static Vector<Audio::DriverInfo> mDriverInfoList(__FILE__, __LINE__);
static bool mInitialized = false;
static Audio::DriverInfo *mActiveDriver = NULL; // the current audio driver (miles, none, ...)
static F32 mCaptureGainScale = 1.f; // amount to scale captured buffers before sending to server
static F32 mAudioTypeVolume[Audio::NumAudioTypes]; // the attenuation for each of the channel types
//-------------------------------------------------------------------------
struct LoopingImage
{
AUDIOHANDLE mHandle;
Resource<AudioBuffer> mBuffer;
Audio::Description mDescription;
EffectDescription * mpEffectDescription;
AudioSampleEnvironment * mEnvironment;
Point3F mPosition;
Point3F mDirection;
F32 mPan;
F32 mPitch;
F32 mScore;
S32 mCullTime;
LoopingImage() { clear(); }
void clear()
{
mHandle = NULL_AUDIOHANDLE;
mBuffer = NULL;
dMemset(&mDescription, 0, sizeof(Audio::Description));
mpEffectDescription = NULL;
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPan = 0.f;
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
}
};
#ifdef __linux
class DecoderStream
{
private:
public:
DecoderStream() { /* STUB STUB STUB */ ; }
~DecoderStream() { /* STUB STUB STUB */ ; }
static bool open() { /* STUB STUB STUB */ ; }
static bool open(const char *suffix) { /* STUB STUB STUB */ ; }
static void close() { /* STUB STUB STUB */ ; }
bool openStream() { /* STUB STUB STUB */ ; }
void closeStream() { /* STUB STUB STUB */ ; }
void setBuffer(U8 *data, U32 size) { /* STUB STUB STUB */ ; }
U32 getBuffer(U8 **data, U32 *size) { /* STUB STUB STUB */ ; }
void process(bool flush=false) { /* STUB STUB STUB */ ; }
};
class EncoderStream
{
private:
public:
//--------------------------------------
EncoderStream() { /* STUB STUB STUB */ ; }
~EncoderStream() { /* STUB STUB STUB */ ; }
static bool open() { /* STUB STUB STUB */ ; }
static bool open(const char *suffix) { /* STUB STUB STUB */ ; }
static void close() { /* STUB STUB STUB */ ; }
bool openStream() { /* STUB STUB STUB */ ; }
void closeStream() { /* STUB STUB STUB */ ; }
bool setConnection(GameConnection *con) { /* STUB STUB STUB */ ; }
void setBuffer(const U8 *buffer, U32 size) { /* STUB STUB STUB */ ; }
void process(bool flush=false) { /* STUB STUB STUB */ ; }
void flush() { /* STUB STUB STUB */ ; }
};
#endif
//-------------------------------------------------------------------------
struct VoiceStream
{
U32 mClientId;
U8 mStreamId;
DecoderStream mDecoder;
ALuint mBuffer;
AUDIOHANDLE mHandle;
};
static F32 mMasterVolume = 1.f; // traped from AL_LISTENER gain (miles has difficulties with 3d sources)
static ALuint mSource[MAX_AUDIOSOURCES]; // ALSources
static AUDIOHANDLE mHandle[MAX_AUDIOSOURCES]; // unique handles
static Resource<AudioBuffer> mBuffer[MAX_AUDIOSOURCES]; // each of the playing buffers (needed for AudioThread)
static F32 mScore[MAX_AUDIOSOURCES]; // for figuring out which sources to cull/uncull
static F32 mSourceVolume[MAX_AUDIOSOURCES]; // the samples current un-attenuated gain (not scaled by master/channel gains)
static U32 mType[MAX_AUDIOSOURCES]; // the channel which this source belongs
static bool mRecording = false; // currently recording?
static bool mLocalCapture = false; // recording to local buffer?
static ALboolean mCaptureInitialized = AL_FALSE; // capture initialized?
static ALuint mStreamBuffer; // the music strean buffer (only 1 music stream currently supported)
static AUDIOHANDLE mStreamHandle; // handle to the music stream
static bool mFinishedMusicStream = false; // set in callback (al thread) to process end of music stream
static bool mFinishedMusicStreamStopState = false; // set in callback (passed along to console)
static EffectDescription * mpEffectDescription[MAX_AUDIOSOURCES] = {NULL}; // currently playing force-feedback
static AudioSampleEnvironment * mSampleEnvironment[MAX_AUDIOSOURCES]; // currently playing sample environments
static bool mEnvironmentEnabled = false; // environment enabled?
static SimObjectPtr<AudioEnvironment> mCurrentEnvironment; // the last environment set
#define LOCAL_CAPTURE_SIZE (RECORD_LEN * RECORD_SPEED)
static U8 * mLocalCaptureBuffer = 0; // buffer to contain captured data
static U32 mLocalCaptureBufferPos = 0; // current capture buffer pos (circular)
static bool mLocalCaptureFinished = false; // finished capturing? (set in callback)
static ALuint mALCaptureBuffer = 0; // the buffer used to playback local capture data
static ALuint mEnvironment = 0; // al environment handle
struct LoopingList : VectorPtr<LoopingImage*>
{
LoopingList() : VectorPtr<LoopingImage*>(__FILE__, __LINE__) { }
LoopingList::iterator findImage(AUDIOHANDLE handle);
void sort();
};
// LoopingList and LoopingFreeList own the images
static LoopingList mLoopingList; // all the looping sources
static LoopingList mLoopingFreeList; // free store
static LoopingList mLoopingInactiveList; // sources which have not been played yet
static LoopingList mLoopingCulledList; // sources which have been culled (alxPlay called)
static VectorPtr<VoiceStream*> sVoiceStreams(__FILE__, __LINE__);
static VectorPtr<VoiceStream*> sVoiceStreams_free(__FILE__, __LINE__);
#define AUDIOHANDLE_LOOPING_BIT (0x80000000)
#define AUDIOHANDLE_VOICE_BIT (0x40000000)
#define AUDIOHANDLE_INACTIVE_BIT (0x20000000)
#define AUDIOHANDLE_LOADING_BIT (0x10000000)
#define HANDLE_MASK ~(AUDIOHANDLE_LOOPING_BIT | AUDIOHANDLE_VOICE_BIT | AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT)
// keep the 'AUDIOHANDLE_LOOPING_BIT' on the handle returned to the caller so that
// the handle can quickly be rejected from looping list queries
#define RETURN_MASK ~(AUDIOHANDLE_VOICE_BIT | AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT)
static AUDIOHANDLE mLastHandle = NULL_AUDIOHANDLE;
static bool mForceMaxDistanceUpdate = false; // force gain setting for 3d distances
static U32 mNumSources = 0; // total number of sources to work with
static U32 mRequestSources = MAX_AUDIOSOURCES; // number of sources to request from openAL
#define INVALID_SOURCE 0xffffffff
#define CAPTURE_BUFFER_SIZE (300)
static U32 sCaptureTimeout = 0;
static EncoderStream mEncoderStream;
inline bool areEqualHandles(AUDIOHANDLE a, AUDIOHANDLE b)
{
return((a & HANDLE_MASK) == (b & HANDLE_MASK));
}
//-------------------------------------------------------------------------
// Looping image
//-------------------------------------------------------------------------
inline LoopingList::iterator LoopingList::findImage(AUDIOHANDLE handle)
{
if(handle & AUDIOHANDLE_LOOPING_BIT)
{
LoopingList::iterator itr = begin();
while(itr != end())
{
if(areEqualHandles((*itr)->mHandle, handle))
return(itr);
itr++;
}
}
return(0);
}
inline int QSORT_CALLBACK loopingImageSort(const void * p1, const void * p2)
{
const LoopingImage * ip1 = *(const LoopingImage**)p1;
const LoopingImage * ip2 = *(const LoopingImage**)p2;
// min->max
return ip2->mScore - ip1->mScore;
}
void LoopingList::sort()
{
dQsort(address(), size(), sizeof(LoopingImage*), loopingImageSort);
}
//-------------------------------------------------------------------------
LoopingImage * createLoopingImage()
{
LoopingImage *image;
if (mLoopingFreeList.size())
{
image = mLoopingFreeList.last();
mLoopingFreeList.pop_back();
}
else
image = new LoopingImage;
return(image);
}
//-------------------------------------------------------------------------
static AUDIOHANDLE getNewHandle()
{
mLastHandle++;
mLastHandle &= HANDLE_MASK;
if (mLastHandle == NULL_AUDIOHANDLE)
mLastHandle++;
return mLastHandle;
}
#ifndef __linux
} // namespace {}
#endif
//-------------------------------------------------------------------------
// function declarations
void alxLoopingUpdate();
void alxUpdateScores(bool);
//-------------------------------------------------------------------------
static void alxFreeVoiceStream(VectorPtr<VoiceStream*>::iterator itr)
{
VoiceStream *vs = *itr;
sVoiceStreams.erase_fast(itr);
sVoiceStreams_free.push_back(vs);
vs->mHandle = NULL_AUDIOHANDLE;
Con::evaluatef("clientCmdPlayerStoppedTalking(%d, 1);", vs->mClientId);
}
static void alxFreeVoiceStream(AUDIOHANDLE handle)
{
VectorPtr<VoiceStream*>::iterator itr = sVoiceStreams.begin();
for (; itr != sVoiceStreams.end(); itr++)
if ((*itr)->mHandle == handle)
{
alxFreeVoiceStream(itr);
return;
}
}
inline VectorPtr<VoiceStream*>::iterator alxFindVoiceStream(AUDIOHANDLE handle)
{
for(VectorPtr<VoiceStream*>::iterator itr = sVoiceStreams.begin(); itr != sVoiceStreams.end(); itr++)
if(areEqualHandles((*itr)->mHandle, handle))
return(itr);
return(0);
}
static bool findFreeSource(U32 *index)
{
for(U32 i = 0; i < mNumSources; i++)
if(mHandle[i] == NULL_AUDIOHANDLE)
{
*index = i;
return(true);
}
return(false);
}
//--------------------------------------------------------------------------
// - cull out the min source that is below volume
// - streams/voice/loading streams are all scored > 2
// - volumes are attenuated by channel only
static bool cullSource(U32 *index, F32 volume)
{
alGetError();
F32 minVolume = volume;
S32 best = -1;
for(S32 i = 0; i < mNumSources; i++)
{
if(mScore[i] < minVolume)
{
minVolume = mScore[i];
best = i;
}
}
if(best == -1)
return(false);
// check if culling a looper
LoopingList::iterator itr = mLoopingList.findImage(mHandle[best]);
if(itr)
{
// check if culling an inactive looper
if(mHandle[best] & AUDIOHANDLE_INACTIVE_BIT)
{
AssertFatal(!mLoopingInactiveList.findImage(mHandle[best]), "cullSource: image already in inactive list");
AssertFatal(!mLoopingCulledList.findImage(mHandle[best]), "cullSource: image should not be in culled list");
mLoopingInactiveList.push_back(*itr);
}
else
{
(*itr)->mHandle |= AUDIOHANDLE_INACTIVE_BIT;
AssertFatal(!mLoopingCulledList.findImage(mHandle[best]), "cullSource: image already in culled list");
AssertFatal(!mLoopingInactiveList.findImage(mHandle[best]), "cullSource: image should no be in inactive list");
(*itr)->mCullTime = Platform::getRealMilliseconds();
mLoopingCulledList.push_back(*itr);
}
}
alSourceStop(mSource[best]);
mHandle[best] = NULL_AUDIOHANDLE;
mBuffer[best] = 0;
*index = best;
return(true);
}
//--------------------------------------------------------------------------
// we want to compute approximate max volume at a particular distance
// ignore cone volume influnces
static F32 approximate3DVolume(const Audio::Description *desc, const Point3F &position)
{
Point3F p1;
alxGetListener3f(AL_POSITION, &p1.x, &p1.y, &p1.z);
p1 -= position;
F32 distance = p1.magnitudeSafe();
if(distance >= desc->mMaxDistance)
return(0.f);
else if(distance > desc->mMinDistance)
return(desc->mMinDistance / distance);
else
return 1.0f;
}
//--------------------------------------------------------------------------
inline U32 alxFindIndex(AUDIOHANDLE handle)
{
for (U32 i=0; i<MAX_AUDIOSOURCES; i++)
if(mHandle[i] && areEqualHandles(mHandle[i], handle))
return i;
return MAX_AUDIOSOURCES;
}
//--------------------------------------------------------------------------
ALuint alxFindSource(AUDIOHANDLE handle)
{
for (U32 i=0; i<MAX_AUDIOSOURCES; i++)
if(mHandle[i] && areEqualHandles(mHandle[i], handle))
return mSource[i];
return(INVALID_SOURCE);
}
//--------------------------------------------------------------------------
// * a handle is valid if it is a currently playing source, inactive source,
// or a looping source (basically anything where a alxSource??? call will succeed)
bool alxIsValidHandle(AUDIOHANDLE handle)
{
if(handle == NULL_AUDIOHANDLE)
return(false);
// inactive sources are valid
U32 idx = alxFindIndex(handle);
if(idx != MAX_AUDIOSOURCES)
{
if(mHandle[idx] & AUDIOHANDLE_INACTIVE_BIT)
return(true);
// if it is active but not playing then it has stopped...
ALint state = AL_STOPPED;
alGetSourcei(mSource[idx], AL_SOURCE_STATE, &state);
return(state == AL_PLAYING);
}
if(mLoopingList.findImage(handle))
return(true);
return(false);
}
bool alxIsPlaying(AUDIOHANDLE handle)
{
if(handle == NULL_AUDIOHANDLE)
return(false);
U32 idx = alxFindIndex(handle);
if(idx == MAX_AUDIOSOURCES)
return(false);
ALint state = 0;
alGetSourcei(mSource[idx], AL_SOURCE_STATE, &state);
return(state == AL_PLAYING);
}
//--------------------------------------------------------------------------
// - setup a sources environmental effect settings
static void alxSourceEnvironment(ALuint source, F32 environmentLevel, AudioSampleEnvironment * env)
{
#ifndef __linux
// environment level is on the AudioDatablock
alSourcef(source, AL_ENV_SAMPLE_REVERB_MIX_EXT, environmentLevel);
if(!env)
return;
alSourcei(source, AL_ENV_SAMPLE_DIRECT_EXT, env->mDirect);
alSourcei(source, AL_ENV_SAMPLE_DIRECT_HF_EXT, env->mDirectHF);
alSourcei(source, AL_ENV_SAMPLE_ROOM_EXT, env->mRoom);
alSourcei(source, AL_ENV_SAMPLE_ROOM_HF_EXT, env->mRoomHF);
alSourcei(source, AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, env->mOutsideVolumeHF);
alSourcei(source, AL_ENV_SAMPLE_FLAGS_EXT, env->mFlags);
alSourcef(source, AL_ENV_SAMPLE_OBSTRUCTION_EXT, env->mObstruction);
alSourcef(source, AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, env->mObstructionLFRatio);
alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_EXT, env->mOcclusion);
alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, env->mOcclusionLFRatio);
alSourcef(source, AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, env->mOcclusionRoomRatio);
alSourcef(source, AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, env->mRoomRolloff);
alSourcef(source, AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, env->mAirAbsorption);
#endif
}
static void alxSourceEnvironment(ALuint source, LoopingImage * image)
{
AssertFatal(image, "alxSourceEnvironment: invalid looping image");
if(image->mDescription.mIs3D)
alxSourceEnvironment(source, image->mDescription.mEnvironmentLevel, image->mEnvironment);
}
//--------------------------------------------------------------------------
// setup a source to play... loopers have pitch/pan cached
// - by default, pitch is 1x and pan is center (settings not defined in description)
// - all the settings are cached by openAL (miles version), so no worries setting them here
static void alxSourcePlay(ALuint source, Resource<AudioBuffer> buffer, const Audio::Description *desc, const MatrixF *transform)
{
alSourcei(source, AL_BUFFER, buffer->getALBuffer());
alSourcef(source, AL_GAIN_LINEAR, desc->mVolume * mAudioTypeVolume[desc->mType] * mMasterVolume);
alSourcei(source, AL_SOURCE_LOOPING, desc->mIsLooping ? AL_TRUE : AL_FALSE);
alSourcef(source, AL_PITCH, 1.f);
// stream sources are only setup for music streams (below...)
#ifndef __linux
alSourcei(source, AL_STREAMING, AL_FALSE);
#endif
// 3d?
if(transform != NULL)
{
#ifdef __linux
alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
#else
alSourcei(source, AL_SOURCE_AMBIENT, AL_FALSE);
#endif
alSourcei(source, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle);
alSourcei(source, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle);
alSourcef(source, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume);
Point3F p;
transform->getColumn(3, &p);
alSource3f(source, AL_POSITION, p.x, p.y, p.z);
transform->getRow(1, &p);
alSource3f(source, AL_DIRECTION, p.x, p.y, p.z);
// forcing different falloffs?
if(mDisableOuterFalloffs)
{
alSourcef(source, AL_MIN_DISTANCE, mInnerFalloffScale * desc->mMinDistance);
alSourcef(source, AL_MAX_DISTANCE, FORCED_OUTER_FALLOFF);
}
else
{
alSourcef(source, AL_MIN_DISTANCE, desc->mMinDistance);
alSourcef(source, AL_MAX_DISTANCE, desc->mMaxDistance);
}
#ifndef __linux
// environmental audio stuff:
alSourcef(source, AL_ENV_SAMPLE_REVERB_MIX_EXT, desc->mEnvironmentLevel);
if(desc->mEnvironmentLevel != 0.f)
alSourceResetEnvironment_EXT(source);
#endif
}
else // 2d source (default the pan)
{
#ifdef __linux
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
#else
alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE);
alSourcef(source, AL_PAN, 0.f);
#endif
}
}
// helper for looping images
static void alxSourcePlay(ALuint source, LoopingImage * image)
{
AssertFatal(image, "alxSourcePlay: invalid looping image");
// 3d source? need position/direction
if(image->mDescription.mIs3D)
{
MatrixF transform(true);
transform.setColumn(3, image->mPosition);
transform.setRow(1, image->mDirection);
alxSourcePlay(source, image->mBuffer, &image->mDescription, &transform);
}
else // 2d source?
{
alxSourcePlay(source, image->mBuffer, &image->mDescription, 0);
// set the pan/pitch...
alxSourcef(source, AL_PITCH, image->mPitch);
alxSourcef(source, AL_PAN, image->mPan);
}
}
//--------------------------------------------------------------------------
AUDIOHANDLE alxCreateSource(const Audio::Description * desc, const char * filename, const MatrixF * transform,
EffectDescription * effectDescription, AudioSampleEnvironment * sampleEnvironment)
{
if(desc == NULL || filename == NULL || *filename == '\0')
return NULL_AUDIOHANDLE;
F32 volume = desc->mVolume;
#ifdef IMMERSION_SUPPORT
bool storeEffectDescription = false;
if (effectDescription)
{
effectDescription->mVolume = volume;
storeEffectDescription = volume > 0.0f;
}
#endif
// calculate an approximate attenuation for 3d sounds
if(transform && desc->mIs3D)
{
Point3F position;
transform->getColumn(3, &position);
volume *= approximate3DVolume(desc, position);
#ifdef IMMERSION_SUPPORT
// Calculate Force-Feedback Effect volume
if (effectDescription)
{
Audio::Description effectDesc;
dMemcpy(&effectDesc, desc, sizeof(Audio::Description));
effectDesc.mMinDistance = effectDescription->mMinDistance;
effectDesc.mMaxDistance = effectDescription->mMaxDistance;
effectDescription->mVolume *= approximate3DVolume(&effectDesc, position);
storeEffectDescription = effectDescription->mVolume > 0.0f;
}
#endif
}
// check the type specific volume
AssertFatal(desc->mType < Audio::NumAudioTypes, "alxCreateSource: invalid type for source");
if(desc->mType >= Audio::NumAudioTypes)
return(NULL_AUDIOHANDLE);
// done if channel is muted (and not a looper)
if(!desc->mIsLooping && (mAudioTypeVolume[desc->mType] == 0.f))
return(NULL_AUDIOHANDLE);
// scale volume by channel attenuation
volume *= mAudioTypeVolume[desc->mType];
// non-loopers don't add if < minvolume
if(!desc->mIsLooping && (volume <= MIN_GAIN))
return(NULL_AUDIOHANDLE);
U32 index = MAX_AUDIOSOURCES;
// try and find an available source: 0 volume loopers get added to inactive list
if(volume > MIN_GAIN)
{
if(!findFreeSource(&index))
{
alxUpdateScores(true);
// scores do not include master volume
if(!cullSource(&index, volume))
index = MAX_AUDIOSOURCES;
}
}
// make sure that loopers are added
if(index == MAX_AUDIOSOURCES)
{
if(desc->mIsLooping)
{
Resource<AudioBuffer> buffer = AudioBuffer::find(filename);
if(!(bool)buffer)
return(NULL_AUDIOHANDLE);
// create the inactive looping image
LoopingImage * image = createLoopingImage();
image->mHandle = getNewHandle() | AUDIOHANDLE_LOOPING_BIT | AUDIOHANDLE_INACTIVE_BIT;
image->mBuffer = buffer;
image->mDescription = *desc;
image->mScore = volume;
image->mEnvironment = sampleEnvironment;
#ifdef IMMERSION_SUPPORT
image->mpEffectDescription = (storeEffectDescription ? effectDescription : NULL);
#endif
// grab position/direction if 3d source
if(transform)
{
transform->getColumn(3, &image->mPosition);
transform->getRow(1, &image->mDirection);
}
AssertFatal(!mLoopingInactiveList.findImage(image->mHandle), "alxCreateSource: handle in inactive list");
AssertFatal(!mLoopingCulledList.findImage(image->mHandle), "alxCreateSource: handle in culled list");
// add to the looping and inactive lists
mLoopingList.push_back(image);
mLoopingInactiveList.push_back(image);
return(image->mHandle & RETURN_MASK);
}
else
return(NULL_AUDIOHANDLE);
}
// clear the error state
alGetError();
// grab the buffer
Resource<AudioBuffer> buffer = AudioBuffer::find(filename);
if((bool)buffer == false)
return NULL_AUDIOHANDLE;
// init the source (created inactive) and store needed values
mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT;
mType[index] = desc->mType;
mBuffer[index] = buffer;
mScore[index] = volume;
mSourceVolume[index] = desc->mVolume;
mSampleEnvironment[index] = sampleEnvironment;
ALuint source = mSource[index];
#ifdef IMMERSION_SUPPORT
mpEffectDescription[index] = (storeEffectDescription ? effectDescription : NULL);
#else
mpEffectDescription[index] = NULL;
#endif
// setup play info
alxSourcePlay(source, buffer, desc, desc->mIs3D ? transform : 0);
if(mEnvironmentEnabled)
alxSourceEnvironment(source, desc->mEnvironmentLevel, sampleEnvironment);
// setup a LoopingImage ONLY if the sound is a looper:
if(desc->mIsLooping)
{
mHandle[index] |= AUDIOHANDLE_LOOPING_BIT;
LoopingImage * image = createLoopingImage();
image->mHandle = mHandle[index];
image->mBuffer = buffer;
image->mDescription = *desc;
image->mScore = volume;
image->mEnvironment = sampleEnvironment;
// grab position/direction
if(transform)
{
transform->getColumn(3, &image->mPosition);
transform->getRow(1, &image->mDirection);
}
#ifdef IMMERSION_SUPPORT
image->mpEffectDescription = (storeEffectDescription ? effectDescription : NULL);
#else
image->mpEffectDescription = NULL;
#endif
AssertFatal(!mLoopingInactiveList.findImage(image->mHandle), "alxCreateSource: handle in inactive list");
AssertFatal(!mLoopingCulledList.findImage(image->mHandle), "alxCreateSource: handle in culled list");
// add to the looping list
mLoopingList.push_back(image);
}
// clear off all but looping bit
return(mHandle[index] & RETURN_MASK);
}
//------------------------------------------------------------------------------
AUDIOHANDLE alxCreateSource(AudioDescription *descObject,
const char *filename,
const MatrixF *transform,
EffectDescription *effectDescription,
AudioSampleEnvironment * sampleEnvironment )
{
if(!descObject || !descObject->getDescription())
return(NULL_AUDIOHANDLE);
return (alxCreateSource(descObject->getDescription(), filename, transform, effectDescription, sampleEnvironment));
}
AUDIOHANDLE alxCreateSource(const AudioProfile *profile, const MatrixF *transform)
{
if (profile == NULL)
return NULL_AUDIOHANDLE;
EffectDescription *pED = NULL;
#ifdef IMMERSION_SUPPORT
EffectProfile *pEP = profile->mEffectProfile;
// If no Immersion-compatible device is plugged in, EffectDescription will still
// exist, but the mpEffect pointer will be NULL. Save some time by NOT putting
// this EffectDescription in the active list.
if (pEP)
{
pED = &pEP->mDescription;
if (!pED->mpEffect)
pED = NULL;
}
#endif
return alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, pED, profile->mSampleEnvironment);
}
//--------------------------------------------------------------------------
extern void threadPlay(AudioBuffer * buffer, AUDIOHANDLE handle);
AUDIOHANDLE alxPlay(AUDIOHANDLE handle)
{
U32 index = alxFindIndex(handle);
if(index != MAX_AUDIOSOURCES)
{
// play if not already playing
if(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT)
{
// jff: thread stuff
// // have the thread play this once the buffer is loaded
// if(bool(mBuffer[index]) && mBuffer[index]->isLoading())
// {
// // move loopers into the inactive list
// LoopingList::iterator itr = mLoopingList.findImage(handle);
// if(itr)
// {
// mHandle[index] = NULL_AUDIOHANDLE;
// mBuffer[index] = 0;
//
// (*itr)->mHandle |= AUDIOHANDLE_LOADING_BIT;
// mLoopingInactiveList.push_back(*itr);
// }
//
// mHandle[index] |= AUDIOHANDLE_LOADING_BIT;
//
// if(gAudioThread)
// gAudioThread->setBufferPlayHandle(mBuffer[index], handle);
// return(handle);
// }
mHandle[index] &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT);
// make sure the looping image also clears it's inactive bit
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
(*itr)->mHandle &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT);
alSourcePlay(mSource[index]);
// Immersion component
#ifdef IMMERSION_SUPPORT
EffectDescription* ed = mpEffectDescription[index];
if (ed)
ed->mpEffect->Start(ed->mLooping ? INFINITE : 1);
#endif
return(handle);
}
}
else
{
// move inactive loopers to the culled list, try to start the sound
LoopingList::iterator itr = mLoopingInactiveList.findImage(handle);
if(itr)
{
AssertFatal(!mLoopingCulledList.findImage(handle), "alxPlay: image already in culled list");
mLoopingCulledList.push_back(*itr);
mLoopingInactiveList.erase_fast(itr);
alxLoopingUpdate();
}
else if(mLoopingCulledList.findImage(handle))
{
alxLoopingUpdate();
}
else
return(NULL_AUDIOHANDLE);
}
return(handle);
}
//--------------------------------------------------------------------------
// helper function.. create a source and play it
AUDIOHANDLE alxPlay(const AudioProfile *profile, const MatrixF *transform, const Point3F* /*velocity*/)
{
if(profile == NULL)
return NULL_AUDIOHANDLE;
EffectDescription *pED = NULL;
#ifdef IMMERSION_SUPPORT
EffectProfile *pEP = profile->mEffectProfile;
// If no Immersion-compatible device is plugged in, EffectDescription will still
// exist, but the mpEffect pointer will be NULL. Save some time by NOT putting
// this EffectDescription in the active list.
if (pEP)
{
pED = &pEP->mDescription;
if (!pED->mpEffect)
pED = NULL;
}
#endif
AUDIOHANDLE handle = alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, pED, profile->mSampleEnvironment);
if(handle != NULL_AUDIOHANDLE)
return(alxPlay(handle));
return(handle);
}
//--------------------------------------------------------------------------
void alxStop(AUDIOHANDLE handle)
{
U32 index = alxFindIndex(handle);
// stop it
if(index != MAX_AUDIOSOURCES)
{
// jff: will have problems if buffer still loading
if(!(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT))
{
alSourceStop(mSource[index]);
#ifdef IMMERSION_SUPPORT
if (mpEffectDescription[index])
mpEffectDescription[index]->mpEffect->Stop();
#endif
}
mSampleEnvironment[index] = 0;
mHandle[index] = NULL_AUDIOHANDLE;
mBuffer[index] = 0;
#ifdef IMMERSION_SUPPORT
mpEffectDescription[index] = NULL;
#endif
}
// remove loopingImage and add it to the free list
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
// remove from inactive/culled list
if((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT)
{
LoopingList::iterator tmp = mLoopingInactiveList.findImage(handle);
// inactive?
if(tmp)
mLoopingInactiveList.erase_fast(tmp);
else
{
//culled?
tmp = mLoopingCulledList.findImage(handle);
AssertFatal(tmp, "alxStop: failed to find inactive looping source");
mLoopingCulledList.erase_fast(tmp);
}
}
AssertFatal(!mLoopingInactiveList.findImage((*itr)->mHandle), "alxStop: handle in inactive list");
AssertFatal(!mLoopingCulledList.findImage((*itr)->mHandle), "alxStop: handle in culled list");
// remove it
(*itr)->clear();
mLoopingFreeList.push_back(*itr);
mLoopingList.erase_fast(itr);
}
}
//--------------------------------------------------------------------------
void alxStopAll()
{
// stop all open sources
for(S32 i = mNumSources - 1; i >= 0; i--)
if(mHandle[i] != NULL_AUDIOHANDLE)
alxStop(mHandle[i]);
// stop all looping sources
while(mLoopingList.size())
alxStop(mLoopingList.last()->mHandle);
}
void alxLoopSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value)
{
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
switch(pname)
{
case AL_PAN:
(*itr)->mPan = value;
break;
case AL_GAIN:
(*itr)->mDescription.mVolume = Audio::DBToLinear(value);
break;
case AL_GAIN_LINEAR:
(*itr)->mDescription.mVolume = value;
break;
case AL_PITCH:
(*itr)->mPitch = value;
break;
case AL_MIN_DISTANCE:
(*itr)->mDescription.mMinDistance = value;
break;
case AL_CONE_OUTER_GAIN:
(*itr)->mDescription.mMaxDistance = value;
break;
}
}
}
void alxLoopSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3)
{
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
switch(pname)
{
case AL_POSITION:
(*itr)->mPosition.x = value1;
(*itr)->mPosition.y = value2;
(*itr)->mPosition.z = value3;
break;
case AL_DIRECTION:
(*itr)->mDirection.x = value1;
(*itr)->mDirection.y = value2;
(*itr)->mDirection.z = value3;
break;
}
}
}
void alxLoopSourcei(AUDIOHANDLE handle, ALenum pname, ALint value)
{
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
switch(pname)
{
case AL_SOURCE_AMBIENT:
(*itr)->mDescription.mIs3D = value;
break;
case AL_CONE_INNER_ANGLE:
(*itr)->mDescription.mConeInsideAngle = value;
break;
case AL_CONE_OUTER_ANGLE:
(*itr)->mDescription.mConeOutsideAngle = value;
break;
}
}
}
void alxLoopGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value)
{
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
switch(pname)
{
case AL_PAN:
*value = (*itr)->mPan;
break;
case AL_GAIN:
*value = Audio::linearToDB((*itr)->mDescription.mVolume);
break;
case AL_GAIN_LINEAR:
*value = (*itr)->mDescription.mVolume;
break;
case AL_PITCH:
*value = (*itr)->mPitch;
break;
case AL_MIN_DISTANCE:
*value = (*itr)->mDescription.mMinDistance;
break;
case AL_CONE_OUTER_GAIN:
*value = (*itr)->mDescription.mMaxDistance;
break;
}
}
}
void alxLoopGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3)
{
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
switch(pname)
{
case AL_POSITION:
*value1 = (*itr)->mPosition.x;
*value2 = (*itr)->mPosition.y;
*value3 = (*itr)->mPosition.z;
break;
case AL_DIRECTION:
*value1 = (*itr)->mDirection.x;
*value2 = (*itr)->mDirection.y;
*value3 = (*itr)->mDirection.z;
break;
}
}
}
void alxLoopGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value)
{
LoopingList::iterator itr = mLoopingList.findImage(handle);
if(itr)
{
switch(pname)
{
case AL_SOURCE_AMBIENT:
*value = (*itr)->mDescription.mIs3D;
break;
case AL_SOURCE_LOOPING:
*value = true;
break;
case AL_CONE_INNER_ANGLE:
*value = (*itr)->mDescription.mConeInsideAngle;
break;
case AL_CONE_OUTER_ANGLE:
*value = (*itr)->mDescription.mConeOutsideAngle;
break;
}
}
}
//--------------------------------------------------------------------------
// AL get/set methods: Source
//--------------------------------------------------------------------------
// - only need to worry about playing sources.. proper volume gets set on
// create source (so, could get out of sync if someone changes volume between
// a createSource and playSource call...)
void alxUpdateTypeGain(U32 typeMask)
{
for(U32 i = 0; i < MAX_AUDIOSOURCES; i++)
{
if(mHandle[i] == NULL_AUDIOHANDLE)
continue;
if(!(typeMask & (1 << mType[i])))
continue;
ALint state = AL_STOPPED;
alGetSourcei(mSource[i], AL_SOURCE_STATE, &state);
if(state == AL_PLAYING)
{
// volume = SourceVolume * ChannelVolume * MasterVolume
F32 vol = mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume;
alSourcef(mSource[i], AL_GAIN_LINEAR, mClampF(vol, 0.f, 1.f));
}
}
}
void alxSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value)
{
#ifdef __linux
// ignore magical token
if( pname == AL_PAN ) {
return;
}
#endif
ALuint source = alxFindSource(handle);
if(source != INVALID_SOURCE)
{
// ensure gain_linear
if(pname == AL_GAIN)
{
value = Audio::DBToLinear(value);
pname = AL_GAIN_LINEAR;
}
// need to process gain settings (so source can be affected by channel/master gains)
if(pname == AL_GAIN_LINEAR)
{
U32 idx = alxFindIndex(handle);
AssertFatal(idx != MAX_AUDIOSOURCES, "alxSourcef: handle not located for found source");
if(idx == MAX_AUDIOSOURCES)
return;
// update the stored value
mSourceVolume[idx] = value;
// volume = SourceVolume * ChannelVolume * MasterVolume
F32 vol = mSourceVolume[idx] * mAudioTypeVolume[mType[idx]] * mMasterVolume;
alSourcef(source, AL_GAIN_LINEAR, mClampF(vol, 0.f, 1.f));
}
else
alSourcef(source, pname, value);
}
alxLoopSourcef(handle, pname, value);
}
void alxSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values)
{
ALuint source = alxFindSource(handle);
if(source != INVALID_SOURCE)
alSourcefv(source, pname, values);
if((pname == AL_POSITION) || (pname == AL_DIRECTION) || (pname == AL_VELOCITY))
alxLoopSource3f(handle, pname, values[0], values[1], values[2]);
}
void alxSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3)
{
ALuint source = alxFindSource(handle);
if(source != INVALID_SOURCE)
{
ALfloat values[3];
values[0] = value1;
values[1] = value2;
values[2] = value3;
alSourcefv(source, pname, values);
}
alxLoopSource3f(handle, pname, value1, value2, value3);
}
void alxSourcei(AUDIOHANDLE handle, ALenum pname, ALint value)
{
ALuint source = alxFindSource(handle);
#ifdef __linux
// reroute fictional token
if( pname == AL_SOURCE_AMBIENT ) {
if ( value ) {
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
} else {
alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
}
} else {
alSourcei( source, pname, value );
}
#else
if(source != INVALID_SOURCE)
alSourcei(source, pname, value);
#endif
alxLoopSourcei(handle, pname, value);
}
// sets the position and direction of the source
void alxSourceMatrixF(AUDIOHANDLE handle, const MatrixF *transform)
{
ALuint source = alxFindSource(handle);
Point3F pos;
transform->getColumn(3, &pos);
Point3F dir;
transform->getRow(1, &dir);
if(source != INVALID_SOURCE)
{
alSource3f(source, AL_POSITION, pos.x, pos.y, pos.z);
alSource3f(source, AL_DIRECTION, dir.x, dir.y, dir.z);
}
alxLoopSource3f(handle, AL_POSITION, pos.x, pos.y, pos.z);
alxLoopSource3f(handle, AL_DIRECTION, dir.x, dir.y, dir.z);
}
//--------------------------------------------------------------------------
void alxGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value)
{
ALuint source = alxFindSource(handle);
if(source != INVALID_SOURCE)
{
// gain queries return unattenuated values
if((pname == AL_GAIN) || (pname == AL_GAIN_LINEAR))
{
U32 idx = alxFindIndex(handle);
AssertFatal(idx != MAX_AUDIOSOURCES, "alxGetSourcef: found source but handle is invalid");
if(idx == MAX_AUDIOSOURCES)
{
*value = 0.f;
return;
}
if(pname == AL_GAIN)
*value = Audio::linearToDB(mSourceVolume[idx]);
else
*value = mSourceVolume[idx];
}
else
alGetSourcef(source, pname, value);
}
else
alxLoopGetSourcef(handle, pname, value);
}
void alxGetSourcefv(AUDIOHANDLE handle, ALenum pname, ALfloat *values)
{
if((pname == AL_POSITION) || (pname == AL_DIRECTION) || (pname == AL_VELOCITY))
alxGetSource3f(handle, pname, &values[0], &values[1], &values[2]);
}
void alxGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3)
{
ALuint source = alxFindSource(handle);
if(source != INVALID_SOURCE)
{
ALfloat values[3];
alGetSourcefv(source, pname, values);
*value1 = values[0];
*value2 = values[1];
*value3 = values[2];
}
else
alxLoopGetSource3f(handle, pname, value1, value2, value3);
}
void alxGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value)
{
ALuint source = alxFindSource(handle);
if(source != INVALID_SOURCE)
alGetSourcei(source, pname, value);
else
alxLoopGetSourcei(handle, pname, value);
}
//--------------------------------------------------------------------------
// AL get/set methods: Listener
//--------------------------------------------------------------------------
void alxListenerf(ALenum pname, ALfloat value)
{
#ifdef HANDLE_LISTENER_GAIN
// listener gain is handled through wrapper
if((pname == AL_GAIN) || (pname == AL_GAIN_LINEAR))
{
// just handles al_gain_linear
if(pname == AL_GAIN)
value = Audio::DBToLinear(value);
mMasterVolume = mClampF(value, 0.f, 1.f);
// update all the sources
alxUpdateTypeGain(0xffffffff);
}
else
alListenerf(pname, value);
#else
alListenerf(pname, value);
#endif
}
void alxListenerfv(ALenum pname, ALfloat * values)
{
alListenerfv(pname, values);
}
void alxListener3f(ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3)
{
alListener3f(pname, value1, value2, value3);
}
//--------------------------------------------------------------------------
// set the listener's position and orientation
void alxListenerMatrixF(const MatrixF *transform)
{
Point3F p1, p2;
transform->getColumn(3, &p1);
alListener3f(AL_POSITION, p1.x, p1.y, p1.z);
transform->getColumn(2, &p1); // Up Vector
transform->getColumn(1, &p2); // Forward Vector
F32 orientation[6];
orientation[0] = p1.x;
orientation[1] = p1.y;
orientation[2] = p1.z;
orientation[3] = p2.x;
orientation[4] = p2.y;
orientation[5] = p2.z;
alListenerfv(AL_ORIENTATION, orientation);
}
//--------------------------------------------------------------------------
void alxGetListenerf(ALenum pname, ALfloat *value)
{
#ifdef HANDLE_LISTENER_GAIN
// listener gain is handled through wrapper
if(pname == AL_GAIN)
*value = Audio::linearToDB(mMasterVolume);
else if(pname == AL_GAIN_LINEAR)
*value = mMasterVolume;
else
alGetListenerf(pname, value);
#else
alGetListenerf(pname, value);
#endif
}
void alxGetListenerfv(ALenum pname, ALfloat *values)
{
alGetListenerfv(pname, values);
}
void alxGetListener3f(ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3)
{
ALfloat val[3];
alGetListenerfv(pname, val);
*value1 = val[0];
*value2 = val[1];
*value3 = val[2];
}
void alxGetListeneri(ALenum pname, ALint *value)
{
alGetListeneri(pname, value);
}
//--------------------------------------------------------------------------
// Capture methods
//--------------------------------------------------------------------------
void alxCaptureDestroy()
{
#ifndef __linux
if(mCaptureInitialized)
alCaptureDestroy_EXT();
#endif
mEncoderStream.closeStream();
EncoderStream::close();
DecoderStream::close();
}
bool alxCaptureInit()
{
#ifdef __linux
// Not yet supported...
return(false);
#else
alxCaptureDestroy();
const char *codec = Con::getVariable("$Audio::voiceCodec");
if(!codec)
return(false);
mCaptureInitialized = alCaptureInit_EXT(RECORD_FORMAT, RECORD_SPEED, RECORD_BUFFERSIZE);
if(mCaptureInitialized == AL_FALSE)
return(false);
EncoderStream::open(codec);
mEncoderStream.openStream();
DecoderStream::open(codec);
return(true);
#endif
}
//--------------------------------------------------------------------------
void alxCaptureStart(bool local)
{
if(!mCaptureInitialized || mRecording)
return;
mLocalCapture = local;
if(mLocalCapture)
{
mLocalCaptureBuffer = (U8 *)dMalloc(LOCAL_CAPTURE_SIZE);
mLocalCaptureBufferPos = 0;
Con::executef(2, "localCaptureStart", "record");
}
else
{
GameConnection* connection = GameConnection::getServerConnection();
if(!connection)
{
Con::errorf(ConsoleLogEntry::General, "alxCaptureStart: no server connection");
return;
}
mEncoderStream.setConnection(connection);
}
sCaptureTimeout = Platform::getRealMilliseconds();
#ifndef __linux
alCaptureStart_EXT();
#endif
mRecording = true;
}
void scaleSamples(void * data, U32 size, U32 format, F32 scale)
{
if((format != AL_FORMAT_MONO16) || (scale == 1.f))
return;
S16 * pData = (S16 *)data;
U32 samples = size >> 1;
for(U32 i = 0; i < samples; i++)
{
S32 samp = pData[i];
samp *= scale;
pData[i] = mClamp(samp, S16_MIN, S16_MAX);
}
}
static void alxCaptureSend(bool flush = false)
{
#ifndef __linux
U32 size;
U8 buffer[CAPTURE_BUFFER_SIZE];
do {
size = alCaptureGetData_EXT(buffer, CAPTURE_BUFFER_SIZE, RECORD_FORMAT, RECORD_SPEED);
scaleSamples(buffer, size, RECORD_FORMAT, mCaptureGainScale);
mEncoderStream.setBuffer(buffer, size);
mEncoderStream.process();
} while ((flush && size) || (size == CAPTURE_BUFFER_SIZE));
#else
Con::warnf( ConsoleLogEntry::General, "alxCaptureSend stub");
#endif
}
void alxBufferCapture()
{
#ifndef __linux
S32 len = LOCAL_CAPTURE_SIZE - mLocalCaptureBufferPos;
if(len > 0)
{
U32 size = alCaptureGetData_EXT(mLocalCaptureBuffer + mLocalCaptureBufferPos, len, RECORD_FORMAT, RECORD_SPEED);
scaleSamples(mLocalCaptureBuffer + mLocalCaptureBufferPos, size, RECORD_FORMAT, mCaptureGainScale);
mLocalCaptureBufferPos += size;
}
#else
Con::warnf( ConsoleLogEntry::General, "alxBufferCapture stub");
#endif
}
// called from Miles thread... must process in main thread
void localCaptureFinishedCallback(U32, bool)
{
mLocalCaptureFinished = true;
}
void alxCaptureStop()
{
if(!mCaptureInitialized || !mRecording)
return;
mRecording = false;
sCaptureTimeout = 0;
#ifndef __linux
alCaptureStop_EXT();
#else
Con::warnf( ConsoleLogEntry::General, "alCaptureStop_EXT stub" );
#endif
if(mLocalCapture && mLocalCaptureBuffer)
{
Con::executef(2, "localCaptureStop", "record");
alGenBuffers(1, &mALCaptureBuffer);
if(alGetError() != AL_NO_ERROR)
return;
alBufferData(mALCaptureBuffer, RECORD_FORMAT, mLocalCaptureBuffer, mLocalCaptureBufferPos, RECORD_SPEED);
dFree(mLocalCaptureBuffer);
mLocalCaptureBuffer = 0;
U32 index;
if(findFreeSource(&index))
{
mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT;
mType[index] = Audio::DefaultAudioType;
ALuint source = mSource[index];
#ifdef __linux
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
#else
alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE);
#endif
alSourcei(source, AL_SOURCE_LOOPING, AL_FALSE);
alSourcei(source, AL_BUFFER, mALCaptureBuffer);
alSourcef(source, AL_GAIN_LINEAR, mAudioTypeVolume[Audio::DefaultAudioType]);
alSourceCallback_EXT(source, localCaptureFinishedCallback);
alxPlay(mHandle[index]);
Con::executef(2, "localCaptureStart", "play");
}
else
alDeleteBuffers(1, &mALCaptureBuffer);
}
else
{
alxCaptureSend(true);
mEncoderStream.flush();
}
}
bool alxIsCapturing()
{
return(mRecording);
}
void alxCaptureUpdate()
{
// check if should destroy local capture buffer
if(mLocalCaptureFinished)
{
alDeleteBuffers(1, &mALCaptureBuffer);
Con::executef(2, "localCaptureStop", "play");
mLocalCaptureFinished = false;
}
// check if need to stop capturing
if(mCaptureInitialized && sCaptureTimeout != 0)
{
if((Platform::getRealMilliseconds()-sCaptureTimeout) > RECORD_LEN)
alxCaptureStop();
else
{
if(mLocalCapture)
alxBufferCapture();
else
alxCaptureSend();
}
}
}
//--------------------------------------------------------------------------
// Voice streams
//--------------------------------------------------------------------------
Vector<VoiceStream*>::iterator alxFindStream(U32 clientId, U8 streamId)
{
Vector<VoiceStream*>::iterator itr = sVoiceStreams.begin();
for (; itr != sVoiceStreams.end(); itr++)
{
VoiceStream *vs = *itr;
if (vs->mClientId == clientId && vs->mStreamId == streamId)
return itr;
}
return NULL;
}
Vector<VoiceStream*>::iterator alxNewStream(U32 clientId, U8 streamId)
{
// need to get a new stream
VoiceStream *vs;
if (sVoiceStreams_free.size())
{
vs = sVoiceStreams_free.last();
sVoiceStreams_free.pop_back();
}
else
vs = new VoiceStream;
vs->mClientId = clientId;
vs->mStreamId = streamId;
vs->mDecoder.openStream();
sVoiceStreams.push_back(vs);
return &sVoiceStreams.last();
}
void alxPlayStream(U32 clientId, U8 streamId)
{
Vector<VoiceStream*>::iterator itr = alxFindStream(clientId, streamId);
if (itr == NULL)
return;
Con::evaluatef("clientCmdPlayerStartTalking(%d, 1);", clientId);
VoiceStream *vs = *itr;
alGenBuffers(1, &vs->mBuffer);
vs->mDecoder.process();
U8 *data;
U32 size;
vs->mDecoder.getBuffer(&data, &size);
if(size)
{
alBufferData(vs->mBuffer, RECORD_FORMAT, data, size, RECORD_SPEED);
// find an available source, if none then (hopefully) force a cull
U32 index;
if(!findFreeSource(&index))
if(!cullSource(&index, 2.0f))
return;
// clear the error state
alGetError();
// init and play the source
mHandle[index] = getNewHandle() | AUDIOHANDLE_VOICE_BIT;
mSourceVolume[index] = mAudioTypeVolume[Audio::VoiceAudioType];
vs->mHandle = mHandle[index];
ALuint source = mSource[index];
alSourcei(source, AL_BUFFER, vs->mBuffer);
alSourcei(source, AL_SOURCE_LOOPING, AL_FALSE);
#ifdef __linux
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
#else
alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE);
#endif
alSourcef(source, AL_GAIN_LINEAR, mSourceVolume[index] * mMasterVolume);
alSourcePlay(source);
}
else
Con::printf("Decode size is ZERO! client %d", clientId);
vs->mDecoder.closeStream();
}
//--------------------------------------------------------------------------
void alxReceiveVoiceStream(SimVoiceStreamEvent *event)
{
Vector<VoiceStream*>::iterator itr;
if (event->mSequence == 0)
itr = alxNewStream(event->mClientId, event->mStreamId);
else
itr = alxFindStream(event->mClientId, event->mStreamId);
if (itr)
{
if (event->getSize())
(*itr)->mDecoder.setBuffer(event->getData(), event->getSize());
if (event->getSize() < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE)
alxPlayStream(event->mClientId, event->mStreamId);
}
}
// Music stream: -----------------------------------------------------------
// called from Miles thread... must process in main thread
void streamCallback(U32, bool stopped)
{
mFinishedMusicStream = true;
mFinishedMusicStreamStopState = stopped;
}
void alxPlayMusicStream(const char * filename)
{
if(!alIsBuffer(mStreamBuffer))
{
alGenBuffers(1, &mStreamBuffer);
if(alGetError() != AL_NO_ERROR)
return;
// streamed buffers should not be managed
alBufferi_EXT(mStreamBuffer, AL_BUFFER_KEEP_RESIDENT, AL_TRUE);
}
alxStop(mStreamHandle);
if(!alBufferStreamFile_EXT(mStreamBuffer, (const ALubyte *)filename))
{
const char * error = (const char *)alGetString(alGetError());
Con::errorf(ConsoleLogEntry::General, "alxStartStream: %s", error);
}
U32 index;
if(findFreeSource(&index))
{
mHandle[index] = getNewHandle() | AUDIOHANDLE_INACTIVE_BIT;
mType[index] = Audio::MusicAudioType;
mSourceVolume[index] = mAudioTypeVolume[Audio::MusicAudioType];
ALuint source = mSource[index];
#ifdef __linux
alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
#else
alSourcei(source, AL_SOURCE_AMBIENT, AL_TRUE);
#endif
alSourcei(source, AL_STREAMING, AL_TRUE);
alSourcei(source, AL_BUFFER, mStreamBuffer);
alSourcef(source, AL_GAIN_LINEAR, mSourceVolume[index] * mMasterVolume);
alSourceCallback_EXT(source, streamCallback);
alxPlay(mHandle[index]);
mStreamHandle = mHandle[index];
}
else
{
Con::errorf(ConsoleLogEntry::General, "alxStartStream: no free sources");
alDeleteBuffers(1, &mStreamBuffer);
}
}
void alxStopMusicStream()
{
alxStop(mStreamHandle);
if(alIsBuffer(mStreamBuffer))
alDeleteBuffers(1, &mStreamBuffer);
}
// checks if the music stream is done and calls into console..
void alxStreamUpdate()
{
if(mFinishedMusicStream)
{
Con::executef(2, "finishedMusicStream", mFinishedMusicStreamStopState ? "true" : "false");
mFinishedMusicStream = false;
}
}
//--------------------------------------------------------------------------
// This is a somewhat crappy way of doing this, but under win32 the outer falloff
// is not always the distance at which the gain is clamped to 0 (some drivers
// ignore this setting on the dsound 3dbuffer). So, if this is enabled then
// the outer falloffs of all sources is pushed out to MAX_FALLOFF and the inner ones
// are scaled by the inner falloff scale (some hardware drivers attenuate differently)
// - sources are scaled on the play call as well
// - will trash any non-default falloffs
void alxDisableOuterFalloffs(bool disable)
{
if(disable == mDisableOuterFalloffs)
return;
// update all the playing sources (non-loopers cannot be reset after this)
for(U32 i = 0; i < MAX_AUDIOSOURCES; i++)
{
if(alxIsValidHandle(mHandle[i]))
{
// only care about 3d sources...
ALint ambient = AL_FALSE;
alxGetSourcei(mHandle[i], AL_SOURCE_AMBIENT, &ambient);
if(ambient == AL_FALSE)
continue;
if(disable)
{
ALfloat min = 1.f;
alGetSourcef(mSource[i], AL_MIN_DISTANCE, &min);
alSourcef(mSource[i], AL_MIN_DISTANCE, min * mInnerFalloffScale);
alSourcef(mSource[i], AL_MAX_DISTANCE, FORCED_OUTER_FALLOFF);
}
else
{
LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]);
if(itr)
{
alSourcef(mSource[i], AL_MIN_DISTANCE, (*itr)->mDescription.mMinDistance);
alSourcef(mSource[i], AL_MAX_DISTANCE, (*itr)->mDescription.mMaxDistance);
}
}
}
}
mDisableOuterFalloffs = disable;
}
void alxSetInnerFalloffScale(F32 scale)
{
mInnerFalloffScale = mClampF(scale, 0.1f, 10.f);
}
F32 alxGetInnerFalloffScale()
{
return(mInnerFalloffScale);
}
//--------------------------------------------------------------------------
// Simple metrics
//--------------------------------------------------------------------------
#ifdef GATHER_METRICS
static void alxGatherMetrics()
{
S32 mNumOpenHandles = 0;
S32 mNumOpenLoopingHandles = 0;
S32 mNumOpenVoiceHandles = 0;
S32 mNumActiveStreams = 0;
S32 mNumNullActiveStreams = 0;
S32 mNumActiveLoopingStreams = 0;
S32 mNumActiveVoiceStreams = 0;
S32 mNumLoopingStreams = 0;
S32 mNumInactiveLoopingStreams = 0;
S32 mNumCulledLoopingStreams = 0;
S32 mDynamicMemUsage = 0;
S32 mDynamicMemSize = 0;
S32 mMemUsage = 0;
S32 mBufferCount = 0;
S32 mDynamicBufferCount = 0;
// count installed streams and open handles
for(U32 i = 0; i < mNumSources; i++)
{
if(mHandle[i] != NULL_AUDIOHANDLE)
{
mNumOpenHandles++;
if(mHandle[i] & AUDIOHANDLE_LOOPING_BIT)
mNumOpenLoopingHandles++;
if(mHandle[i] & AUDIOHANDLE_VOICE_BIT)
mNumOpenVoiceHandles++;
}
ALint state = AL_STOPPED;
alGetSourcei(mSource[i], AL_SOURCE_STATE, &state);
if(state == AL_PLAYING)
{
mNumActiveStreams++;
if(mHandle[i] == NULL_AUDIOHANDLE)
mNumNullActiveStreams++;
if(mHandle[i] & AUDIOHANDLE_LOOPING_BIT)
mNumActiveLoopingStreams++;
if(mHandle[i] & AUDIOHANDLE_VOICE_BIT)
mNumActiveVoiceStreams++;
}
}
for(LoopingList::iterator itr = mLoopingList.begin(); itr != mLoopingList.end(); itr++)
mNumLoopingStreams++;
for(LoopingList::iterator itr = mLoopingInactiveList.begin(); itr != mLoopingInactiveList.end(); itr++)
mNumInactiveLoopingStreams++;
for(LoopingList::iterator itr = mLoopingCulledList.begin(); itr != mLoopingCulledList.end(); itr++)
mNumCulledLoopingStreams++;
alGetContexti_EXT(ALC_BUFFER_MEMORY_USAGE, &mMemUsage);
alGetContexti_EXT(ALC_BUFFER_DYNAMIC_MEMORY_SIZE, &mDynamicMemSize);
alGetContexti_EXT(ALC_BUFFER_DYNAMIC_MEMORY_USAGE, &mDynamicMemUsage);
alGetContexti_EXT(ALC_BUFFER_COUNT, &mBufferCount);
alGetContexti_EXT(ALC_BUFFER_DYNAMIC_COUNT, &mDynamicBufferCount);
Con::setIntVariable("Audio::numOpenHandles", mNumOpenHandles);
Con::setIntVariable("Audio::numOpenLoopingHandles", mNumOpenLoopingHandles);
Con::setIntVariable("Audio::numOpenVoiceHandles", mNumOpenVoiceHandles);
Con::setIntVariable("Audio::numActiveStreams", mNumActiveStreams);
Con::setIntVariable("Audio::numNullActiveStreams", mNumNullActiveStreams);
Con::setIntVariable("Audio::numActiveLoopingStreams", mNumActiveLoopingStreams);
Con::setIntVariable("Audio::numActiveVoiceStreams", mNumActiveVoiceStreams);
Con::setIntVariable("Audio::numLoopingStreams", mNumLoopingStreams);
Con::setIntVariable("Audio::numInactiveLoopingStreams", mNumInactiveLoopingStreams);
Con::setIntVariable("Audio::numCulledLoopingStreams", mNumCulledLoopingStreams);
Con::setIntVariable("Audio::memUsage", mMemUsage >> 10);
Con::setIntVariable("Audio::dynamicMemSize", mDynamicMemSize >> 10);
Con::setIntVariable("Audio::dynamicMemUsage", mDynamicMemUsage >> 10);
Con::setIntVariable("Audio::bufferCount", mBufferCount);
Con::setIntVariable("Audio::dynamicBufferCount", mDynamicBufferCount);
}
#endif
//--------------------------------------------------------------------------
// Audio Update...
//--------------------------------------------------------------------------
void alxLoopingUpdate()
{
static LoopingList culledList;
S32 updateTime = Platform::getRealMilliseconds();
// check if can wakeup the inactive loopers
if(mLoopingCulledList.size())
{
Point3F listener;
alxListenerGetPoint3F(AL_POSITION, &listener);
// get the 'sort' value for this sound (could be based on time played...),
// and add to the culled list
LoopingList::iterator itr;
culledList.clear();
for(itr = mLoopingCulledList.begin(); itr != mLoopingCulledList.end(); itr++)
{
if((*itr)->mScore <= MIN_UNCULL_GAIN)
continue;
if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD)
continue;
culledList.push_back(*itr);
}
if(!culledList.size())
return;
U32 index = MAX_AUDIOSOURCES;
if(culledList.size() > 1)
culledList.sort();
for(itr = culledList.begin(); itr != culledList.end(); itr++)
{
if(!findFreeSource(&index))
{
// score does not include master volume
if(!cullSource(&index, (*itr)->mScore))
break;
// check buffer
if(!bool((*itr)->mBuffer))
{
// remove from culled list
LoopingList::iterator tmp;
tmp = mLoopingCulledList.findImage((*itr)->mHandle);
AssertFatal(tmp, "alxLoopingUpdate: failed to find culled source");
mLoopingCulledList.erase_fast(tmp);
// remove from looping list (and free)
tmp = mLoopingList.findImage((*itr)->mHandle);
if(tmp)
{
(*tmp)->clear();
mLoopingFreeList.push_back(*tmp);
mLoopingList.erase_fast(tmp);
}
continue;
}
}
// remove from culled list
LoopingList::iterator tmp = mLoopingCulledList.findImage((*itr)->mHandle);
AssertFatal(tmp, "alxLoopingUpdate: failed to find culled source");
mLoopingCulledList.erase_fast(tmp);
// restore all state data
mHandle[index] = (*itr)->mHandle;
mBuffer[index] = (*itr)->mBuffer;
mScore[index] = (*itr)->mScore;
mSourceVolume[index] = (*itr)->mDescription.mVolume;
mType[index] = (*itr)->mDescription.mType;
mpEffectDescription[index] = (*itr)->mpEffectDescription;
mSampleEnvironment[index] = (*itr)->mEnvironment;
ALuint source = mSource[index];
// setup play info
alGetError();
alxSourcePlay(source, *itr);
if(mEnvironmentEnabled)
alxSourceEnvironment(source, *itr);
alxPlay(mHandle[index]);
}
}
}
//--------------------------------------------------------------------------
void alxCloseHandles()
{
for(U32 i = 0; i < mNumSources; i++)
{
if(mHandle[i] & AUDIOHANDLE_LOADING_BIT)
continue;
if(mHandle[i] == NULL_AUDIOHANDLE)
continue;
ALint state = 0;
alGetSourcei(mSource[i], AL_SOURCE_STATE, &state);
if(state == AL_PLAYING)
continue;
// voice
if(mHandle[i] & AUDIOHANDLE_VOICE_BIT)
{
VectorPtr<VoiceStream*>::iterator itr = alxFindVoiceStream(mHandle[i]);
if(itr)
alxFreeVoiceStream(itr);
}
else if(!(mHandle[i] & AUDIOHANDLE_INACTIVE_BIT))
{
// should be playing? must have encounted an error.. remove
LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]);
if(itr && !((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT))
{
AssertFatal(!mLoopingInactiveList.findImage((*itr)->mHandle), "alxCloseHandles: image incorrectly in inactive list");
AssertFatal(!mLoopingCulledList.findImage((*itr)->mHandle), "alxCloseHandles: image already in culled list");
mLoopingCulledList.push_back(*itr);
(*itr)->mHandle |= AUDIOHANDLE_INACTIVE_BIT;
mHandle[i] = NULL_AUDIOHANDLE;
mBuffer[i] = 0;
}
}
mHandle[i] = NULL_AUDIOHANDLE;
mBuffer[i] = 0;
}
}
//----------------------------------------------------------------------------------
// - update the score for each audio source. this is used for culing sources.
// normal ranges are between 0.f->1.f, voice/loading/music streams are scored
// outside this range so that they will not be culled
// - does not scale by attenuated volumes
void alxUpdateScores(bool sourcesOnly)
{
Point3F listener;
alxGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z);
// do the base sources
for(U32 i = 0; i < mNumSources; i++)
{
if(mHandle[i] == NULL_AUDIOHANDLE)
{
mScore[i] = 0.f;
continue;
}
// thread loading buffer or voice buffer?
if(mHandle[i] & (AUDIOHANDLE_LOADING_BIT|AUDIOHANDLE_VOICE_BIT))
{
mScore[i] = 3.f;
continue;
}
// streaming?
ALint val = AL_FALSE;
alGetSourcei(mSource[i], AL_STREAMING, &val);
if(val == AL_TRUE)
{
mScore[i] = 3.f;
continue;
}
// grab the volume.. (not attenuated by master for score)
F32 volume = mSourceVolume[i] * mAudioTypeVolume[mType[i]];
// 3d?
val = AL_FALSE;
#ifdef __linux
alGetSourcei(mSource[i], AL_SOURCE_RELATIVE, &val);
#else
alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val);
#endif
if(val == AL_FALSE)
{
// approximate 3d volume
Point3F pos;
alGetSourcefv(mSource[i], AL_POSITION, (ALfloat * )((F32*)pos));
ALfloat min, max;
alGetSourcef(mSource[i], AL_MIN_DISTANCE, &min);
alGetSourcef(mSource[i], AL_MAX_DISTANCE, &max);
pos -= listener;
F32 dist = pos.magnitudeSafe();
if(dist >= max)
mScore[i] = 0.f;
else if(dist > min)
mScore[i] = min / dist;
else
mScore[i] = volume;
}
else
mScore[i] = volume;
}
if(sourcesOnly)
return;
S32 updateTime = Platform::getRealMilliseconds();
// update the loopers
for(LoopingList::iterator itr = mLoopingList.begin(); itr != mLoopingList.end(); itr++)
{
if(!((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT))
continue;
if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD)
continue;
if((*itr)->mDescription.mIs3D)
{
Point3F pos = (*itr)->mPosition - listener;
F32 dist = pos.magnitudeSafe();
F32 min = (*itr)->mDescription.mMinDistance;
F32 max = (*itr)->mDescription.mMaxDistance;
if(dist >= max)
(*itr)->mScore = 0.f;
else if(dist > min)
(*itr)->mScore = (*itr)->mDescription.mVolume * (min / dist);
else
(*itr)->mScore = (*itr)->mDescription.mVolume;
}
else
(*itr)->mScore = (*itr)->mDescription.mVolume;
// attenuate by the channel gain
(*itr)->mScore *= mAudioTypeVolume[(*itr)->mDescription.mType];
}
}
// the directx buffers are set to mute at max distance, but many of the providers seem to
// ignore this flag... that is why this is here
void alxUpdateMaxDistance()
{
#ifndef __linux
Point3F listener;
alxGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z);
for(U32 i = 0; i < mNumSources; i++)
{
if(mHandle[i] == NULL_AUDIOHANDLE)
continue;
ALint val = AL_FALSE;
alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val);
if(val == AL_TRUE)
continue;
val = AL_FALSE;
alGetSourcei(mSource[i], AL_STREAMING, &val);
if(val == AL_TRUE)
continue;
Point3F pos;
alGetSourcefv(mSource[i], AL_POSITION, (F32*)pos);
F32 dist = 0.f;
alGetSourcef(mSource[i], AL_MAX_DISTANCE, &dist);
pos -= listener;
dist -= pos.len();
alSourcef(mSource[i], AL_GAIN_LINEAR, (dist < 0.f) ? 0.f : mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume);
}
#endif
}
//--------------------------------------------------------------------------
// Called to update alx system
//--------------------------------------------------------------------------
void alxUpdate()
{
if(mForceMaxDistanceUpdate)
alxUpdateMaxDistance();
alxCloseHandles();
alxUpdateScores(false);
alxLoopingUpdate();
alxCaptureUpdate();
alxStreamUpdate();
#ifndef DISABLE_AUDIO_THREAD
AudioThread::process();
#endif
#ifdef GATHER_METRICS
alxGatherMetrics();
#endif
#ifdef __linux
alxFakeCallbackUpdate();
#endif
}
//--------------------------------------------------------------------------
// Misc
//--------------------------------------------------------------------------
// client-side function only
ALuint alxGetWaveLen(ALuint buffer)
{
if(buffer == AL_INVALID)
return(0);
ALint frequency = 0;
ALint bits = 0;
ALint channels = 0;
ALint size;
alGetBufferi(buffer, AL_FREQUENCY, &frequency);
alGetBufferi(buffer, AL_BITS, &bits);
alGetBufferi(buffer, AL_CHANNELS, &channels);
alGetBufferi(buffer, AL_SIZE, &size);
if(!frequency || !bits || !channels)
{
Con::errorf(ConsoleLogEntry::General, "alxGetWaveLen: invalid buffer");
return(0);
}
F64 len = (F64(size) * 8000.f) / F64(frequency * bits * channels);
return(len);
}
//--------------------------------------------------------------------------
// Console functions
//--------------------------------------------------------------------------
static void cAudio_detect(SimObject *, S32, const char **)
{
Audio::detect();
}
static void cAudio_destroy(SimObject *, S32, const char **)
{
Audio::destroy();
}
static bool cAudio_setDriver(SimObject *, S32, const char *argv[])
{
return(Audio::setDriver(argv[1]));
}
//--------------------------------------------------------------------------
static S32 cAudio_alxCreateSource(SimObject *, S32 argc, const char *argv[])
{
AudioDescription *description = NULL;
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
description = dynamic_cast<AudioDescription*>( Sim::findObject( argv[1] ) );
if (description == NULL)
{
Con::printf("Unable to locate audio profile/description '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
}
if (profile)
{
if (argc == 2)
return alxCreateSource(profile);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) ));
return alxCreateSource(profile, &transform);
}
if (description)
{
if (argc == 3)
return alxCreateSource(description, argv[2]);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) ));
return alxCreateSource(description, argv[2], &transform);
}
return NULL_AUDIOHANDLE;
}
//--------------------------------------------------------------------------
// Expose all al get/set methods...
//--------------------------------------------------------------------------
enum {
Source = BIT(0),
Listener = BIT(1),
Context = BIT(2),
Environment = BIT(3),
Get = BIT(4),
Set = BIT(5),
Int = BIT(6),
Float = BIT(7),
Float3 = BIT(8),
Float6 = BIT(9),
DebugGet = BIT(10),
DebugSet = BIT(11),
Debug = (DebugGet|DebugSet),
};
static ALenum getEnum(const char * name, U32 flags)
{
AssertFatal(name, "getEnum: bad param");
static struct {
char * mName;
ALenum mAlenum;
U32 mFlags;
} table[] = {
//-----------------------------------------------------------------------------------------------------------------
// "name" ENUM Flags
//-----------------------------------------------------------------------------------------------------------------
{ "AL_PAN", AL_PAN, (Source|Get|Set|Float) },
{ "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) },
{ "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) },
{ "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) },
{ "AL_MIN_DISTANCE", AL_MIN_DISTANCE, (Source|Get|Set|Float) },
{ "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) },
{ "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) },
{ "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) },
{ "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) },
{ "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) },
{ "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) },
{ "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) },
{ "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) },
{ "AL_SOURCE_LOOPING", AL_SOURCE_LOOPING, (Source|Get|Set|Int) },
{ "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) },
{ "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) },
#ifdef __linux
// can't 1 to 1 map this because we need to set the position
#endif
{ "AL_SOURCE_AMBIENT", AL_SOURCE_AMBIENT, (Source|Get|Set|Int) },
{ "ALC_PROVIDER", ALC_PROVIDER, (Context|Get|Set|Int) },
{ "ALC_PROVIDER_COUNT", ALC_PROVIDER_COUNT, (Context|Get|Int) },
{ "ALC_PROVIDER_NAME", ALC_PROVIDER_NAME, (Context|Get|Int) },
{ "ALC_SPEAKER", ALC_SPEAKER, (Context|Get|Set|Int) },
{ "ALC_SPEAKER_COUNT", ALC_SPEAKER_COUNT, (Context|Get|Int) },
{ "ALC_SPEAKER_NAME", ALC_SPEAKER_NAME, (Context|Get|Int) },
{ "ALC_BUFFER_DYNAMIC_MEMORY_SIZE", ALC_BUFFER_DYNAMIC_MEMORY_SIZE, (Context|Get|Set|Int) },
{ "ALC_BUFFER_DYNAMIC_MEMORY_USAGE",ALC_BUFFER_DYNAMIC_MEMORY_USAGE, (Context|Get|Int) },
{ "ALC_BUFFER_DYNAMIC_COUNT", ALC_BUFFER_DYNAMIC_COUNT, (Context|Get|Int) },
{ "ALC_BUFFER_MEMORY_USAGE", ALC_BUFFER_MEMORY_USAGE, (Context|Get|Int) },
{ "ALC_BUFFER_COUNT", ALC_BUFFER_COUNT, (Context|Get|Int) },
{ "ALC_BUFFER_LATENCY", ALC_BUFFER_LATENCY, (Context|Get|Int) },
// environment
{ "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) },
// sample environment
{ "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) },
};
for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++)
{
#ifdef DEBUG
if(((table[i].mFlags & ~Debug) & flags) != flags)
continue;
#else
if((table[i].mFlags & flags) != flags)
continue;
#endif
if(!dStricmp(table[i].mName, name))
return(table[i].mAlenum);
}
return(AL_INVALID);
}
//--------------------------------------------------------------------------
// Source
//--------------------------------------------------------------------------
static void cAudio_alxSourcef(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]);
return;
}
alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3]));
}
static void cAudio_alxSource3f(SimObject *, S32 argc, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]);
return;
}
if(argc != 3 || argc != 6)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args");
return;
}
Point3F pos;
if(argc == 3)
dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[1]);
pos.y = dAtof(argv[2]);
pos.z = dAtof(argv[3]);
}
alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z);
}
static void cAudio_alxSourcei(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]);
return;
}
alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3]));
}
//--------------------------------------------------------------------------
static F32 cAudio_alxGetSourcef(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]);
return(0.f);
}
F32 value;
alxGetSourcef(dAtoi(argv[1]), e, &value);
return(value);
}
static const char * cAudio_alxGetSource3f(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]);
return("0 0 0");
}
F32 value1, value2, value3;
alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
static S32 cAudio_alxGetSourcei(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]);
return(0);
}
S32 value;
alxGetSourcei(dAtoi(argv[1]), e, &value);
return(value);
}
//--------------------------------------------------------------------------
// Listener
//--------------------------------------------------------------------------
static void cAudio_alxListenerf(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Listener|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxListenerf: invalid enum name '%s'", argv[1]);
return;
}
alxListenerf(e, dAtof(argv[2]));
}
static void cAudio_alxListener3f(SimObject *, S32 argc, const char ** argv)
{
ALenum e = getEnum(argv[1], (Listener|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxListener3f: invalid enum name '%s'", argv[1]);
return;
}
if(argc != 3 || argc != 5)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxListener3f: wrong number of args");
return;
}
Point3F pos;
if(argc == 3)
dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[2]);
pos.y = dAtof(argv[3]);
pos.z = dAtof(argv[4]);
}
alxListener3f(e, pos.x, pos.y, pos.z);
}
//--------------------------------------------------------------------------
static F32 cAudio_alxGetListenerf(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListenerf: invalid enum name '%s'", argv[1]);
return(0.f);
}
F32 value;
alxGetListenerf(e, &value);
return(value);
}
static const char * cAudio_alxGetListener3f(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListener3f: invalid enum name '%s'", argv[1]);
return("0 0 0");
}
F32 value1, value2, value3;
alxGetListener3f(e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
static S32 cAudio_alxGetListeneri(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetListeneri: invalid enum name '%s'", argv[1]);
return(0);
}
S32 value;
alxGetListeneri(e, &value);
return(value);
}
//--------------------------------------------------------------------------
// Play/Stop
//--------------------------------------------------------------------------
static S32 cAudio_alxPlay(SimObject *, S32 argc, const char *argv[])
{
if (argc == 2)
{
AUDIOHANDLE handle = dAtoi(argv[1]);
return alxPlay(handle);
}
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
Con::printf("Unable to locate audio profile '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
Point3F pos(0.f, 0.f, 0.f);
if(argc == 3)
dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z);
else if(argc == 5)
pos.set(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
MatrixF transform;
transform.set(EulerF(0,0,0), pos);
return alxPlay(profile, &transform, NULL);
}
static void cAudio_alxStop(SimObject *, S32, const char ** argv)
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return;
alxStop(handle);
}
static void cAudio_alxStopAll(SimObject *, S32, const char **)
{
alxStopAll();
}
//--------------------------------------------------------------------------
// Capture
//--------------------------------------------------------------------------
static bool cAudio_alxCaptureInit(SimObject *, S32, const char **)
{
return(alxCaptureInit());
}
static void cAudio_alxCaptureDestroy(SimObject *, S32, const char **)
{
alxCaptureDestroy();
}
static void cAudio_alxCaptureStart(SimObject *, S32 argc, const char ** argv)
{
alxCaptureStart((argc == 2) ? dAtob(argv[1]) : false);
}
static void cAudio_alxCaptureStop(SimObject *, S32, const char **)
{
alxCaptureStop();
}
static bool cAudio_alxIsCapturing(SimObject *, S32, const char **)
{
return(alxIsCapturing());
}
//--------------------------------------------------------------------------
// Environment:
//--------------------------------------------------------------------------
void alxEnvironmenti(ALenum pname, ALint value)
{
alEnvironmentiIASIG(mEnvironment, pname, value);
}
void alxEnvironmentf(ALenum pname, ALfloat value)
{
alEnvironmentfIASIG(mEnvironment, pname, value);
}
void alxGetEnvironmenti(ALenum pname, ALint * value)
{
alGetEnvironmentiIASIG_EXT(mEnvironment, pname, value);
}
void alxGetEnvironmentf(ALenum pname, ALfloat * value)
{
alGetEnvironmentfIASIG_EXT(mEnvironment, pname, value);
}
void alxEnableEnvironmental(bool enable)
{
if(mEnvironmentEnabled == enable)
return;
// go through the playing sources and update their reverb mix
// - only 3d samples get environmental fx
// - only loopers can reenable fx
for(U32 i = 0; i < MAX_AUDIOSOURCES; i++)
{
if(mHandle[i] == NULL_AUDIOHANDLE)
continue;
ALint val = AL_FALSE;
// 3d?
alGetSourcei(mSource[i], AL_SOURCE_AMBIENT, &val);
if(val == AL_TRUE)
continue;
// stopped?
val = AL_STOPPED;
alGetSourcei(mSource[i], AL_SOURCE_STATE, &val);
// only looping sources can reenable environmental effects (no description around
// for the non-loopers)
if(enable)
{
LoopingList::iterator itr = mLoopingList.findImage(mHandle[i]);
if(!itr)
continue;
alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, (*itr)->mDescription.mEnvironmentLevel);
}
else
alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, 0.f);
}
mEnvironmentEnabled = enable;
}
void alxSetEnvironment(const AudioEnvironment * env)
{
mCurrentEnvironment = const_cast<AudioEnvironment*>(env);
// reset environmental audio?
if(!env)
{
alxEnvironmenti(AL_ENV_ROOM_IASIG, AL_ENVIRONMENT_GENERIC);
return;
}
// room trashes all the values
if(env->mUseRoom)
{
alxEnvironmenti(AL_ENV_ROOM_IASIG, env->mRoom);
return;
}
// set all the params
alxEnvironmenti(AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, env->mRoomHF);
alxEnvironmenti(AL_ENV_REFLECTIONS_IASIG, env->mReflections);
alxEnvironmenti(AL_ENV_REVERB_IASIG, env->mReverb);
alxEnvironmentf(AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, env->mRoomRolloffFactor);
alxEnvironmentf(AL_ENV_DECAY_TIME_IASIG, env->mDecayTime);
alxEnvironmentf(AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, env->mDecayTime);
alxEnvironmentf(AL_ENV_REFLECTIONS_DELAY_IASIG, env->mReflectionsDelay);
alxEnvironmentf(AL_ENV_REVERB_DELAY_IASIG, env->mReverbDelay);
alxEnvironmentf(AL_ENV_DENSITY_IASIG, env->mAirAbsorption);
alxEnvironmentf(AL_ENV_DIFFUSION_IASIG, env->mEnvironmentDiffusion);
// ext:
alxEnvironmenti(AL_ENV_ROOM_VOLUME_EXT, env->mRoomVolume);
alxEnvironmenti(AL_ENV_FLAGS_EXT, env->mFlags);
alxEnvironmentf(AL_ENV_EFFECT_VOLUME_EXT, env->mEffectVolume);
alxEnvironmentf(AL_ENV_DAMPING_EXT, env->mDamping);
alxEnvironmentf(AL_ENV_ENVIRONMENT_SIZE_EXT, env->mEnvironmentSize);
}
const AudioEnvironment * alxGetEnvironment()
{
return(mCurrentEnvironment);
}
//--------------------------------------------------------------------------
static void cAudio_alxEnvironmenti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmenti: invalid enum name '%s'", argv[1]);
return;
}
alxEnvironmenti(e, dAtoi(argv[2]));
}
static void cAudio_alxEnvironmentf(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmentf: invalid enum name '%s'", argv[1]);
return;
}
alxEnvironmenti(e, dAtof(argv[2]));
}
static S32 cAudio_alxGetEnvironmenti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmenti: invalid enum name '%s'", argv[1]);
return(0);
}
S32 value;
alxGetEnvironmenti(e, &value);
return(value);
}
static F32 cAudio_alxGetEnvironmentf(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmentf: invalid enum name '%s'", argv[1]);
return(0.f);
}
F32 value;
alxGetEnvironmentf(e, &value);
return(value);
}
static void cAudio_alxSetEnvironment(SimObject *, S32, const char ** argv)
{
AudioEnvironment * environment = dynamic_cast<AudioEnvironment*>(Sim::findObject(argv[1]));
alxSetEnvironment(environment);
}
static void cAudio_alxEnableEnvironmental(SimObject *, S32, const char ** argv)
{
alxEnableEnvironmental(dAtob(argv[1]));
}
//--------------------------------------------------------------------------
// Misc
//--------------------------------------------------------------------------
static S32 cAudio_alxGetWaveLen(SimObject *, S32, const char ** argv)
{
// filename or profile?
AudioProfile * profile = 0;
const char * fileName = 0;
if(!dStrrchr(argv[1], '.'))
{
profile = dynamic_cast<AudioProfile*>(Sim::findObject(argv[1]));
if(!profile)
{
Con::errorf(ConsoleLogEntry::General, "Unable to locate audio profile: '%s'", argv[1]);
return(0);
}
fileName = profile->mFilename;
}
else
fileName = argv[1];
Resource<AudioBuffer> buffer = AudioBuffer::find(fileName);
if(!bool(buffer))
{
Con::errorf(ConsoleLogEntry::General, "Failed to find wave file: '%s'", fileName);
return(0);
}
ALuint alBuffer = buffer->getALBuffer(true);
if(alBuffer == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetWaveLen: invalid buffer");
return(0);
}
return(alxGetWaveLen(alBuffer));
}
static const char* cGetAudioDriverList( SimObject*, S32, const char** )
{
return( Audio::getDriverListString() );
}
static const char* cGetAudioDriverInfo( SimObject*, S32, const char** )
{
return( Audio::getCurrentDriverInfo() );
}
static F32 cAudio_alxGetChannelVolume(SimObject *, S32, const char ** argv)
{
U32 type = dAtoi(argv[1]);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1]));
return(0.f);
}
return(mAudioTypeVolume[type]);
}
static void cAudio_alxSetChannelVolume(SimObject *, S32, const char ** argv)
{
U32 type = dAtoi(argv[1]);
F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f);
if(type >= Audio::NumAudioTypes)
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSetChannelVolume: invalid channel '%d'", dAtoi(argv[1]));
else
{
mAudioTypeVolume[type] = volume;
alxUpdateTypeGain(1 << type);
}
}
static void cAudio_alxSetCaptureGainScale(SimObject *, S32, const char ** argv)
{
mCaptureGainScale = mClampF(dAtof(argv[1]), MIN_CAPTURE_SCALE, MAX_CAPTURE_SCALE);
}
static F32 cAudio_alxGetCaptureGainScale(SimObject *, S32, const char **)
{
return(mCaptureGainScale);
}
static void cAudio_alxForceMaxDistanceUpdate(SimObject *, S32, const char ** argv)
{
mForceMaxDistanceUpdate = dAtob(argv[1]);
}
static void cAudio_alxDisableOuterFalloffs(SimObject *, S32, const char ** argv)
{
alxDisableOuterFalloffs(dAtob(argv[1]));
}
static void cAudio_alxSetInnerFalloffScale(SimObject *, S32, const char ** argv)
{
alxSetInnerFalloffScale(dAtof(argv[1]));
}
static F32 cAudio_alxGetInnerFalloffScale(SimObject *, S32, const char **)
{
return(alxGetInnerFalloffScale());
}
// Music: ----------------------------------------------------------------
static void cAudio_alxPlayMusic(SimObject *, S32, const char ** argv)
{
alxPlayMusicStream(StringTable->insert(argv[1]));
}
static void cAudio_alxStopMusic(SimObject *, S32, const char **)
{
alxStopMusicStream();
}
// Context: ----------------------------------------------------------------
void alxContexti(ALenum pname, ALint value)
{
alContexti_EXT(pname, value);
}
void alxGetContexti(ALenum pname, ALint * value)
{
alGetContexti_EXT(pname, value);
}
void alxGetContextstr(ALenum pname, ALuint idx, ALubyte ** value)
{
alGetContextstr_EXT(pname, idx, value);
}
static void cAudio_alxContexti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Context|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxContexti: invalid enum name '%s'", argv[1]);
return;
}
alxContexti(e, dAtoi(argv[2]));
}
static S32 cAudio_alxGetContexti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Context|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContexti: invalid enum name '%s'", argv[2]);
return(0);
}
ALint value = 0;
alxGetContexti(e, &value);
return(value);
}
static const char * cAudio_alxGetContextstr(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Context|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContextstr: invalid enum name '%s'", argv[2]);
return("");
}
ALubyte * str = (ALubyte *)"";
alxGetContextstr(e, dAtoi(argv[2]), &str);
return(StringTable->insert((const char *)str));
}
static bool cAudio_isEnabled(SimObject *, S32, const char ** argv)
{
if(!dStricmp(argv[1], "system"))
return(mInitialized);
if(!dStricmp(argv[1], "capture"))
return(mCaptureInitialized);
if(!dStricmp(argv[1], "environment_iasig"))
return(mEnvironment && mEnvironmentEnabled);
return(false);
}
static bool cAudio_isExtensionPresent(SimObject *, S32, const char ** argv)
{
return((bool)alIsExtensionPresent((const ALubyte *)argv[1]));
}
// Namespace: Audio ---------------------------------------------------------
namespace Audio
{
//---------------------------------------------------------------------------
// the following db<->linear conversion functions come from Loki openAL linux driver
// code, here more for completeness than anything else (all current audio code
// uses AL_GAIN_LINEAR)... in Audio:: so that looping updates and audio channel updates
// can convert gain types and to give the miles driver access
static const F32 logtab[] = {
0.00, 0.001, 0.002, 0.003, 0.004,
0.005, 0.01, 0.011, 0.012, 0.013,
0.014, 0.015, 0.016, 0.02, 0.021,
0.022, 0.023, 0.024, 0.025, 0.03,
0.031, 0.032, 0.033, 0.034, 0.04,
0.041, 0.042, 0.043, 0.044, 0.05,
0.051, 0.052, 0.053, 0.054, 0.06,
0.061, 0.062, 0.063, 0.064, 0.07,
0.071, 0.072, 0.073, 0.08, 0.081,
0.082, 0.083, 0.084, 0.09, 0.091,
0.092, 0.093, 0.094, 0.10, 0.101,
0.102, 0.103, 0.11, 0.111, 0.112,
0.113, 0.12, 0.121, 0.122, 0.123,
0.124, 0.13, 0.131, 0.132, 0.14,
0.141, 0.142, 0.143, 0.15, 0.151,
0.152, 0.16, 0.161, 0.162, 0.17,
0.171, 0.172, 0.18, 0.181, 0.19,
0.191, 0.192, 0.20, 0.201, 0.21,
0.211, 0.22, 0.221, 0.23, 0.231,
0.24, 0.25, 0.251, 0.26, 0.27,
0.271, 0.28, 0.29, 0.30, 0.301,
0.31, 0.32, 0.33, 0.34, 0.35,
0.36, 0.37, 0.38, 0.39, 0.40,
0.41, 0.43, 0.50, 0.60, 0.65,
0.70, 0.75, 0.80, 0.85, 0.90,
0.95, 0.97, 0.99 };
const int logmax = sizeof logtab / sizeof *logtab;
F32 DBToLinear(F32 value)
{
if(value <= 0.f)
return(0.f);
if(value >= 1.f)
return(1.f);
S32 max = logmax;
S32 min = 0;
S32 mid;
S32 last = -1;
mid = (max - min) / 2;
do {
last = mid;
if(logtab[mid] == value)
break;
if(logtab[mid] < value)
min = mid;
else
max = mid;
mid = min + ((max - min) / 2);
} while(last != mid);
return((F32)mid / logmax);
}
F32 linearToDB(F32 value)
{
if(value <= 0.f)
return(0.f);
if(value >= 1.f)
return(1.f);
return(logtab[(U32)(logmax * value)]);
}
//---------------------------------------------------------------------------
static ALvoid errorCallback(ALbyte *msg)
{
// used to allow our OpenAL implementation to display info on the console
Con::errorf(ConsoleLogEntry::General, (const char *)msg);
}
//--------------------------------------------------------------------------
bool prepareContext()
{
// mForceMaxDistanceUpdate = Con::getBoolVariable("$pref::audio::forceMaxDistanceUpdate", false);
mForceMaxDistanceUpdate = false;
// allocate source channels: can only get 16 sources on NT, so check the max before
// jff: todo, allow for more than 16 channels on non-NT boxes
mNumSources = mRequestSources;
alGenSources(mRequestSources, mSource);
// invalidate all existing handles
dMemset(mHandle, NULL_AUDIOHANDLE, sizeof(mHandle));
mCaptureInitialized = AL_FALSE;
// pre-load profile data
SimGroup* grp = Sim::getDataBlockGroup();
if (grp != NULL)
{
SimObjectList::iterator itr = grp->begin();
for (; itr != grp->end(); itr++)
{
AudioProfile *profile = dynamic_cast<AudioProfile*>(*itr);
if(profile != NULL && profile->isPreload())
{
Resource<AudioBuffer> buffer = AudioBuffer::find(profile->mFilename);
if((bool)buffer)
{
ALuint bufferId = buffer->getALBuffer(true);
alBufferi_EXT(bufferId, AL_BUFFER_KEEP_RESIDENT, AL_TRUE);
}
}
}
}
mInitialized = true;
return(true);
}
void shutdownContext()
{
alxCaptureStop();
// invalidate active handles
dMemset(mSource, 0, sizeof(mSource));
}
//--------------------------------------------------------------------------
void init()
{
#ifdef __linux
alutInit( 0, 0 );
libraryInitExtensions( );
prepareContext( );
#ifdef DEBUG
alBombOnError_LOKI( );
#endif
#endif
Con::addCommand("AudioDetect", cAudio_detect, "AudioDetect()", 1, 1);
Con::addCommand("AudioDestroy", cAudio_destroy, "AudioDestroy()", 1, 1);
Con::addCommand("AudioSetDriver", cAudio_setDriver, "AudioSetDriver(name)", 2, 2);
Con::addCommand("alxIsEnabled", cAudio_isEnabled, "alxIsEnabled(name)", 2, 2);
Con::addCommand("alxIsExtensionPresent", cAudio_isExtensionPresent, "alxIsExtensionPresent(name)", 2, 2);
Con::addCommand("alxCreateSource", cAudio_alxCreateSource, "alxCreateSource(profile, {x,y,z} | description, filename, {x,y,z})", 2, 6);
Con::addCommand("alxSourcef", cAudio_alxSourcef, "alxSourcef(handle, ALenum, value)", 4, 4);
Con::addCommand("alxSource3f", cAudio_alxSource3f, "alxSource3f(handle, ALenum, \"x y z\" | x, y, z)", 3, 6);
Con::addCommand("alxSourcei", cAudio_alxSourcei, "alxSourcei(handle, ALenum, value)", 4, 4);
Con::addCommand("alxGetSourcef", cAudio_alxGetSourcef, "alxGetSourcef(handle, ALenum)", 3, 3);
Con::addCommand("alxGetSource3f", cAudio_alxGetSource3f, "alxGetSource3f(handle, ALenum)", 3, 3);
Con::addCommand("alxGetSourcei", cAudio_alxGetSourcei, "alxGetSourcei(handle, ALenum)", 3, 3);
Con::addCommand("alxListenerf", cAudio_alxListenerf, "alxListenerf(ALenum, value)", 3, 3);
Con::addCommand("alxListener3f", cAudio_alxListener3f, "alxListener3f(ALenum, \"x y z\" | x, y, z)", 3, 5);
Con::addCommand("alxGetListenerf", cAudio_alxGetListenerf, "alxGetListenerf(Alenum)", 2, 2);
Con::addCommand("alxGetListener3f", cAudio_alxGetListener3f, "alxGetListener3f(Alenum)", 2, 2);
Con::addCommand("alxGetListeneri", cAudio_alxGetListeneri, "alxGetListeneri(Alenum)", 2, 2);
Con::addCommand("alxContexti", cAudio_alxContexti, "alxContexti(Alenum, value)", 3, 3);
Con::addCommand("alxGetContexti", cAudio_alxGetContexti, "alxGetContexti(Alenum)", 2, 2);
Con::addCommand("alxGetContextstr", cAudio_alxGetContextstr, "alxGetContextstr(Alenum, idx)", 3, 3);
Con::addCommand("alxPlay", cAudio_alxPlay, "alxPlay(handle) | alxPlay(profile, {x,y,z})", 2, 5);
Con::addCommand("alxStop", cAudio_alxStop, "alxStop(handle)", 2, 2);
Con::addCommand("alxStopAll", cAudio_alxStopAll, "alxStopAll()", 1, 1);
Con::addCommand("alxCaptureInit", cAudio_alxCaptureInit, "alxCaptureInit()", 1, 1);
Con::addCommand("alxCaptureDestroy", cAudio_alxCaptureDestroy, "alxCaptureDestroy()", 1, 1);
Con::addCommand("alxCaptureStart", cAudio_alxCaptureStart, "alxCaptureStart(<local>)", 1, 2);
Con::addCommand("alxCaptureStop", cAudio_alxCaptureStop, "alxCaptureStop()", 1, 1);
Con::addCommand("alxIsCapturing", cAudio_alxIsCapturing, "alxIsCapturing()", 1, 1);
Con::addCommand("alxEnvironmenti", cAudio_alxEnvironmenti, "alxEnvironmenti(Alenum, value)", 3, 3);
Con::addCommand("alxEnvironmentf", cAudio_alxEnvironmentf, "alxEnvironmentf(Alenum, value)", 3, 3);
Con::addCommand("alxGetEnvironmenti", cAudio_alxGetEnvironmenti, "alxGetEnvironmenti(Alenum)", 2, 2);
Con::addCommand("alxGetEnvironmentf", cAudio_alxGetEnvironmentf, "alxGetEnvironmentf(Alenum)", 2, 2);
Con::addCommand("alxSetEnvironment", cAudio_alxSetEnvironment, "alxSetEnvironment(AudioEnvironmentData)", 2, 2);
Con::addCommand("alxEnableEnvironmental", cAudio_alxEnableEnvironmental, "alxEnableEnvironmental(bool)", 2, 2 );
Con::addCommand("alxGetWaveLen", cAudio_alxGetWaveLen, "alxGetWaveLen(profile|filename)", 2, 2);
Con::addCommand("getAudioDriverList", cGetAudioDriverList, "getAudioDriverList();", 1, 1);
Con::addCommand("getAudioDriverInfo", cGetAudioDriverInfo, "getAudioDriverInfo();", 1, 1);
ResourceManager->registerExtension(".wav", AudioBuffer::construct);
#ifdef IMMERSION_SUPPORT
ResourceManager->registerExtension(".ifr", CImmSupport::construct);
#endif
Con::setIntVariable("DefaultAudioType", Audio::DefaultAudioType);
Con::setIntVariable("ChatAudioType", Audio::ChatAudioType);
Con::setIntVariable("GuiAudioType", Audio::GuiAudioType);
Con::setIntVariable("EffectAudioType", Audio::EffectAudioType);
Con::setIntVariable("VoiceAudioType", Audio::VoiceAudioType);
Con::setIntVariable("MusicAudioType", Audio::MusicAudioType);
Con::addCommand("alxGetChannelVolume", cAudio_alxGetChannelVolume, "alxGetChannelVolume(channel)", 2, 2);
Con::addCommand("alxSetChannelVolume", cAudio_alxSetChannelVolume, "alxSetChannelVolume(channel, volume)", 3, 3);
Con::addCommand("alxSetCaptureGainScale", cAudio_alxSetCaptureGainScale, "alxSetCaptureGainScale(scale)", 2, 2);
Con::addCommand("alxGetCaptureGainScale", cAudio_alxGetCaptureGainScale, "alxGetCaptureGainScale()", 1, 1);
Con::addCommand("alxPlayMusic", cAudio_alxPlayMusic, "alxPlayMusic(file)", 2, 2);
Con::addCommand("alxStopMusic", cAudio_alxStopMusic, "alxStopMusic()", 1, 1);
Con::addCommand("alxDisableOuterFalloffs", cAudio_alxDisableOuterFalloffs, "alxDisableOuterFalloffs(bool)", 2, 2);
Con::addCommand("alxSetInnerFalloffScale", cAudio_alxSetInnerFalloffScale, "alxSetInnerFalloffScale(scale)", 2, 2);
Con::addCommand("alxGetInnerFalloffScale", cAudio_alxGetInnerFalloffScale, "alxGetInnerFalloffScale()", 1, 1);
Con::addCommand("alxForceMaxDistanceUpdate", cAudio_alxForceMaxDistanceUpdate, "alxForceMaxDistanceUpdate(bool)", 2, 2);
// default all channels to full gain
for(U32 i = 0; i < Audio::NumAudioTypes; i++)
mAudioTypeVolume[i] = 1.f;
#ifndef DISABLE_AUDIO_THREAD
AudioThread::create();
#endif
}
//--------------------------------------------------------------------------
void detect()
{
// destroy method takes down the audio thread
if(mDriverInfoList.size())
{
destroy();
#ifndef DISABLE_AUDIO_THREAD
AudioThread::create();
#endif
}
DriverInfo di;
di.mName = dStrdup("None");
di.mVender = dStrdup((const char*)alGetString(AL_VENDOR));
di.mVersion = dStrdup((const char*)alGetString(AL_VERSION));
di.mRenderer = dStrdup((const char*)alGetString(AL_RENDERER));
di.mExtensions = dStrdup((const char*)alGetString(AL_EXTENSIONS));
mDriverInfoList.push_back(di);
mActiveDriver = mDriverInfoList.begin();
#ifndef __linux
const char *str = Con::getVariable("$pref::Audio::drivers");
if (str)
{
char driverString[1024];
dStrcpy(driverString, str);
char *tok = dStrtok(driverString, " \t");
while (tok != NULL)
{
if(libraryInit((const char *)tok))
{
if(alGetError() == AL_NO_ERROR)
{
if(alIsExtensionPresent((const ALubyte *)"AL_EXT_DYNAMIX"))
{
di.mName = dStrdup(tok);
di.mVender = dStrdup((const char*)alGetString(AL_VENDOR));
di.mVersion = dStrdup((const char*)alGetString(AL_VERSION));
di.mRenderer = dStrdup((const char*)alGetString(AL_RENDERER));
di.mExtensions = dStrdup((const char*)alGetString(AL_EXTENSIONS));
mDriverInfoList.push_back(di);
}
}
libraryShutdown();
}
tok = dStrtok(NULL, " \t");
}
}
#endif
}
//--------------------------------------------------------------------------
void destroy()
{
#ifndef __linux
// FIXME: weird race condition
AudioThread::destroy();
#endif
alxCaptureDestroy();
if(mInitialized)
{
if(mEnvironment)
{
alDeleteEnvironmentIASIG(1, (ALuint*)&mEnvironment);
mEnvironment = 0;
}
alutExit();
}
libraryShutdown();
while(mLoopingList.size())
{
mLoopingList.last()->mBuffer.purge();
delete mLoopingList.last();
mLoopingList.pop_back();
}
while(mLoopingFreeList.size())
{
mLoopingFreeList.last()->mBuffer.purge();
delete mLoopingFreeList.last();
mLoopingFreeList.pop_back();
}
mInitialized = false;
while (mDriverInfoList.size())
{
dFree(mDriverInfoList.last().mName);
dFree(mDriverInfoList.last().mVender);
dFree(mDriverInfoList.last().mVersion);
dFree(mDriverInfoList.last().mRenderer);
dFree(mDriverInfoList.last().mExtensions);
mDriverInfoList.pop_back();
}
mActiveDriver = mDriverInfoList.begin();
for(U32 i = 0; i < MAX_AUDIOSOURCES; i++)
mBuffer[i] = 0;
}
//--------------------------------------------------------------------------
bool setDriver(const char *name)
{
// handle a couple special keywords
if (name == NULL || dStricmp(name, "none") == 0)
name = mDriverInfoList.first().mName;
if (dStricmp(name, "default") == 0)
name = mDriverInfoList.last().mName;
// don't reset if driver is already active
if (mActiveDriver && (dStricmp(name, mActiveDriver->mName) == 0) )
return true;
DriverInfo *newDriver = NULL;
// locate the driver
Vector<DriverInfo>::iterator itr = mDriverInfoList.begin();
for (; itr != mDriverInfoList.end(); itr++)
{
if (dStricmp(name, itr->mName) == 0)
{
newDriver = itr;
break;
}
}
// no driver by that name
if (newDriver == NULL)
{
Con::errorf(ConsoleLogEntry::General, "Unknown OpenAL audio driver '%s'", name );
return false;
}
// shut down existing driver
if (mActiveDriver)
{
alxCaptureDestroy();
alutExit();
libraryShutdown();
mActiveDriver = NULL;
}
// stub driver
if (newDriver == mDriverInfoList.begin())
{
libraryShutdown();
prepareContext();
mActiveDriver = mDriverInfoList.begin();
return true;
}
// load driver
if(!libraryInit((const char *)name))
{
// install stub driver
libraryShutdown();
mActiveDriver = mDriverInfoList.begin();
return(false);
}
S32 attribs[] = { ALC_FREQUENCY, 0,
ALC_RESOLUTION, 0,
ALC_CHANNELS, 0, 0 };
// grab the console prefs
attribs[1] = Con::getIntVariable("$pref::Audio::frequency", ALX_DEF_SAMPLE_RATE);
attribs[3] = Con::getIntVariable("$pref::Audio::sampleBits", ALX_DEF_SAMPLE_BITS);
attribs[5] = Con::getIntVariable("$pref::Audio::channels", ALX_DEF_CHANNELS);
// initialize the system
alutInit(attribs, 0);
if(alGetError() != AL_NO_ERROR)
{
// exit, load stub
alutExit();
libraryShutdown();
mActiveDriver = mDriverInfoList.begin();
return(false);
}
// must have dynamix extension
if(alIsExtensionPresent((const ALubyte *)"AL_EXT_DYNAMIX"))
{
// bind the _EXT methods
libraryInitExtensions();
prepareContext();
mActiveDriver = itr;
Con::setVariable("$pref::Audio::activeDriver", mActiveDriver->mName);
mInitialized = true;
// generate an environment if present
if(alIsExtensionPresent((const ALubyte *)"AL_EXT_IASIG"))
{
alGenEnvironmentIASIG(1, &mEnvironment);
if(alGetError() != AL_NO_ERROR)
mEnvironment = 0;
}
// set the listener gain to full and handle through this layer?
#ifdef HANDLE_LISTENER_GAIN
alListenerf(AL_GAIN_LINEAR, 1.f);
#endif
return(true);
}
// driver load failed, load the stub driver
libraryShutdown();
mActiveDriver = mDriverInfoList.begin();
return(false);
}
//--------------------------------------------------------------------------
const Vector<DriverInfo>* getDriverList()
{
return &mDriverInfoList;
}
//--------------------------------------------------------------------------
const char* getDriverListString()
{
U32 deviceCount = mDriverInfoList.size();
if ( deviceCount > 0 ) // It better be...
{
U32 i, bufSize = 0;
for ( i = 0; i < deviceCount; i++ )
bufSize += ( dStrlen( mDriverInfoList[i].mName ) + 1 );
char* returnString = Con::getReturnBuffer( bufSize );
dStrcpy( returnString, mDriverInfoList[0].mName );
for ( i = 1; i < deviceCount; i++ )
{
dStrcat( returnString, "\t" );
dStrcat( returnString, mDriverInfoList[i].mName );
}
return( returnString );
}
return( "" );
}
//--------------------------------------------------------------------------
const char* getCurrentDriverInfo()
{
if ( mActiveDriver )
{
U32 bufSize = ( mActiveDriver->mName ? dStrlen( mActiveDriver->mName ) : 0 )
+ ( mActiveDriver->mVender ? dStrlen( mActiveDriver->mVender ) : 0 )
+ ( mActiveDriver->mRenderer ? dStrlen( mActiveDriver->mRenderer ) : 0 )
+ ( mActiveDriver->mVersion ? dStrlen( mActiveDriver->mVersion ) : 0 )
+ ( mActiveDriver->mExtensions ? dStrlen( mActiveDriver->mExtensions ) : 0 )
+ 5;
char* returnString = Con::getReturnBuffer( bufSize );
dSprintf( returnString, bufSize, "%s\t%s\t%s\t%s\t%s",
( mActiveDriver->mName ? mActiveDriver->mName : "" ),
( mActiveDriver->mVender ? mActiveDriver->mVender : "" ),
( mActiveDriver->mRenderer ? mActiveDriver->mRenderer : "" ),
( mActiveDriver->mVersion ? mActiveDriver->mVersion : "" ),
( mActiveDriver->mExtensions ? mActiveDriver->mExtensions : "" ) );
return( returnString );
}
return( "" );
}
} // end OpenAL namespace