549 lines
14 KiB
C++
549 lines
14 KiB
C++
/**
|
|
* @file audioengine_openal.cpp
|
|
* @brief implementation of audio engine using OpenAL
|
|
* support as a OpenAL 3D implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2002&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2002-2009, Linden Research, Inc.
|
|
*
|
|
* Second Life Viewer Source Code
|
|
* The source code in this file ("Source Code") is provided by Linden Lab
|
|
* to you under the terms of the GNU General Public License, version 2.0
|
|
* ("GPL"), unless you have obtained a separate licensing agreement
|
|
* ("Other License"), formally executed by you and Linden Lab. Terms of
|
|
* the GPL can be found in doc/GPL-license.txt in this distribution, or
|
|
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* There are special exceptions to the terms and conditions of the GPL as
|
|
* it is applied to this Source Code. View the full text of the exception
|
|
* in the file doc/FLOSS-exception.txt in this software distribution, or
|
|
* online at
|
|
* http://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* By copying, modifying or distributing this software, you acknowledge
|
|
* that you have read and understood your obligations described above,
|
|
* and agree to abide by those obligations.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
#include "lldir.h"
|
|
|
|
#include "llaudioengine_openal.h"
|
|
#include "lllistener_openal.h"
|
|
|
|
|
|
LLAudioEngine_OpenAL::LLAudioEngine_OpenAL()
|
|
:
|
|
mWindGen(NULL),
|
|
mWindBuf(NULL),
|
|
mWindBufFreq(0),
|
|
mWindBufSamples(0),
|
|
mWindBufBytes(0),
|
|
mWindSource(AL_NONE),
|
|
mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS)
|
|
{
|
|
}
|
|
|
|
// virtual
|
|
LLAudioEngine_OpenAL::~LLAudioEngine_OpenAL()
|
|
{
|
|
}
|
|
|
|
// virtual
|
|
bool LLAudioEngine_OpenAL::init(const S32 num_channels, void* userdata)
|
|
{
|
|
mWindGen = NULL;
|
|
LLAudioEngine::init(num_channels, userdata);
|
|
|
|
if(!alutInit(NULL, NULL))
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::init() ALUT initialization failed: " << alutGetErrorString (alutGetError ()) << llendl;
|
|
return false;
|
|
}
|
|
|
|
llinfos << "LLAudioEngine_OpenAL::init() OpenAL successfully initialized" << llendl;
|
|
|
|
llinfos << "OpenAL version: "
|
|
<< ll_safe_string(alGetString(AL_VERSION)) << llendl;
|
|
llinfos << "OpenAL vendor: "
|
|
<< ll_safe_string(alGetString(AL_VENDOR)) << llendl;
|
|
llinfos << "OpenAL renderer: "
|
|
<< ll_safe_string(alGetString(AL_RENDERER)) << llendl;
|
|
|
|
ALint major = alutGetMajorVersion ();
|
|
ALint minor = alutGetMinorVersion ();
|
|
llinfos << "ALUT version: " << major << "." << minor << llendl;
|
|
|
|
ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
|
|
|
|
alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major);
|
|
alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &minor);
|
|
llinfos << "ALC version: " << major << "." << minor << llendl;
|
|
|
|
llinfos << "ALC default device: "
|
|
<< ll_safe_string(alcGetString(device,
|
|
ALC_DEFAULT_DEVICE_SPECIFIER))
|
|
<< llendl;
|
|
|
|
return true;
|
|
}
|
|
|
|
// virtual
|
|
std::string LLAudioEngine_OpenAL::getDriverName(bool verbose)
|
|
{
|
|
ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
|
|
std::ostringstream version;
|
|
|
|
version <<
|
|
"OpenAL";
|
|
|
|
if (verbose)
|
|
{
|
|
version <<
|
|
", version " <<
|
|
ll_safe_string(alGetString(AL_VERSION)) <<
|
|
" / " <<
|
|
ll_safe_string(alGetString(AL_VENDOR)) <<
|
|
" / " <<
|
|
ll_safe_string(alGetString(AL_RENDERER));
|
|
|
|
if (device)
|
|
version <<
|
|
": " <<
|
|
ll_safe_string(alcGetString(device,
|
|
ALC_DEFAULT_DEVICE_SPECIFIER));
|
|
}
|
|
|
|
return version.str();
|
|
}
|
|
|
|
// virtual
|
|
void LLAudioEngine_OpenAL::allocateListener()
|
|
{
|
|
mListenerp = (LLListener *) new LLListener_OpenAL();
|
|
if(!mListenerp)
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::allocateListener() Listener creation failed" << llendl;
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
void LLAudioEngine_OpenAL::shutdown()
|
|
{
|
|
llinfos << "About to LLAudioEngine::shutdown()" << llendl;
|
|
LLAudioEngine::shutdown();
|
|
|
|
llinfos << "About to alutExit()" << llendl;
|
|
if(!alutExit())
|
|
{
|
|
llwarns << "Nuts." << llendl;
|
|
llwarns << "LLAudioEngine_OpenAL::shutdown() ALUT shutdown failed: " << alutGetErrorString (alutGetError ()) << llendl;
|
|
}
|
|
|
|
llinfos << "LLAudioEngine_OpenAL::shutdown() OpenAL successfully shut down" << llendl;
|
|
|
|
delete mListenerp;
|
|
mListenerp = NULL;
|
|
}
|
|
|
|
LLAudioBuffer *LLAudioEngine_OpenAL::createBuffer()
|
|
{
|
|
return new LLAudioBufferOpenAL();
|
|
}
|
|
|
|
LLAudioChannel *LLAudioEngine_OpenAL::createChannel()
|
|
{
|
|
return new LLAudioChannelOpenAL();
|
|
}
|
|
|
|
void LLAudioEngine_OpenAL::setInternalGain(F32 gain)
|
|
{
|
|
//llinfos << "LLAudioEngine_OpenAL::setInternalGain() Gain: " << gain << llendl;
|
|
alListenerf(AL_GAIN, gain);
|
|
}
|
|
|
|
LLAudioChannelOpenAL::LLAudioChannelOpenAL()
|
|
:
|
|
mALSource(AL_NONE),
|
|
mLastSamplePos(0)
|
|
{
|
|
alGenSources(1, &mALSource);
|
|
}
|
|
|
|
LLAudioChannelOpenAL::~LLAudioChannelOpenAL()
|
|
{
|
|
cleanup();
|
|
alDeleteSources(1, &mALSource);
|
|
}
|
|
|
|
void LLAudioChannelOpenAL::cleanup()
|
|
{
|
|
alSourceStop(mALSource);
|
|
mCurrentBufferp = NULL;
|
|
}
|
|
|
|
void LLAudioChannelOpenAL::play()
|
|
{
|
|
if (mALSource == AL_NONE)
|
|
{
|
|
llwarns << "Playing without a mALSource, aborting" << llendl;
|
|
return;
|
|
}
|
|
|
|
if(!isPlaying())
|
|
{
|
|
alSourcePlay(mALSource);
|
|
getSource()->setPlayedOnce(true);
|
|
}
|
|
}
|
|
|
|
void LLAudioChannelOpenAL::playSynced(LLAudioChannel *channelp)
|
|
{
|
|
if (channelp)
|
|
{
|
|
LLAudioChannelOpenAL *masterchannelp =
|
|
(LLAudioChannelOpenAL*)channelp;
|
|
if (mALSource != AL_NONE &&
|
|
masterchannelp->mALSource != AL_NONE)
|
|
{
|
|
// we have channels allocated to master and slave
|
|
ALfloat master_offset;
|
|
alGetSourcef(masterchannelp->mALSource, AL_SEC_OFFSET,
|
|
&master_offset);
|
|
|
|
llinfos << "Syncing with master at " << master_offset
|
|
<< "sec" << llendl;
|
|
// *TODO: detect when this fails, maybe use AL_SAMPLE_
|
|
alSourcef(mALSource, AL_SEC_OFFSET, master_offset);
|
|
}
|
|
}
|
|
play();
|
|
}
|
|
|
|
bool LLAudioChannelOpenAL::isPlaying()
|
|
{
|
|
if (mALSource != AL_NONE)
|
|
{
|
|
ALint state;
|
|
alGetSourcei(mALSource, AL_SOURCE_STATE, &state);
|
|
if(state == AL_PLAYING)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLAudioChannelOpenAL::updateBuffer()
|
|
{
|
|
if (LLAudioChannel::updateBuffer())
|
|
{
|
|
// Base class update returned true, which means that we need to actually
|
|
// set up the source for a different buffer.
|
|
LLAudioBufferOpenAL *bufferp = (LLAudioBufferOpenAL *)mCurrentSourcep->getCurrentBuffer();
|
|
ALuint buffer = bufferp->getBuffer();
|
|
alSourcei(mALSource, AL_BUFFER, buffer);
|
|
mLastSamplePos = 0;
|
|
}
|
|
|
|
if (mCurrentSourcep)
|
|
{
|
|
alSourcef(mALSource, AL_GAIN,
|
|
mCurrentSourcep->getGain() * getSecondaryGain());
|
|
alSourcei(mALSource, AL_LOOPING,
|
|
mCurrentSourcep->isLoop() ? AL_TRUE : AL_FALSE);
|
|
alSourcef(mALSource, AL_ROLLOFF_FACTOR,
|
|
gAudiop->mListenerp->getRolloffFactor());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LLAudioChannelOpenAL::updateLoop()
|
|
{
|
|
if (mALSource == AL_NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Hack: We keep track of whether we looped or not by seeing when the
|
|
// sample position looks like it's going backwards. Not reliable; may
|
|
// yield false negatives.
|
|
//
|
|
ALint cur_pos;
|
|
alGetSourcei(mALSource, AL_SAMPLE_OFFSET, &cur_pos);
|
|
if (cur_pos < mLastSamplePos)
|
|
{
|
|
mLoopedThisFrame = true;
|
|
}
|
|
mLastSamplePos = cur_pos;
|
|
}
|
|
|
|
|
|
void LLAudioChannelOpenAL::update3DPosition()
|
|
{
|
|
if(!mCurrentSourcep)
|
|
{
|
|
return;
|
|
}
|
|
if (mCurrentSourcep->isAmbient())
|
|
{
|
|
alSource3f(mALSource, AL_POSITION, 0.0, 0.0, 0.0);
|
|
alSource3f(mALSource, AL_VELOCITY, 0.0, 0.0, 0.0);
|
|
alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
} else {
|
|
LLVector3 float_pos;
|
|
float_pos.setVec(mCurrentSourcep->getPositionGlobal());
|
|
alSourcefv(mALSource, AL_POSITION, float_pos.mV);
|
|
alSourcefv(mALSource, AL_VELOCITY, mCurrentSourcep->getVelocity().mV);
|
|
alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_FALSE);
|
|
}
|
|
|
|
alSourcef(mALSource, AL_GAIN, mCurrentSourcep->getGain() * getSecondaryGain());
|
|
}
|
|
|
|
LLAudioBufferOpenAL::LLAudioBufferOpenAL()
|
|
{
|
|
mALBuffer = AL_NONE;
|
|
}
|
|
|
|
LLAudioBufferOpenAL::~LLAudioBufferOpenAL()
|
|
{
|
|
cleanup();
|
|
}
|
|
|
|
void LLAudioBufferOpenAL::cleanup()
|
|
{
|
|
if(mALBuffer != AL_NONE)
|
|
{
|
|
alDeleteBuffers(1, &mALBuffer);
|
|
mALBuffer = AL_NONE;
|
|
}
|
|
}
|
|
|
|
bool LLAudioBufferOpenAL::loadWAV(const std::string& filename)
|
|
{
|
|
cleanup();
|
|
mALBuffer = alutCreateBufferFromFile(filename.c_str());
|
|
if(mALBuffer == AL_NONE)
|
|
{
|
|
ALenum error = alutGetError();
|
|
if (gDirUtilp->fileExists(filename))
|
|
{
|
|
llwarns <<
|
|
"LLAudioBufferOpenAL::loadWAV() Error loading "
|
|
<< filename
|
|
<< " " << alutGetErrorString(error) << llendl;
|
|
}
|
|
else
|
|
{
|
|
// It's common for the file to not actually exist.
|
|
lldebugs <<
|
|
"LLAudioBufferOpenAL::loadWAV() Error loading "
|
|
<< filename
|
|
<< " " << alutGetErrorString(error) << llendl;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
U32 LLAudioBufferOpenAL::getLength()
|
|
{
|
|
if(mALBuffer == AL_NONE)
|
|
{
|
|
return 0;
|
|
}
|
|
ALint length;
|
|
alGetBufferi(mALBuffer, AL_SIZE, &length);
|
|
return length / 2; // convert size in bytes to size in (16-bit) samples
|
|
}
|
|
|
|
// ------------
|
|
|
|
bool LLAudioEngine_OpenAL::initWind()
|
|
{
|
|
ALenum error;
|
|
llinfos << "LLAudioEngine_OpenAL::initWind() start" << llendl;
|
|
|
|
mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS;
|
|
|
|
alGetError(); /* clear error */
|
|
|
|
alGenSources(1,&mWindSource);
|
|
|
|
if((error=alGetError()) != AL_NO_ERROR)
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<llendl;
|
|
}
|
|
|
|
mWindGen = new LLWindGen<WIND_SAMPLE_T>;
|
|
|
|
mWindBufFreq = mWindGen->getInputSamplingRate();
|
|
mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC);
|
|
mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T);
|
|
|
|
mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/];
|
|
|
|
if(mWindBuf==NULL)
|
|
{
|
|
llerrs << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << llendl;
|
|
return false;
|
|
}
|
|
|
|
llinfos << "LLAudioEngine_OpenAL::initWind() done" << llendl;
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLAudioEngine_OpenAL::cleanupWind()
|
|
{
|
|
llinfos << "LLAudioEngine_OpenAL::cleanupWind()" << llendl;
|
|
|
|
if (mWindSource != AL_NONE)
|
|
{
|
|
// detach and delete all outstanding buffers on the wind source
|
|
alSourceStop(mWindSource);
|
|
ALint processed;
|
|
alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed);
|
|
while (processed--)
|
|
{
|
|
ALuint buffer = AL_NONE;
|
|
alSourceUnqueueBuffers(mWindSource, 1, &buffer);
|
|
alDeleteBuffers(1, &buffer);
|
|
}
|
|
|
|
// delete the wind source itself
|
|
alDeleteSources(1, &mWindSource);
|
|
|
|
mWindSource = AL_NONE;
|
|
}
|
|
|
|
delete[] mWindBuf;
|
|
mWindBuf = NULL;
|
|
|
|
delete mWindGen;
|
|
mWindGen = NULL;
|
|
}
|
|
|
|
void LLAudioEngine_OpenAL::updateWind(LLVector3 wind_vec, F32 camera_altitude)
|
|
{
|
|
LLVector3 wind_pos;
|
|
F64 pitch;
|
|
F64 center_freq;
|
|
ALenum error;
|
|
|
|
if (!mEnableWind)
|
|
return;
|
|
|
|
if(!mWindBuf)
|
|
return;
|
|
|
|
if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL))
|
|
{
|
|
|
|
// wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up)
|
|
// need to convert this to the conventional orientation DS3D and OpenAL use
|
|
// where +X = right, +Y = up, +Z = backwards
|
|
|
|
wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]);
|
|
|
|
pitch = 1.0 + mapWindVecToPitch(wind_vec);
|
|
center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0));
|
|
|
|
mWindGen->mTargetFreq = (F32)center_freq;
|
|
mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain;
|
|
mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec);
|
|
|
|
alSourcei(mWindSource, AL_LOOPING, AL_FALSE);
|
|
alSource3f(mWindSource, AL_POSITION, 0.0, 0.0, 0.0);
|
|
alSource3f(mWindSource, AL_VELOCITY, 0.0, 0.0, 0.0);
|
|
alSourcef(mWindSource, AL_ROLLOFF_FACTOR, 0.0);
|
|
alSourcei(mWindSource, AL_SOURCE_RELATIVE, AL_TRUE);
|
|
}
|
|
|
|
// ok lets make a wind buffer now
|
|
|
|
ALint processed, queued, unprocessed;
|
|
alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed);
|
|
alGetSourcei(mWindSource, AL_BUFFERS_QUEUED, &queued);
|
|
unprocessed = queued - processed;
|
|
|
|
// ensure that there are always at least 3x as many filled buffers
|
|
// queued as we managed to empty since last time.
|
|
mNumEmptyWindALBuffers = llmin(mNumEmptyWindALBuffers + processed * 3 - unprocessed, MAX_NUM_WIND_BUFFERS-unprocessed);
|
|
mNumEmptyWindALBuffers = llmax(mNumEmptyWindALBuffers, 0);
|
|
|
|
//llinfos << "mNumEmptyWindALBuffers: " << mNumEmptyWindALBuffers <<" (" << unprocessed << ":" << processed << ")" << llendl;
|
|
|
|
while(processed--) // unqueue old buffers
|
|
{
|
|
ALuint buffer;
|
|
ALenum error;
|
|
alGetError(); /* clear error */
|
|
alSourceUnqueueBuffers(mWindSource, 1, &buffer);
|
|
error = alGetError();
|
|
if(error != AL_NO_ERROR)
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::updateWind() error swapping (unqueuing) buffers" << llendl;
|
|
}
|
|
else
|
|
{
|
|
alDeleteBuffers(1, &buffer);
|
|
}
|
|
}
|
|
|
|
unprocessed += mNumEmptyWindALBuffers;
|
|
while (mNumEmptyWindALBuffers > 0) // fill+queue new buffers
|
|
{
|
|
ALuint buffer;
|
|
alGetError(); /* clear error */
|
|
alGenBuffers(1,&buffer);
|
|
if((error=alGetError()) != AL_NO_ERROR)
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::updateWind() Error creating wind buffer: " << error << llendl;
|
|
break;
|
|
}
|
|
|
|
alBufferData(buffer,
|
|
AL_FORMAT_STEREO16,
|
|
mWindGen->windGenerate(mWindBuf,
|
|
mWindBufSamples),
|
|
mWindBufBytes,
|
|
mWindBufFreq);
|
|
error = alGetError();
|
|
if(error != AL_NO_ERROR)
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::updateWind() error swapping (bufferdata) buffers" << llendl;
|
|
}
|
|
|
|
alSourceQueueBuffers(mWindSource, 1, &buffer);
|
|
error = alGetError();
|
|
if(error != AL_NO_ERROR)
|
|
{
|
|
llwarns << "LLAudioEngine_OpenAL::updateWind() error swapping (queuing) buffers" << llendl;
|
|
}
|
|
|
|
--mNumEmptyWindALBuffers;
|
|
}
|
|
|
|
ALint playing;
|
|
alGetSourcei(mWindSource, AL_SOURCE_STATE, &playing);
|
|
if(playing != AL_PLAYING)
|
|
{
|
|
alSourcePlay(mWindSource);
|
|
|
|
lldebugs << "Wind had stopped - probably ran out of buffers - restarting: " << (unprocessed+mNumEmptyWindALBuffers) << " now queued." << llendl;
|
|
}
|
|
}
|
|
|