/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "HeadSpin.h" #include "al.h" #include "alc.h" #include "efx.h" #include #ifdef EAX_SDK_AVAILABLE #include #endif #include "hsTimer.h" #include "hsGeometry3.h" #include "plgDispatch.h" #include "plProfile.h" #include "../plStatusLog/plStatusLog.h" #include "plSound.h" #include "plAudioCaps.h" #include "plAudioSystem.h" #include "plDSoundBuffer.h" #include "plEAXEffects.h" #include "plEAXListenerMod.h" #include "plVoiceChat.h" #include "../pnMessage/plAudioSysMsg.h" #include "../plMessage/plRenderMsg.h" #include "../pnMessage/plRefMsg.h" #include "../plMessage/plAgeLoadedMsg.h" #include "../pnMessage/plTimeMsg.h" #include "../pnKeyedObject/plFixedKey.h" #include "../pnKeyedObject/plKey.h" #define SAFE_RELEASE(p) if(p){ p->Release(); p = nil; } #define FADE_TIME 3 #define MAX_NUM_SOURCES 128 #define UPDATE_TIME_MS 100 plProfile_CreateTimer("EAX Update", "Sound", SoundEAXUpdate); plProfile_CreateTimer("Soft Update", "Sound", SoundSoftUpdate); plProfile_CreateCounter("Max Sounds", "Sound", SoundMaxNum); plProfile_CreateTimer("AudioUpdate", "RenderSetup", AudioUpdate); typedef std::vector::iterator DeviceIter; //// Internal plSoftSoundNode Class Definition /////////////////////////////// class plSoftSoundNode { public: const plKey fSoundKey; hsScalar fRank; plSoftSoundNode *fNext; plSoftSoundNode **fPrev; plSoftSoundNode *fSortNext; plSoftSoundNode **fSortPrev; plSoftSoundNode( const plKey s ) : fSoundKey( s ) { fNext = nil; fPrev = nil; } ~plSoftSoundNode() { Unlink(); } void Link( plSoftSoundNode **prev ) { fNext = *prev; fPrev = prev; if( fNext != nil ) fNext->fPrev = &fNext; *prev = this; } void Unlink( void ) { if( fPrev != nil ) { *fPrev = fNext; if( fNext ) fNext->fPrev = fPrev; fPrev = nil; fNext = nil; } } void SortedLink( plSoftSoundNode **prev, hsScalar rank ) { fRank = rank; fSortNext = *prev; fSortPrev = prev; if( fSortNext != nil ) fSortNext->fSortPrev = &fSortNext; *prev = this; } // Larger values are first in the list void AddToSortedLink( plSoftSoundNode *toAdd, hsScalar rank ) { if( fRank > rank ) { if( fSortNext != nil ) fSortNext->AddToSortedLink( toAdd, rank ); else { toAdd->SortedLink( &fSortNext, rank ); } } else { // Cute trick here... toAdd->SortedLink( fSortPrev, rank ); } } void BootSourceOff( void ) { plSound *sound = plSound::ConvertNoRef( fSoundKey->ObjectIsLoaded() ); if( sound != nil ) { sound->ForceUnregisterFromAudioSys(); } } }; // plAudioSystem ////////////////////////////////////////////////////////////////////////// Int32 plAudioSystem::fMaxNumSounds = 16; Int32 plAudioSystem::fNumSoundsSlop = 8; plAudioSystem::plAudioSystem() : fStartTime(0), fListenerInit(false), fSoftRegionSounds(nil), fActiveSofts(nil), fCurrDebugSound(nil), fDebugActiveSoundDisplay(nil), fUsingEAX(false), fRestartOnDestruct(false), fWaitingForShutdown(false), fActive(false), fDisplayNumBuffers(false), fStartFade(0), fFadeLength(FADE_TIME), fCaptureDevice(nil), fLastUpdateTimeMs(0) { fCurrListenerPos.Set( -1.e30, -1.e30, -1.e30 ); //fCommittedListenerPos.Set( -1.e30, -1.e30, -1.e30 ); fLastPos.Set(100, 100, 100); } plAudioSystem::~plAudioSystem() { } void plAudioSystem::IEnumerateDevices() { fDeviceList.clear(); plStatusLog::AddLineS("audio.log", "--Audio Devices --" ); char *devices = (char *)alcGetString(nil, ALC_DEVICE_SPECIFIER); int major, minor; while(*devices != nil) { ALCdevice *device = alcOpenDevice(devices); if (device) { ALCcontext *context = alcCreateContext(device, NULL); if (context) { alcMakeContextCurrent(context); // if new actual device name isn't already in the list, then add it... bool bNewName = true; for (DeviceIter i = fDeviceList.begin(); i != fDeviceList.end(); i++) { if (strcmp((*i).GetDeviceName(), devices) == 0) { bNewName = false; } } if ((bNewName)) { alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(int), &major); alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(int), &minor); plStatusLog::AddLineS("audio.log", "%s OpenAL ver: %d.%d", devices, major, minor ); // filter out any devices that aren't openal 1.1 compliant if(major > 1 || (major == 1 && minor >= 1)) { hsBool supportsEAX = false; #ifdef EAX_SDK_AVAILABLE if(alIsExtensionPresent((ALchar *)"EAX4.0") || alIsExtensionPresent((ALchar *) "EAX4.0Emulated")) { supportsEAX = true; } #endif DeviceDescriptor desc(devices, supportsEAX); fDeviceList.push_back(desc); } } alcMakeContextCurrent(nil); alcDestroyContext(context); } alcCloseDevice(device); } devices += strlen(devices) + 1; } DeviceDescriptor temp("", 0); // attempt to order devices for(unsigned i = 0; i < fDeviceList.size(); ++i) { if(strstr(fDeviceList[i].GetDeviceName(), "Software")) { temp = fDeviceList[i]; fDeviceList[i] = fDeviceList[0]; fDeviceList[0] = temp; } if(strstr(fDeviceList[i].GetDeviceName(), "Hardware")) { temp = fDeviceList[i]; fDeviceList[i] = fDeviceList[1]; fDeviceList[1] = temp; } } } //// Init //////////////////////////////////////////////////////////////////// hsBool plAudioSystem::Init( hsWindowHndl hWnd ) { plgAudioSys::fRestarting = false; static hsBool firstTimeInit = true; plStatusLog::AddLineS( "audio.log", plStatusLog::kBlue, "ASYS: -- Init --" ); fMaxNumSources = 0; plStatusLog::AddLineS( "audio.log", plStatusLog::kGreen, "ASYS: Detecting caps..." ); plSoundBuffer::Init(); // Set the maximum number of sounds based on priority cutoff slider SetMaxNumberOfActiveSounds(); const char *deviceName = plgAudioSys::fDeviceName.c_str(); hsBool useDefaultDevice = true; if(firstTimeInit) { IEnumerateDevices(); firstTimeInit = false; } if(!fDeviceList.size()) { plStatusLog::AddLineS( "audio.log", plStatusLog::kRed, "ASYS: ERROR Unable to query any devices, is openal installed?" ); return false; } // shouldn't ever happen, but just in case if(!deviceName) plgAudioSys::SetDeviceName(DEFAULT_AUDIO_DEVICE_NAME); for(DeviceIter i = fDeviceList.begin(); i != fDeviceList.end(); i++) { if(!strcmp((*i).GetDeviceName(), deviceName)) { useDefaultDevice = false; break; } } if(useDefaultDevice) { // if no device has been specified we will use the "Generic Software" device by default. If "Generic Software" is unavailable(which can happen) we select the first available device // We want to use software by default since some audio cards have major problems with hardware + eax. const char *defaultDev = fDeviceList.front().GetDeviceName(); for(DeviceIter i = fDeviceList.begin(); i != fDeviceList.end(); i++) { if(!strcmp(DEFAULT_AUDIO_DEVICE_NAME, (*i).GetDeviceName())) { defaultDev = DEFAULT_AUDIO_DEVICE_NAME; break; } } plgAudioSys::SetDeviceName(defaultDev, false); fDevice = alcOpenDevice(defaultDev); plStatusLog::AddLineS( "audio.log", plStatusLog::kRed, "ASYS: %s device selected", defaultDev ); deviceName = defaultDev; } else { plgAudioSys::SetDeviceName(deviceName, false); fDevice = alcOpenDevice(deviceName); plStatusLog::AddLineS( "audio.log", plStatusLog::kRed, "ASYS: %s device selected", deviceName ); } if(!fDevice) { plStatusLog::AddLineS( "audio.log", plStatusLog::kRed, "ASYS: ERROR initializing OpenAL" ); return false; } fContext = alcCreateContext(fDevice, 0); alcMakeContextCurrent(fContext); ALenum error; if(alGetError() != AL_NO_ERROR) { plStatusLog::AddLineS( "audio.log", plStatusLog::kRed, "ASYS: ERROR alcMakeContextCurrent failed" ); return false; } plStatusLog::AddLineS("audio.log", "OpenAL vendor: %s", alGetString(AL_VENDOR)); plStatusLog::AddLineS("audio.log", "OpenAL version: %s", alGetString(AL_VERSION)); plStatusLog::AddLineS("audio.log", "OpenAL renderer: %s", alGetString(AL_RENDERER)); plStatusLog::AddLineS("audio.log", "OpenAL extensions: %s", alGetString(AL_EXTENSIONS)); plAudioCaps caps = plAudioCapsDetector::Detect(); if(strcmp(deviceName, DEFAULT_AUDIO_DEVICE_NAME)) { // we are using a hardware device, set priority based on number of hardware voices unsigned int numVoices = caps.GetMaxNumVoices(); if(numVoices < 16) plgAudioSys::SetPriorityCutoff(3); SetMaxNumberOfActiveSounds(); } // setup capture device ALCsizei bufferSize = FREQUENCY * 2 * BUFFER_LEN_SECONDS; // times 2 for 16-bit format //const char *dev = alcGetString(fDevice, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); fCaptureDevice = alcCaptureOpenDevice(nil, FREQUENCY, AL_FORMAT_MONO16, bufferSize); fMaxNumSources = caps.GetMaxNumVoices(); // attempt to init the EAX listener. if( plgAudioSys::fEnableEAX ) { fUsingEAX = plEAXListener::GetInstance().Init(); if( fUsingEAX ) { plStatusLog::AddLineS( "audio.log", plStatusLog::kGreen, "ASYS: EAX support detected and enabled." ); } else { plStatusLog::AddLineS( "audio.log", plStatusLog::kRed, "ASYS: EAX support NOT detected. EAX effects disabled." ); } } else fUsingEAX = false; plProfile_Set(SoundMaxNum, fMaxNumSounds); alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); error = alGetError(); fWaitingForShutdown = false; plgDispatch::Dispatch()->RegisterForExactType( plAgeLoadedMsg::Index(), GetKey() ); return true; } //// Shutdown //////////////////////////////////////////////////////////////// void plAudioSystem::Shutdown() { plStatusLog::AddLineS( "audio.log", plStatusLog::kBlue, "ASYS: -- Shutdown --" ); plSoundBuffer::Shutdown(); // Delete our active sounds list delete fDebugActiveSoundDisplay; fDebugActiveSoundDisplay = nil; // Unregister soft sounds while( fSoftRegionSounds != nil ) { fSoftRegionSounds->BootSourceOff(); delete fSoftRegionSounds; } while( fActiveSofts != nil ) { fActiveSofts->BootSourceOff(); delete fActiveSofts; } while( fEAXRegions.GetCount() > 0 ) { if( fEAXRegions[ 0 ] != nil && fEAXRegions[ 0 ]->GetKey() != nil ) { GetKey()->Release( fEAXRegions[ 0 ]->GetKey() ); } fEAXRegions.Remove( 0 ); } plEAXListener::GetInstance().ClearProcessCache(); plSound::SetCurrDebugPlate( nil ); fCurrDebugSound = nil; // Reset this, just in case fPendingRegisters.Reset(); //fListenerInit = false; if( fUsingEAX ) { plEAXListener::GetInstance().Shutdown(); } alcCaptureStop(fCaptureDevice); alcCaptureCloseDevice(fCaptureDevice); fCaptureDevice = nil; alcMakeContextCurrent(nil); alcDestroyContext(fContext); alcCloseDevice(fDevice); fContext = nil; fDevice = nil; fStartTime = 0; fUsingEAX = false; fCurrListenerPos.Set( -1.e30, -1.e30, -1.e30 ); //fCommittedListenerPos.Set( -1.e30, -1.e30, -1.e30 ); if( fRestartOnDestruct ) { fRestartOnDestruct = false; plgAudioSys::Activate( true ); } plgDispatch::Dispatch()->UnRegisterForExactType(plAgeLoadedMsg::Index(), GetKey() ); fWaitingForShutdown = false; } int plAudioSystem::GetNumAudioDevices() { return fDeviceList.size(); } const char *plAudioSystem::GetAudioDeviceName(int index) { if(index < 0 || index >= fDeviceList.size()) { hsAssert(false, "Invalid index passed to GetAudioDeviceName"); return nil; } return fDeviceList[index].GetDeviceName(); } hsBool plAudioSystem::SupportsEAX(const char *deviceName) { for(DeviceIter i = fDeviceList.begin(); i != fDeviceList.end(); i++) { if(!strcmp((*i).GetDeviceName(), deviceName)) { return (*i).SupportsEAX(); } } return false; } void plAudioSystem::SetDistanceModel(int i) { switch(i) { case 0: alDistanceModel(AL_NONE); break; case 1: alDistanceModel(AL_INVERSE_DISTANCE ); break; case 2: alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); break; case 3: alDistanceModel(AL_LINEAR_DISTANCE ); break; case 4: alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED ); break; case 5: alDistanceModel(AL_EXPONENT_DISTANCE ); break; case 6: alDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED); break; } } // Set the number of active sounds the audio system is allowed to play, based on the priority cutoff void plAudioSystem::SetMaxNumberOfActiveSounds() { UInt16 priorityCutoff = plgAudioSys::GetPriorityCutoff(); int maxNumSounds = 24; // Keep this to a reasonable amount based on the users hardware, since we want the sounds to be played in hardware if(maxNumSounds > fMaxNumSources && fMaxNumSources != 0 ) maxNumSounds = fMaxNumSources / 2; // Allow a certain number of sounds based on a user specified setting. // Above that limit, we want 1/2 more sounds (8 for the max of 16) for a slop buffer, // if they fit, so that sounds that shouldn't be playing can still play in case they suddenly pop // back into priority range (so we don't incur performance hits when sounds hover on // the edge of being high enough priority to play) fMaxNumSounds = maxNumSounds; fNumSoundsSlop = fMaxNumSounds / 2; plStatusLog::AddLineS( "audio.log", "Max Number of Sounds Set to: %d", fMaxNumSounds); } void plAudioSystem::SetListenerPos(const hsPoint3 pos) { fCurrListenerPos = pos; alListener3f(AL_POSITION, pos.fX, pos.fZ, -pos.fY); // negate z coord, since openal uses opposite handedness } void plAudioSystem::SetListenerVelocity(const hsVector3 vel) { alListener3f(AL_VELOCITY, 0, 0, 0); // no doppler shift } void plAudioSystem::SetListenerOrientation(const hsVector3 view, const hsVector3 up) { ALfloat orientation[] = { view.fX, view.fZ, -view.fY, up.fX, up.fZ, -up.fY }; alListenerfv(AL_ORIENTATION, orientation); } void plAudioSystem::SetActive( hsBool b ) { fActive = b; if( fActive ) { // Clear to send activate message (if listener not inited yet, delay until then) plgDispatch::MsgSend( TRACKED_NEW plAudioSysMsg( plAudioSysMsg::kActivate ) ); } } //// IRegisterSoftSound ////////////////////////////////////////////////////// // Note: each sound might kick another off the top-n-ranked-sounds list, so // we call IUpdateSoftSounds() each time one is added. Ugly as sin, but at // least all the calc code is in one place. Possible optimization in the // future: when calling IUpdate(), any sounds that are already active don't // need to be recalced, just resorted. void plAudioSystem::RegisterSoftSound( const plKey soundKey ) { plSoftSoundNode *node = TRACKED_NEW plSoftSoundNode( soundKey ); node->Link( &fSoftRegionSounds ); fCurrDebugSound = nil; plSound::SetCurrDebugPlate( nil ); } //// IUnregisterSoftSound //////////////////////////////////////////////////// void plAudioSystem::UnregisterSoftSound( const plKey soundKey ) { plSoftSoundNode *node; for( node = fActiveSofts; node != nil; node = node->fNext ) { if( node->fSoundKey == soundKey ) { delete node; return; } } for( node = fSoftRegionSounds; node != nil; node = node->fNext ) { if( node->fSoundKey == soundKey ) { delete node; return; } } // We might have unregistered it ourselves on destruction, so don't bother fCurrDebugSound = nil; plSound::SetCurrDebugPlate( nil ); } //// IUpdateSoftSounds /////////////////////////////////////////////////////// // OK, so the listener moved. Since our sound soft volumes are all based on // the listener's position, we have to update all sounds with soft volumes // now. This involves some steps: // - Determining what volumes the listener has moved out of // - Determining what volumes the listener has changed position in // - Determining what volumes the listener has entered // - Updating the levels of all sounds associated with all of the above // volumes // The first two tests are easy, since we'll have kept track of what volumes // the listener was in last time. The last part is the tricky one and the // one that will kill us in performance if we're not careful. However, we // can first check the bounding box of the sounds in question, since outside // of them the soft volume won't have any effect. The last part is simply a // matter of querying the sound volumes based on the listener's position and // setting the soft volume attenuation on the sound to that level. // // Note: for each sound that is still in range, we call CalcSoftVolume() and // use the resulting value to rank each sound. Then, we take the top n sounds // and disable the rest. We *could* rank by distance to listener, which would // be far faster; however, we could have a sound (or background music) that // is technically closest to the listener but completely faded by a soft volume, // thus needlessly cutting off another sound that might be more important. // This way is slower, but better quality. // Also note: to differentiate between two sounds in the same soft volume at // full strength, we divide the soft volume ranks by the distSquared, thus // giving us a reasonable approximation at a good rank... void plAudioSystem::IUpdateSoftSounds( const hsPoint3 &newPosition ) { plSoftSoundNode *node, *myNode; hsScalar distSquared, rank; plSoftSoundNode *sortedList = nil; Int32 i; plProfile_BeginTiming(SoundSoftUpdate); // Check the sounds the listener is already inside of. If we moved out, stop sound, else // just change attenuation for( node = fActiveSofts; node != nil; ) { plSound *sound = plSound::ConvertNoRef( node->fSoundKey->ObjectIsLoaded() ); bool notActive = false; if(sound) sound->Update(); // Quick checks for not-active if( sound == nil ) notActive = true; else if( !sound->IsWithinRange( newPosition, &distSquared ) ) notActive = true; else if(sound->GetPriority() > plgAudioSys::GetPriorityCutoff()) notActive = true; if(plgAudioSys::fMutedStateChange) { sound->SetMuted(plgAudioSys::fMuted); } if( !notActive ) { /// Our initial guess is that it's enabled... sound->CalcSoftVolume( true, distSquared ); rank = sound->GetVolumeRank(); if( rank <= 0.f ) notActive = true; else { /// Queue up in our sorted list... if( sortedList == nil ) node->SortedLink( &sortedList, (10.0f - sound->GetPriority()) * rank ); else sortedList->AddToSortedLink( node, (10.0f - sound->GetPriority()) * rank ); /// Still in radius, so consider it still "active". node = node->fNext; } } if( notActive ) { /// Moved out of range of the sound--stop the sound entirely and move it to our /// yeah-they're-registered-but-not-active list myNode = node; node = node->fNext; myNode->Unlink(); myNode->Link( &fSoftRegionSounds ); /// We know this sound won't be enabled, so skip the Calc() call if( sound != nil ) sound->UpdateSoftVolume( false ); } } // Now check remaining sounds to see if the listener moved into them for( node = fSoftRegionSounds; node != nil; ) { if( !fListenerInit ) { node = node->fNext; continue; } plSound *sound = plSound::ConvertNoRef( node->fSoundKey->ObjectIsLoaded() ); if( !sound || sound->GetPriority() > plgAudioSys::GetPriorityCutoff() ) { node = node->fNext; continue; } sound->Update(); if(plgAudioSys::fMutedStateChange) { sound->SetMuted(plgAudioSys::fMuted); } if( sound->IsWithinRange( newPosition, &distSquared ) ) { /// Our initial guess is that it's enabled... sound->CalcSoftVolume( true, distSquared ); rank = sound->GetVolumeRank(); if( rank > 0.f ) { /// We just moved into its range, so move it to our active list and start the sucker myNode = node; node = node->fNext; myNode->Unlink(); myNode->Link( &fActiveSofts ); /// Queue up in our sorted list... if( sortedList == nil ) myNode->SortedLink( &sortedList, (10.0f - sound->GetPriority()) * rank ); else sortedList->AddToSortedLink( myNode, (10.0f - sound->GetPriority()) * rank ); } else { /// Do NOT notify sound, since we were outside of its range and still are // (but if we're playing, we shouldn't be, so better update) if( sound->IsPlaying() ) sound->UpdateSoftVolume( false ); node = node->fNext; } } else { /// Do NOT notify sound, since we were outside of its range and still are node = node->fNext; sound->Disable(); // ensure that dist attenuation is set to zero so we don't accidentally play } } plgAudioSys::fMutedStateChange = false; /// Go through sorted list, enabling only the first n sounds and disabling the rest // DEBUG: Create a screen-only statusLog to display which sounds are what if( fDebugActiveSoundDisplay == nil ) fDebugActiveSoundDisplay = plStatusLogMgr::GetInstance().CreateStatusLog( 32, "Active Sounds", plStatusLog::kDontWriteFile | plStatusLog::kDeleteForMe | plStatusLog::kFilledBackground ); fDebugActiveSoundDisplay->Clear(); if(fDisplayNumBuffers) fDebugActiveSoundDisplay->AddLineF(0xffffffff, "Num Buffers: %d", plDSoundBuffer::GetNumBuffers() ); fDebugActiveSoundDisplay->AddLine("Not streamed", plStatusLog::kGreen); fDebugActiveSoundDisplay->AddLine("Disk streamed", plStatusLog::kYellow); fDebugActiveSoundDisplay->AddLine("RAM streamed", plStatusLog::kWhite); fDebugActiveSoundDisplay->AddLine("Ogg streamed", plStatusLog::kRed); fDebugActiveSoundDisplay->AddLine("Incidentals", 0xff00ffff); fDebugActiveSoundDisplay->AddLine("--------------------"); for( i = 0; sortedList != nil && i < fMaxNumSounds; sortedList = sortedList->fSortNext ) { plSound *sound = plSound::ConvertNoRef( sortedList->fSoundKey->GetObjectPtr() ); if(!sound) continue; /// Notify sound that it really is still enabled sound->UpdateSoftVolume( true ); UInt32 color = plStatusLog::kGreen; switch (sound->GetStreamType()) { case plSound::kStreamFromDisk: color = plStatusLog::kYellow; break; case plSound::kStreamFromRAM: color = plStatusLog::kWhite; break; case plSound::kStreamCompressed: color = plStatusLog::kRed; break; } if(sound->GetType() == plgAudioSys::kVoice) color = 0xffff8800; if(sound->IsPropertySet(plSound::kPropIncidental)) color = 0xff00ffff; if( fUsingEAX && sound->GetEAXSettings().IsEnabled() ) { fDebugActiveSoundDisplay->AddLineF( color, "%d %1.2f %1.2f (%d occ) %s", sound->GetPriority(), sortedList->fRank, sound->GetVolume() ? sound->GetVolumeRank() / sound->GetVolume() : 0, sound->GetEAXSettings().GetCurrSofts().GetOcclusion(), sound->GetKeyName() ); } else { fDebugActiveSoundDisplay->AddLineF( color, "%d %1.2f %1.2f %s", sound->GetPriority(), sortedList->fRank, sound->GetVolume() ? sound->GetVolumeRank() / sound->GetVolume() : 0, sound->GetKeyName() ); } i++; } for( ; sortedList != nil; sortedList = sortedList->fSortNext, i++ ) { plSound *sound = plSound::ConvertNoRef( sortedList->fSoundKey->GetObjectPtr() ); if(!sound) continue; /// These unlucky sounds don't get to play (yet). Also, be extra mean /// and pretend we're updating for "the first time", which will force them to /// stop immediately // Update: since being extra mean can incur a nasty performance hit when sounds hover back and // forth around the fMaxNumSounds mark, we have a "slop" allowance: i.e. sounds that we're going // to say shouldn't be playing but we'll let them play for a bit anyway just in case they raise // in priority. So only be mean to the sounds outside this slop range sound->UpdateSoftVolume( false, ( i < fMaxNumSounds + fNumSoundsSlop ) ? false : true ); fDebugActiveSoundDisplay->AddLineF( 0xff808080, "%d %1.2f %s", sound->GetPriority(), sound->GetVolume() ? sound->GetVolumeRank() / sound->GetVolume() : 0, sound->GetKeyName() ); } plProfile_EndTiming(SoundSoftUpdate); } void plAudioSystem::NextDebugSound( void ) { plSoftSoundNode *node; if( fCurrDebugSound == nil ) fCurrDebugSound = ( fSoftRegionSounds == nil ) ? fActiveSofts : fSoftRegionSounds; else { node = fCurrDebugSound; fCurrDebugSound = fCurrDebugSound->fNext; if( fCurrDebugSound == nil ) { // Trace back to find which list we were in for( fCurrDebugSound = fSoftRegionSounds; fCurrDebugSound != nil; fCurrDebugSound = fCurrDebugSound->fNext ) { if( fCurrDebugSound == node ) // Was in first list, move to 2nd { fCurrDebugSound = fActiveSofts; break; } } // else Must've been in 2nd list, so keep nil } } if( fCurrDebugSound != nil ) plSound::SetCurrDebugPlate( fCurrDebugSound->fSoundKey ); else plSound::SetCurrDebugPlate( nil ); } void plAudioSystem::SetFadeLength(float lengthSec) { fFadeLength = lengthSec; } hsBool plAudioSystem::MsgReceive(plMessage* msg) { if(plTimeMsg *time = plTimeMsg::ConvertNoRef( msg ) ) { if(!plgAudioSys::IsMuted()) { double currTime = hsTimer::GetSeconds(); if(fStartFade == 0) { plStatusLog::AddLineS("audio.log", "Starting Fade %f", currTime); } if((currTime - fStartFade) > fFadeLength) { fStartFade = 0; plgDispatch::Dispatch()->UnRegisterForExactType( plTimeMsg::Index(), GetKey() ); plStatusLog::AddLineS("audio.log", "Stopping Fade %f", currTime); plgAudioSys::SetGlobalFadeVolume( 1.0 ); } else { plgAudioSys::SetGlobalFadeVolume( (hsScalar)((currTime-fStartFade) / fFadeLength) ); } } return true; } if (plAudioSysMsg* pASMsg = plAudioSysMsg::ConvertNoRef( msg )) { if (pASMsg->GetAudFlag() == plAudioSysMsg::kPing && fListenerInit) { plAudioSysMsg* pMsg = TRACKED_NEW plAudioSysMsg( plAudioSysMsg::kActivate ); pMsg->AddReceiver( pASMsg->GetSender() ); pMsg->SetBCastFlag(plMessage::kBCastByExactType, false); plgDispatch::MsgSend( pMsg ); return true; } else if (pASMsg->GetAudFlag() == plAudioSysMsg::kSetVol) { return true; } else if( pASMsg->GetAudFlag() == plAudioSysMsg::kDestroy ) { if( fWaitingForShutdown ) Shutdown(); return true; } else if( pASMsg->GetAudFlag() == plAudioSysMsg::kUnmuteAll ) { if( !pASMsg->HasBCastFlag( plMessage::kBCastByExactType ) ) plgAudioSys::SetMuted( false ); return true; } } if(plRenderMsg* pRMsg = plRenderMsg::ConvertNoRef( msg )) { //if( fListener ) { plProfile_BeginLap(AudioUpdate, this->GetKey()->GetUoid().GetObjectName()); if(hsTimer::GetMilliSeconds() - fLastUpdateTimeMs > UPDATE_TIME_MS) { IUpdateSoftSounds( fCurrListenerPos ); if( fUsingEAX ) { plProfile_BeginTiming(SoundEAXUpdate); plEAXListener::GetInstance().ProcessMods( fEAXRegions ); plProfile_EndTiming(SoundEAXUpdate); } //fCommittedListenerPos = fCurrListenerPos; } plProfile_EndLap(AudioUpdate, this->GetKey()->GetUoid().GetObjectName()); } return true; } if(plGenRefMsg *refMsg = plGenRefMsg::ConvertNoRef( msg )) { if( refMsg->GetContext() & ( plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace ) ) { fEAXRegions.Append( plEAXListenerMod::ConvertNoRef( refMsg->GetRef() ) ); plEAXListener::GetInstance().ClearProcessCache(); } else if( refMsg->GetContext() & ( plRefMsg::kOnRemove | plRefMsg::kOnDestroy ) ) { int idx = fEAXRegions.Find( plEAXListenerMod::ConvertNoRef( refMsg->GetRef() ) ); if( idx != fEAXRegions.kMissingIndex ) fEAXRegions.Remove( idx ); plEAXListener::GetInstance().ClearProcessCache(); } return true; } if(plAgeLoadedMsg *pALMsg = plAgeLoadedMsg::ConvertNoRef(msg)) { if(!pALMsg->fLoaded) { fLastPos = fCurrListenerPos; fListenerInit = false; } else { fListenerInit = true; } } return hsKeyedObject::MsgReceive(msg); } // plgAudioSystem ////////////////////////////////////////////////////////////////////// plAudioSystem* plgAudioSys::fSys = nil; hsBool plgAudioSys::fInit = false; hsBool plgAudioSys::fActive = false; hsBool plgAudioSys::fUseHardware = false; hsBool plgAudioSys::fMuted = true; bool plgAudioSys::fEnableSubtitles = true; hsBool plgAudioSys::fDelayedActivate = false; hsBool plgAudioSys::fEnableEAX = false; hsWindowHndl plgAudioSys::fWnd = nil; hsScalar plgAudioSys::fChannelVolumes[ kNumChannels ] = { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f }; hsScalar plgAudioSys::f2D3DBias = 0.75f; UInt32 plgAudioSys::fDebugFlags = 0; hsScalar plgAudioSys::fStreamingBufferSize = 2.f; hsScalar plgAudioSys::fStreamFromRAMCutoff = 10.f; UInt8 plgAudioSys::fPriorityCutoff = 9; // We cut off sounds above this priority hsBool plgAudioSys::fEnableExtendedLogs = false; hsScalar plgAudioSys::fGlobalFadeVolume = 1.f; hsBool plgAudioSys::fLogStreamingUpdates = false; std::string plgAudioSys::fDeviceName; hsBool plgAudioSys::fRestarting = false; hsBool plgAudioSys::fMutedStateChange = false; void plgAudioSys::Init(hsWindowHndl hWnd) { fSys = TRACKED_NEW plAudioSystem; fSys->RegisterAs( kAudioSystem_KEY ); plgDispatch::Dispatch()->RegisterForExactType( plAudioSysMsg::Index(), fSys->GetKey() ); plgDispatch::Dispatch()->RegisterForExactType( plRenderMsg::Index(), fSys->GetKey() ); if(hsPhysicalMemory() <= 380) { plStatusLog::AddLineS("audio.log", "StreamFromRam Disabled"); fStreamFromRAMCutoff = 4; } fWnd = hWnd; if(fMuted) SetGlobalFadeVolume(0.0f); if( fDelayedActivate ) Activate( true ); } void plgAudioSys::SetActive(hsBool b) { fActive = b; } void plgAudioSys::SetMuted( hsBool b ) { fMuted = b; fMutedStateChange = true; if(fMuted) SetGlobalFadeVolume(0.0f); else SetGlobalFadeVolume(1.0); } void plgAudioSys::SetEnableSubtitles(bool b) { fEnableSubtitles = b; } void plgAudioSys::SetUseHardware(hsBool b) { fUseHardware = b; if( fActive ) Restart(); } void plgAudioSys::EnableEAX( hsBool b ) { fEnableEAX = b; if( fActive ) Restart(); } void plgAudioSys::SetAudioMode(AudioMode mode) { if(mode == kDisabled) { Activate(false); return; } else if(mode == kSoftware) { fActive = true; fUseHardware = false; fEnableEAX = false; } else if(mode == kHardware) { fActive = true; fUseHardware = true; fEnableEAX = false; } else if(mode == kHardwarePlusEAX) { fActive = true; fUseHardware = true; fEnableEAX = true; } Restart(); } int plgAudioSys::GetAudioMode() { if (fActive) { if (fUseHardware) { if (fEnableEAX) { return kHardwarePlusEAX; } else { return kHardware; } } else { return kSoftware; } } else { return kDisabled; } } void plgAudioSys::Restart( void ) { if( fSys ) { fSys->fRestartOnDestruct = true; Activate( false ); fRestarting = true; } } void plgAudioSys::Shutdown() { Activate( false ); if( fSys ) { fSys->UnRegisterAs( kAudioSystem_KEY ); } } void plgAudioSys::Activate(hsBool b) { if( fSys == nil ) { fDelayedActivate = true; return; } if (b == fInit) return; if (!fActive) return; if( b ) { plStatusLog::AddLineS( "audio.log", plStatusLog::kBlue, "ASYS: -- Attempting audio system init --" ); if( !fSys->Init( fWnd ) ) { // Cannot init audio system. Don't activate return; } fInit = true; fSys->SetActive( true ); if( !IsMuted() ) { SetMuted( true ); plAudioSysMsg *msg = TRACKED_NEW plAudioSysMsg( plAudioSysMsg::kUnmuteAll ); msg->SetTimeStamp( hsTimer::GetSysSeconds() ); msg->AddReceiver( fSys->GetKey() ); msg->SetBCastFlag( plMessage::kBCastByExactType, false ); msg->Send(); } return; } fSys->SetActive( false ); plStatusLog::AddLineS( "audio.log", plStatusLog::kBlue, "ASYS: -- Sending deactivate/destroy messages --" ); plgDispatch::MsgSend( TRACKED_NEW plAudioSysMsg( plAudioSysMsg::kDeActivate ) ); // Send ourselves a shutdown message, so that the deactivates get processed first fSys->fWaitingForShutdown = true; plAudioSysMsg *msg = TRACKED_NEW plAudioSysMsg( plAudioSysMsg::kDestroy ); msg->SetBCastFlag( plMessage::kBCastByExactType, false ); msg->Send( fSys->GetKey() ); // fSys->Shutdown(); fInit = false; } void plgAudioSys::SetChannelVolume( ASChannel chan, hsScalar vol ) { fChannelVolumes[ chan ] = vol; } void plgAudioSys::SetGlobalFadeVolume( hsScalar vol ) { if(!fMuted) fGlobalFadeVolume = vol; else fGlobalFadeVolume = 0; } hsScalar plgAudioSys::GetChannelVolume( ASChannel chan ) { return fChannelVolumes[ chan ]; } void plgAudioSys::NextDebugSound( void ) { fSys->NextDebugSound(); } void plgAudioSys::Set2D3DBias( hsScalar bias ) { f2D3DBias = bias; } hsScalar plgAudioSys::Get2D3Dbias() { return f2D3DBias; } void plgAudioSys::SetDeviceName(const char *device, hsBool restart /* = false */) { fDeviceName = device; if(restart) Restart(); } int plgAudioSys::GetNumAudioDevices() { if(fSys) return fSys->GetNumAudioDevices(); return 0; } const char *plgAudioSys::GetAudioDeviceName(int index) { if(fSys) { return fSys->GetAudioDeviceName(index); } return nil; } ALCdevice *plgAudioSys::GetCaptureDevice() { if(fSys) { return fSys->fCaptureDevice; } return nil; } hsBool plgAudioSys::SupportsEAX(const char *deviceName) { if(fSys) { return fSys->SupportsEAX(deviceName); } return nil; } void plgAudioSys::RegisterSoftSound( const plKey soundKey ) { if(fSys) { fSys->RegisterSoftSound(soundKey); } } void plgAudioSys::UnregisterSoftSound( const plKey soundKey ) { if(fSys) { fSys->UnregisterSoftSound(soundKey); } } void plgAudioSys::SetListenerPos(const hsPoint3 pos) { if(fSys) { fSys->SetListenerPos(pos); } } void plgAudioSys::SetListenerVelocity(const hsVector3 vel) { if(fSys) { fSys->SetListenerVelocity(vel); } } void plgAudioSys::SetListenerOrientation(const hsVector3 view, const hsVector3 up) { if(fSys) { fSys->SetListenerOrientation(view, up); } }