//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "audio/audio.h" #include "audio/audioDataBlock.h" #include "core/tVector.h" #include "console/console.h" #include "console/consoleTypes.h" #include "game/gameConnection.h" #include "core/fileStream.h" #include "audio/audioStreamSourceFactory.h" #ifdef TORQUE_OS_MAC //#define REL_WORKAROUND #endif //------------------------------------------------------------------------- #define MAX_AUDIOSOURCES 16 // maximum number of concurrent sources #define MIN_GAIN 0.05f // anything with lower gain will not be started #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 ALCdevice *mDevice = NULL; // active OpenAL device static ALCcontext *mContext = NULL; // active OpenAL context F32 mAudioTypeVolume[Audio::NumAudioTypes]; // the attenuation for each of the channel types //------------------------------------------------------------------------- struct LoopingImage { AUDIOHANDLE mHandle; Resource mBuffer; Audio::Description mDescription; AudioSampleEnvironment *mEnvironment; Point3F mPosition; Point3F mDirection; F32 mPitch; F32 mScore; U32 mCullTime; LoopingImage() { clear(); } void clear() { mHandle = NULL_AUDIOHANDLE; mBuffer = NULL; dMemset(&mDescription, 0, sizeof(Audio::Description)); mEnvironment = 0; mPosition.set(0.f,0.f,0.f); mDirection.set(0.f,1.f,0.f); mPitch = 1.f; mScore = 0.f; mCullTime = 0; } }; //------------------------------------------------------------------------- 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 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 AudioSampleEnvironment* mSampleEnvironment[MAX_AUDIOSOURCES]; // currently playing sample environments static bool mEnvironmentEnabled = false; // environment enabled? static SimObjectPtr mCurrentEnvironment; // the last environment set static ALuint mEnvironment = 0; // al environment handle struct LoopingList : VectorPtr { LoopingList() : VectorPtr(__FILE__, __LINE__) { } LoopingList::iterator findImage(AUDIOHANDLE handle); void sort(); }; struct StreamingList : VectorPtr { StreamingList() : VectorPtr(__FILE__, __LINE__) { } StreamingList::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) // StreamingList and StreamingFreeList own the images static StreamingList mStreamingList; // all the streaming sources //static StreamingList mStreamingFreeList; // free store static StreamingList mStreamingInactiveList; // sources which have not been played yet static StreamingList mStreamingCulledList; // sources which have been culled (alxPlay called) #define AUDIOHANDLE_LOOPING_BIT (0x80000000) #define AUDIOHANDLE_STREAMING_BIT (0x40000000) #define AUDIOHANDLE_INACTIVE_BIT (0x20000000) #define AUDIOHANDLE_LOADING_BIT (0x10000000) #define HANDLE_MASK ~(AUDIOHANDLE_LOOPING_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_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 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); } //------------------------------------------------------------------------- // StreamingList //------------------------------------------------------------------------- inline StreamingList::iterator StreamingList::findImage(AUDIOHANDLE handle) { if(handle & AUDIOHANDLE_STREAMING_BIT) { StreamingList::iterator itr = begin(); while(itr != end()) { if(areEqualHandles((*itr)->mHandle, handle)) return(itr); itr++; } } return(0); } inline int QSORT_CALLBACK streamingSourceSort(const void * p1, const void * p2) { const AudioStreamSource * ip1 = *(const AudioStreamSource**)p1; const AudioStreamSource * ip2 = *(const AudioStreamSource**)p2; // min->max return ip2->mScore - ip1->mScore; } void StreamingList::sort() { dQsort(address(), size(), sizeof(AudioStreamSource*), streamingSourceSort); } //------------------------------------------------------------------------- LoopingImage * createLoopingImage() { LoopingImage *image; if (mLoopingFreeList.size()) { image = mLoopingFreeList.last(); mLoopingFreeList.pop_back(); } else image = new LoopingImage; return(image); } //------------------------------------------------------------------------- AudioStreamSource * createStreamingSource(const char* filename) { AudioStreamSource *streamSource = AudioStreamSourceFactory::getNewInstance(filename); return(streamSource); } //------------------------------------------------------------------------- static AUDIOHANDLE getNewHandle() { mLastHandle++; mLastHandle &= HANDLE_MASK; if (mLastHandle == NULL_AUDIOHANDLE) mLastHandle++; return mLastHandle; } //------------------------------------------------------------------------- // function declarations void alxLoopingUpdate(); void alxStreamingUpdate(); void alxUpdateScores(bool); 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); } } // check if culling a streamer StreamingList::iterator itr2 = mStreamingList.findImage(mHandle[best]); if(itr2) { // check if culling an inactive streamer if(mHandle[best] & AUDIOHANDLE_INACTIVE_BIT) { AssertFatal(!mStreamingInactiveList.findImage(mHandle[best]), "cullSource: image already in inactive list"); AssertFatal(!mStreamingCulledList.findImage(mHandle[best]), "cullSource: image should not be in culled list"); mStreamingInactiveList.push_back(*itr2); } else { (*itr2)->mHandle |= AUDIOHANDLE_INACTIVE_BIT; AssertFatal(!mStreamingCulledList.findImage(mHandle[best]), "cullSource: image already in culled list"); AssertFatal(!mStreamingInactiveList.findImage(mHandle[best]), "cullSource: image should no be in inactive list"); (*itr2)->freeStream(); (*itr2)->mCullTime = Platform::getRealMilliseconds(); mStreamingCulledList.push_back(*itr2); } } alSourceStop(mSource[best]); mHandle[best] = NULL_AUDIOHANDLE; mBuffer[best] = 0; *index = best; return(true); } //-------------------------------------------------------------------------- /** Compute approximate max volume at a particular distance ignore cone volume influnces */ static F32 approximate3DVolume(const Audio::Description *desc, const Point3F &position) { Point3F p1; alGetListener3f(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->mReferenceDistance) return 1.0f; else return 1.0 - (distance - desc->mReferenceDistance) / (desc->mMaxDistance - desc->mReferenceDistance); } //-------------------------------------------------------------------------- inline U32 alxFindIndex(AUDIOHANDLE handle) { for (U32 i=0; imDirect); 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); */ } static void alxSourceEnvironment(ALuint source, LoopingImage * image) { AssertFatal(image, "alxSourceEnvironment: invalid looping image"); if(image->mDescription.mIs3D) alxSourceEnvironment(source, image->mDescription.mEnvironmentLevel, image->mEnvironment); } static void alxSourceEnvironment(ALuint source, AudioStreamSource * 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 cached // - by default, pitch is 1x (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 buffer, const Audio::Description *desc, const MatrixF *transform) { alSourcei(source, AL_BUFFER, buffer->getALBuffer()); alSourcef(source, AL_GAIN, Audio::linearToDB(desc->mVolume * mAudioTypeVolume[desc->mType] * mMasterVolume)); alSourcei(source, AL_LOOPING, desc->mIsLooping ? AL_TRUE : AL_FALSE); alSourcef(source, AL_PITCH, 1.f); alSourcei(source, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle); alSourcei(source, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle); alSourcef(source, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume); if(transform != NULL) { #ifdef REL_WORKAROUND alSourcei(source, AL_SOURCE_ABSOLUTE, AL_TRUE); #else alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); #endif Point3F p; transform->getColumn(3, &p); alSource3f(source, AL_POSITION, p.x, p.y, p.z); //Always use ConeVector (which is tied to transform) alSource3f(source, AL_DIRECTION, desc->mConeVector.x, desc->mConeVector.y, desc->mConeVector.z); } else { // 2D sound alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); alSource3f(source, AL_POSITION, 0.0f, 0.0f, 1.0f); } alSourcef(source, AL_REFERENCE_DISTANCE, desc->mReferenceDistance); alSourcef(source, AL_MAX_DISTANCE, desc->mMaxDistance); /* todo // environmental audio stuff: alSourcef(source, AL_ENV_SAMPLE_REVERB_MIX_EXT, desc->mEnvironmentLevel); if(desc->mEnvironmentLevel != 0.f) alSourceResetEnvironment_EXT(source); */ } // 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); } } //-------------------------------------------------------------------------- // setup a streaming source to play static void alxSourcePlay(AudioStreamSource *streamSource) { ALuint source = streamSource->mSource; Audio::Description *desc = &streamSource->mDescription; bool ret = streamSource->initStream(); alSourcef(source, AL_GAIN, Audio::linearToDB(desc->mVolume * mAudioTypeVolume[desc->mType] * mMasterVolume)); // alSourcei(source, AL_LOOPING, AL_FALSE); alSourcef(source, AL_PITCH, 1.f); alSourcei(source, AL_CONE_INNER_ANGLE, desc->mConeInsideAngle); alSourcei(source, AL_CONE_OUTER_ANGLE, desc->mConeOutsideAngle); alSourcef(source, AL_CONE_OUTER_GAIN, desc->mConeOutsideVolume); if(streamSource->mDescription.mIs3D) { MatrixF transform(true); transform.setColumn(3, streamSource->mPosition); transform.setRow(1, streamSource->mDirection); #ifdef REL_WORKAROUND alSourcei(source, AL_SOURCE_ABSOLUTE, AL_TRUE); #else alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE); #endif Point3F p; transform.getColumn(3, &p); alSource3f(source, AL_POSITION, p.x, p.y, p.z); //Always use ConeVector (which is tied to transform) alSource3f(source, AL_DIRECTION, desc->mConeVector.x, desc->mConeVector.y, desc->mConeVector.z); } else { // 2D sound // JMQ: slam the stream source's position to our desired value streamSource->mPosition = Point3F(0.0f, 0.0f, 1.0f); alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE); alSource3f(source, AL_POSITION, streamSource->mPosition.x, streamSource->mPosition.y, streamSource->mPosition.z); } alSourcef(source, AL_REFERENCE_DISTANCE, desc->mReferenceDistance); alSourcef(source, AL_MAX_DISTANCE, desc->mMaxDistance); /* todo // environmental audio stuff: alSourcef(source, AL_ENV_SAMPLE_REVERB_MIX_EXT, desc->mEnvironmentLevel); if(desc->mEnvironmentLevel != 0.f) alSourceResetEnvironment_EXT(source); */ } //-------------------------------------------------------------------------- AUDIOHANDLE alxCreateSource(const Audio::Description *desc, const char *filename, const MatrixF *transform, AudioSampleEnvironment *sampleEnvironment) { if (!mContext) return NULL_AUDIOHANDLE; if(desc == NULL || filename == NULL || *filename == '\0') return NULL_AUDIOHANDLE; F32 volume = desc->mVolume; // calculate an approximate attenuation for 3d sounds if(transform && desc->mIs3D) { Point3F position; transform->getColumn(3, &position); volume *= approximate3DVolume(desc, position); } // 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 && !desc->mIsStreaming && (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 && !desc->mIsStreaming && (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 && !(desc->mIsStreaming)) { Resource 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; // grab position/direction if 3d source if(transform) { transform->getColumn(3, &image->mPosition); transform->getColumn(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); } // make sure that streamers are added if(index == MAX_AUDIOSOURCES) { if(desc->mIsStreaming) { // create the inactive audio stream AudioStreamSource * streamSource = createStreamingSource(filename); if (streamSource) { streamSource->mHandle = getNewHandle() | AUDIOHANDLE_STREAMING_BIT | AUDIOHANDLE_INACTIVE_BIT; streamSource->mSource = NULL; streamSource->mDescription = *desc; streamSource->mScore = volume; streamSource->mEnvironment = sampleEnvironment; // grab position/direction if 3d source if(transform) { transform->getColumn(3, &streamSource->mPosition); transform->getColumn(1, &streamSource->mDirection); } AssertFatal(!mStreamingInactiveList.findImage(streamSource->mHandle), "alxCreateSource: handle in inactive list"); AssertFatal(!mStreamingCulledList.findImage(streamSource->mHandle), "alxCreateSource: handle in culled list"); // add to the streaming and inactive lists mStreamingList.push_back(streamSource); mStreamingInactiveList.push_back(streamSource); return(streamSource->mHandle & RETURN_MASK); } else return NULL_AUDIOHANDLE; } else return(NULL_AUDIOHANDLE); } // clear the error state alGetError(); // grab the buffer Resource buffer; if(!(desc->mIsStreaming)) { 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; if(!(desc->mIsStreaming)) { mBuffer[index] = buffer; } mScore[index] = volume; mSourceVolume[index] = desc->mVolume; mSampleEnvironment[index] = sampleEnvironment; ALuint source = mSource[index]; // setup play info if(!desc->mIsStreaming) 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 && !(desc->mIsStreaming)) { 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->getColumn(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 list mLoopingList.push_back(image); } // setup a AudioStreamSource ONLY if the sound is a streamer: if(desc->mIsStreaming) { // Intangir> why is loading bit never used anywhere else? // comes in handy for my oggmixedstream // (prevents it from being deleted before it is loaded) mHandle[index] |= AUDIOHANDLE_STREAMING_BIT | AUDIOHANDLE_LOADING_BIT; AudioStreamSource * streamSource = createStreamingSource(filename); if (streamSource) { streamSource->mHandle = mHandle[index]; streamSource->mSource = mSource[index]; streamSource->mDescription = *desc; streamSource->mScore = volume; streamSource->mEnvironment = sampleEnvironment; // grab position/direction if(transform) { transform->getColumn(3, &streamSource->mPosition); transform->getColumn(1, &streamSource->mDirection); } AssertFatal(!mStreamingInactiveList.findImage(streamSource->mHandle), "alxCreateSource: handle in inactive list"); AssertFatal(!mStreamingCulledList.findImage(streamSource->mHandle), "alxCreateSource: handle in culled list"); alxSourcePlay(streamSource); // add to the looping list mStreamingList.push_back(streamSource); } else { mSampleEnvironment[index] = 0; mHandle[index] = NULL_AUDIOHANDLE; mBuffer[index] = 0; return NULL_AUDIOHANDLE; } } // clear off all but looping bit return(mHandle[index] & RETURN_MASK); } //------------------------------------------------------------------------------ AUDIOHANDLE alxCreateSource(AudioDescription *descObject, const char *filename, const MatrixF *transform, AudioSampleEnvironment * sampleEnvironment ) { if(!descObject || !descObject->getDescription()) return(NULL_AUDIOHANDLE); return (alxCreateSource(descObject->getDescription(), filename, transform, sampleEnvironment)); } AUDIOHANDLE alxCreateSource(const AudioProfile *profile, const MatrixF *transform) { if (profile == NULL) return NULL_AUDIOHANDLE; return alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, 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) { 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); // make sure the streaming image also clears it's inactive bit StreamingList::iterator itr2 = mStreamingList.findImage(handle); if(itr2) (*itr2)->mHandle &= ~(AUDIOHANDLE_INACTIVE_BIT | AUDIOHANDLE_LOADING_BIT); alSourcePlay(mSource[index]); 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(); return(handle); } else if(mLoopingCulledList.findImage(handle)) { alxLoopingUpdate(); return(handle); } else return(NULL_AUDIOHANDLE); // move inactive streamers to the culled list, try to start the sound StreamingList::iterator itr2 = mStreamingInactiveList.findImage(handle); if(itr2) { AssertFatal(!mStreamingCulledList.findImage(handle), "alxPlay: image already in culled list"); (*itr2)->freeStream(); mStreamingCulledList.push_back(*itr2); mStreamingInactiveList.erase_fast(itr2); alxStreamingUpdate(); return(handle); } else if(mStreamingCulledList.findImage(handle)) { alxStreamingUpdate(); return(handle); } 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; AUDIOHANDLE handle = alxCreateSource(profile->mDescriptionObject, profile->mFilename, transform, 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) { if(!(mHandle[index] & AUDIOHANDLE_INACTIVE_BIT)) { alSourceStop(mSource[index]); } mSampleEnvironment[index] = 0; mHandle[index] = NULL_AUDIOHANDLE; mBuffer[index] = 0; } // 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); } // remove streamingImage and add it to the free list StreamingList::iterator itr2 = mStreamingList.findImage(handle); if(itr2) { // remove from inactive/culled list if((*itr2)->mHandle & AUDIOHANDLE_INACTIVE_BIT) { StreamingList::iterator tmp = mStreamingInactiveList.findImage(handle); // inactive? if(tmp) mStreamingInactiveList.erase_fast(tmp); else { //culled? tmp = mStreamingCulledList.findImage(handle); AssertFatal(tmp, "alxStop: failed to find inactive looping source"); mStreamingCulledList.erase_fast(tmp); } } AssertFatal(!mStreamingInactiveList.findImage((*itr2)->mHandle), "alxStop: handle in inactive list"); AssertFatal(!mStreamingCulledList.findImage((*itr2)->mHandle), "alxStop: handle in culled list"); // remove it (*itr2)->freeStream(); delete(*itr2); mStreamingList.erase_fast(itr2); } } //-------------------------------------------------------------------------- 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); // stop all streaming sources while(mStreamingList.size()) alxStop(mStreamingList.last()->mHandle); } void alxLoopSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) { LoopingList::iterator itr = mLoopingList.findImage(handle); if(itr) { switch(pname) { 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_REFERENCE_DISTANCE: (*itr)->mDescription.mReferenceDistance = 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_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_REFERENCE_DISTANCE: *value = (*itr)->mDescription.mReferenceDistance; 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_LOOPING: *value = true; break; case AL_CONE_INNER_ANGLE: *value = (*itr)->mDescription.mConeInsideAngle; break; case AL_CONE_OUTER_ANGLE: *value = (*itr)->mDescription.mConeOutsideAngle; break; } } } //------------------------------------------------------ void alxStreamSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) { StreamingList::iterator itr = mStreamingList.findImage(handle); if(itr) { switch(pname) { 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_REFERENCE_DISTANCE: (*itr)->mDescription.mReferenceDistance = value; break; case AL_CONE_OUTER_GAIN: (*itr)->mDescription.mMaxDistance = value; break; } } } void alxStreamSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat value1, ALfloat value2, ALfloat value3) { StreamingList::iterator itr = mStreamingList.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 alxStreamSourcei(AUDIOHANDLE handle, ALenum pname, ALint value) { StreamingList::iterator itr = mStreamingList.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 alxStreamGetSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat *value) { StreamingList::iterator itr = mStreamingList.findImage(handle); if(itr) { switch(pname) { 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_REFERENCE_DISTANCE: *value = (*itr)->mDescription.mReferenceDistance; break; case AL_CONE_OUTER_GAIN: *value = (*itr)->mDescription.mMaxDistance; break; } } } void alxStreamGetSource3f(AUDIOHANDLE handle, ALenum pname, ALfloat *value1, ALfloat *value2, ALfloat *value3) { StreamingList::iterator itr = mStreamingList.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 alxStreamGetSourcei(AUDIOHANDLE handle, ALenum pname, ALint *value) { StreamingList::iterator itr = mStreamingList.findImage(handle); if(itr) { switch(pname) { //case AL_SOURCE_AMBIENT: // *value = (*itr)->mDescription.mIs3D; // break; case AL_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 type) { for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) { if(mHandle[i] == NULL_AUDIOHANDLE) continue; if(type != mType[i]) continue; ALint state = AL_STOPPED; alGetSourcei(mSource[i], AL_SOURCE_STATE, &state); if(state == AL_PLAYING) { // volume = SourceVolume * ChannelVolume * MasterVolume F32 vol = mClampF(mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume, 0.f, 1.f); alSourcef(mSource[i], AL_GAIN, Audio::linearToDB(vol) ); } } } void alxSourcef(AUDIOHANDLE handle, ALenum pname, ALfloat value) { 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 // #ifdef REL_WORKAROUND // ALint val = AL_TRUE; // alGetSourcei(source, AL_SOURCE_ABSOLUTE, &val); // if(val == AL_FALSE) // #else // ALint val = AL_FALSE; // alGetSourcei(source, AL_SOURCE_RELATIVE, &val); // if(val == AL_TRUE) // #endif { F32 vol = mClampF(mSourceVolume[idx] * mAudioTypeVolume[mType[idx]] * mMasterVolume, 0.f, 1.f); alSourcef(source, AL_GAIN, Audio::linearToDB(vol) ); } } else alSourcef(source, pname, value); } alxLoopSourcef(handle, pname, value); alxStreamSourcef(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]); alxStreamSource3f(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); alxStreamSource3f(handle, pname, value1, value2, value3); } void alxSourcei(AUDIOHANDLE handle, ALenum pname, ALint value) { ALuint source = alxFindSource(handle); if(source != INVALID_SOURCE) alSourcei(source, pname, value); alxLoopSourcei(handle, pname, value); alxStreamSourcei(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->getColumn(1, &dir); if(source != INVALID_SOURCE) { // OpenAL uses a Right-Handed corrdinate system so flip the orientation vector 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); alxStreamSource3f(handle, AL_POSITION, pos.x, pos.y, pos.z); alxStreamSource3f(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 if(handle & AUDIOHANDLE_LOOPING_BIT) alxLoopGetSourcef(handle, pname, value); else alxStreamGetSourcef(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 if(handle & AUDIOHANDLE_LOOPING_BIT) alxLoopGetSource3f(handle, pname, value1, value2, value3); else alxStreamGetSource3f(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 if(handle & AUDIOHANDLE_LOOPING_BIT) alxLoopGetSourcei(handle, pname, value); else alxStreamGetSourcei(handle, pname, value); } //-------------------------------------------------------------------------- /** alListenerfv extension for use with MatrixF's Set the listener's position and orientation using a matrix */ 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); } //-------------------------------------------------------------------------- /** alListenerf extension supporting linear gain */ void alxListenerf(ALenum param, ALfloat value) { if (param == AL_GAIN_LINEAR) { value = Audio::linearToDB(value); param = AL_GAIN; } alListenerf(param, value); } //-------------------------------------------------------------------------- /** alGetListenerf extension supporting linear gain */ void alxGetListenerf(ALenum param, ALfloat *value) { if (param == AL_GAIN_LINEAR) { alGetListenerf(AL_GAIN, value); *value = Audio::DBToLinear(*value); } else alGetListenerf(param, value); } //-------------------------------------------------------------------------- // Simple metrics //-------------------------------------------------------------------------- #ifdef TORQUE_GATHER_METRICS static void alxGatherMetrics() { S32 mNumOpenHandles = 0; S32 mNumOpenLoopingHandles = 0; S32 mNumOpenStreamingHandles = 0; S32 mNumActiveStreams = 0; S32 mNumNullActiveStreams = 0; S32 mNumActiveLoopingStreams = 0; S32 mNumActiveStreamingStreams = 0; S32 mNumLoopingStreams = 0; S32 mNumInactiveLoopingStreams = 0; S32 mNumCulledLoopingStreams = 0; S32 mNumStreamingStreams = 0; S32 mNumInactiveStreamingStreams = 0; S32 mNumCulledStreamingStreams = 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_STREAMING_BIT) mNumOpenStreamingHandles++; } 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_STREAMING_BIT) mNumActiveStreamingStreams++; } } 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++; for(StreamingList::iterator itr = mStreamingList.begin(); itr != mStreamingList.end(); itr++) mNumStreamingStreams++; for(StreamingList::iterator itr = mStreamingInactiveList.begin(); itr != mStreamingInactiveList.end(); itr++) mNumInactiveStreamingStreams++; for(StreamingList::iterator itr = mStreamingCulledList.begin(); itr != mStreamingCulledList.end(); itr++) mNumCulledStreamingStreams++; Con::setIntVariable("Audio::numOpenHandles", mNumOpenHandles); Con::setIntVariable("Audio::numOpenLoopingHandles", mNumOpenLoopingHandles); Con::setIntVariable("Audio::numOpenStreamingHandles", mNumOpenStreamingHandles); Con::setIntVariable("Audio::numActiveStreams", mNumActiveStreams); Con::setIntVariable("Audio::numNullActiveStreams", mNumNullActiveStreams); Con::setIntVariable("Audio::numActiveLoopingStreams", mNumActiveLoopingStreams); Con::setIntVariable("Audio::numActiveStreamingStreams", mNumActiveStreamingStreams); Con::setIntVariable("Audio::numLoopingStreams", mNumLoopingStreams); Con::setIntVariable("Audio::numInactiveLoopingStreams", mNumInactiveLoopingStreams); Con::setIntVariable("Audio::numCulledLoopingStreams", mNumCulledLoopingStreams); Con::setIntVariable("Audio::numStreamingStreams", mNumStreamingStreams); Con::setIntVariable("Audio::numInactiveStreamingStreams", mNumInactiveStreamingStreams); Con::setIntVariable("Audio::numCulledStreamingStreams", mNumCulledStreamingStreams); } #endif //-------------------------------------------------------------------------- // Audio Update... //-------------------------------------------------------------------------- void alxLoopingUpdate() { static LoopingList culledList; U32 updateTime = Platform::getRealMilliseconds(); // check if can wakeup the inactive loopers if(mLoopingCulledList.size()) { Point3F listener; alxGetListenerPoint3F(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; mSampleEnvironment[index] = (*itr)->mEnvironment; ALuint source = mSource[index]; // setup play info alGetError(); alxSourcePlay(source, *itr); if(mEnvironmentEnabled) alxSourceEnvironment(source, *itr); alxPlay(mHandle[index]); } } } void alxStreamingUpdate() { // update buffer queues on active streamers // update the loopers for(StreamingList::iterator itr = mStreamingList.begin(); itr != mStreamingList.end(); itr++) { if((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT) continue; (*itr)->updateBuffers(); } static StreamingList culledList; U32 updateTime = Platform::getRealMilliseconds(); // check if can wakeup the inactive loopers if(mStreamingCulledList.size()) { Point3F listener; alxGetListenerPoint3F(AL_POSITION, &listener); // get the 'sort' value for this sound (could be based on time played...), // and add to the culled list StreamingList::iterator itr; culledList.clear(); for(itr = mStreamingCulledList.begin(); itr != mStreamingCulledList.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 StreamingList::iterator tmp; tmp = mStreamingCulledList.findImage((*itr)->mHandle); AssertFatal(tmp, "alxStreamingUpdate: failed to find culled source"); mStreamingCulledList.erase_fast(tmp); // remove from streaming list (and free) tmp = mStreamingList.findImage((*itr)->mHandle); if(tmp) { delete(*tmp); mStreamingList.erase_fast(tmp); } continue; //} } // remove from culled list StreamingList::iterator tmp = mStreamingCulledList.findImage((*itr)->mHandle); AssertFatal(tmp, "alxStreamingUpdate: failed to find culled source"); mStreamingCulledList.erase_fast(tmp); alxSourcePlay(*itr); // restore all state data mHandle[index] = (*itr)->mHandle; mScore[index] = (*itr)->mScore; mSourceVolume[index] = (*itr)->mDescription.mVolume; mType[index] = (*itr)->mDescription.mType; mSampleEnvironment[index] = (*itr)->mEnvironment; ALuint source = mSource[index]; (*itr)->mSource = mSource[index]; // setup play info alGetError(); 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; 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; } // should be playing? must have encounted an error.. remove // StreamingList::iterator itr2 = mStreamingList.findImage(mHandle[i]); // if(itr2 && !((*itr2)->mHandle & AUDIOHANDLE_INACTIVE_BIT)) // { // AssertFatal(!mStreamingInactiveList.findImage((*itr2)->mHandle), "alxCloseHandles: image incorrectly in inactive list"); // AssertFatal(!mStreamingCulledList.findImage((*itr2)->mHandle), "alxCloseHandles: image already in culled list"); // (*itr2)->freeStream(); // // mStreamingCulledList.push_back(*itr2); // (*itr2)->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; alGetListener3f(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; } // grab the volume.. (not attenuated by master for score) F32 volume = mSourceVolume[i] * mAudioTypeVolume[mType[i]]; // 3d? mScore[i] = volume; #ifdef REL_WORKAROUND ALint val = AL_FALSE; alGetSourcei(mSource[i], AL_SOURCE_ABSOLUTE, &val); if(val == AL_TRUE) #else ALint val = AL_FALSE; alGetSourcei(mSource[i], AL_SOURCE_RELATIVE, &val); if(val == AL_FALSE) #endif { // approximate 3d volume Point3F pos; alGetSourcefv(mSource[i], AL_POSITION, (ALfloat*)((F32*)pos) ); ALfloat min=0, max=1; alGetSourcef(mSource[i], AL_REFERENCE_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] *= (max-dist) / (max-min); } } if(sourcesOnly) return; U32 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; (*itr)->mScore = (*itr)->mDescription.mVolume; if((*itr)->mDescription.mIs3D) { Point3F pos = (*itr)->mPosition - listener; F32 dist = pos.magnitudeSafe(); F32 min = (*itr)->mDescription.mReferenceDistance; F32 max = (*itr)->mDescription.mMaxDistance; if(dist >= max) (*itr)->mScore = 0.f; else if(dist > min) (*itr)->mScore *= (max-dist) / (max-min); } // attenuate by the channel gain (*itr)->mScore *= mAudioTypeVolume[(*itr)->mDescription.mType]; } // update the streamers for(StreamingList::iterator itr = mStreamingList.begin(); itr != mStreamingList.end(); itr++) { if(!((*itr)->mHandle & AUDIOHANDLE_INACTIVE_BIT)) continue; if((updateTime - (*itr)->mCullTime) < MIN_UNCULL_PERIOD) continue; (*itr)->mScore = (*itr)->mDescription.mVolume; if((*itr)->mDescription.mIs3D) { Point3F pos = (*itr)->mPosition - listener; F32 dist = pos.magnitudeSafe(); F32 min = (*itr)->mDescription.mReferenceDistance; F32 max = (*itr)->mDescription.mMaxDistance; if(dist >= max) (*itr)->mScore = 0.f; else if(dist > min) (*itr)->mScore *= (max-dist) / (max-min); } // 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() { Point3F listener; alGetListener3f(AL_POSITION, &listener.x, &listener.y, &listener.z); for(U32 i = 0; i < mNumSources; i++) { if(mHandle[i] == NULL_AUDIOHANDLE) continue; #ifdef REL_WORKAROUND ALint val = AL_FALSE; alGetSourcei(mSource[i], AL_SOURCE_ABSOLUTE, &val); if(val == AL_FALSE) #else ALint val = AL_FALSE; alGetSourcei(mSource[i], AL_SOURCE_RELATIVE, &val); if(val == AL_TRUE) #endif 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(); F32 gain = (dist < 0.f) ? 0.f : mSourceVolume[i] * mAudioTypeVolume[mType[i]] * mMasterVolume; alSourcef(mSource[i], AL_GAIN, Audio::linearToDB(gain)); } } //-------------------------------------------------------------------------- // Called to update alx system //-------------------------------------------------------------------------- void alxUpdate() { //if(mForceMaxDistanceUpdate) alxUpdateMaxDistance(); alxCloseHandles(); alxUpdateScores(false); alxLoopingUpdate(); alxStreamingUpdate(); #ifdef TORQUE_GATHER_METRICS alxGatherMetrics(); #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); } //-------------------------------------------------------------------------- // Environment: //-------------------------------------------------------------------------- void alxEnvironmenti(ALenum pname, ALint value) { /* todo alEnvironmentiIASIG(mEnvironment, pname, value); */ } void alxEnvironmentf(ALenum pname, ALfloat value) { /* todo alEnvironmentfIASIG(mEnvironment, pname, value); */ } void alxGetEnvironmenti(ALenum pname, ALint * value) { /* todo alGetEnvironmentiIASIG_EXT(mEnvironment, pname, value); */ } void alxGetEnvironmentf(ALenum pname, ALfloat * value) { /* todo 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? #ifdef REL_WORKAROUND alGetSourcei(mSource[i], AL_SOURCE_ABSOLUTE, &val); if(val == AL_FALSE) #else alGetSourcei(mSource[i], AL_SOURCE_RELATIVE, &val); if(val == AL_TRUE) #endif 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; /* todo alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, (*itr)->mDescription.mEnvironmentLevel); */ } /* todo else alSourcef(mSource[i], AL_ENV_SAMPLE_REVERB_MIX_EXT, 0.f); */ } mEnvironmentEnabled = enable; } void alxSetEnvironment(const AudioEnvironment * env) { /* todo mCurrentEnvironment = const_cast(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); } F32 alxGetStreamPosition( AUDIOHANDLE handle ) { StreamingList::iterator itr = mStreamingList.findImage(handle); if( !itr ) return -1.f; return (*itr)->getElapsedTime(); } F32 alxGetStreamDuration( AUDIOHANDLE handle ) { StreamingList::iterator itr = mStreamingList.findImage(handle); if( !itr ) return -1.f; return (*itr)->getTotalTime(); } // 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)); // 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(*itr); if(profile != NULL && profile->isPreload()) { Resource buffer = AudioBuffer::find(profile->mFilename); if((bool)buffer) ALuint bufferId = buffer->getALBuffer(); } } } return(true); } void shutdownContext() { // invalidate active handles dMemset(mSource, 0, sizeof(mSource)); } //-------------------------------------------------------------------------- bool OpenALInit() { OpenALShutdown(); if(!OpenALDLLInit()) return false; // Open a device #ifdef TORQUE_OS_LINUX const char* deviceSpecifier = Con::getVariable("Pref::Unix::OpenALSpecifier"); if (dStrlen(deviceSpecifier) == 0) // use SDL for audio output by default deviceSpecifier = "'((devices '(sdl)))"; mDevice = (ALCdevice *)alcOpenDevice((ALubyte*)deviceSpecifier); #else mDevice = (ALCdevice *)alcOpenDevice((ALubyte*)NULL); #endif if (mDevice == (ALCdevice *)NULL) return false; // Create an openAL context #ifdef TORQUE_OS_LINUX int freq = Con::getIntVariable("Pref::Unix::OpenALFrequency"); if (freq == 0) freq = 22050; Con::printf(" Setting OpenAL output frequency to %d", freq); // some versions of openal have bugs converting between 22050 and 44100 // samples when the lib is in 44100 mode. int attrlist[] = { // this 0x100 is "ALC_FREQUENCY" in the linux openal implementation. // it doesn't match the value of the creative headers, so we can't use // that define. seems like linux torque really shouldn't be using the // creative headers. 0x100, freq, 0 }; mContext = alcCreateContext(mDevice,attrlist); #else mContext = alcCreateContext(mDevice,NULL); #endif if (mContext == NULL) return false; // Make this context the active context alcMakeContextCurrent(mContext); mNumSources = mRequestSources; alGenSources(mRequestSources, mSource); // invalidate all existing handles dMemset(mHandle, NULL_AUDIOHANDLE, sizeof(mHandle)); // default all channels to full gain for(U32 i = 0; i < Audio::NumAudioTypes; i++) mAudioTypeVolume[i] = 1.f; // Clear Error Code alGetError(); // Similiar to DSound Model w/o min distance clamping alEnable(AL_DISTANCE_MODEL); alDistanceModel(AL_INVERSE_DISTANCE); alListenerf(AL_GAIN_LINEAR, 1.f); return true; } //-------------------------------------------------------------------------- void OpenALShutdown() { alxStopAll(); //if(mInitialized) { alxEnvironmentDestroy(); } 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(); } for(U32 i = 0; i < MAX_AUDIOSOURCES; i++) mBuffer[i] = 0; alDeleteSources(mNumSources, mSource); if (mContext) { alcDestroyContext(mContext); mContext = NULL; } if (mDevice) { alcCloseDevice(mDevice); mDevice = NULL; } OpenALDLLShutdown(); } } // end OpenAL namespace AudioStreamSource* alxFindAudioStreamSource(AUDIOHANDLE handle) { StreamingList::iterator itr2 = mStreamingList.findImage(handle); if(itr2) return *itr2; return NULL; }