/*==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 . 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 #include #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; if(alIsExtensionPresent((ALchar *)"EAX4.0") || alIsExtensionPresent((ALchar *) "EAX4.0Emulated")) { supportsEAX = true; } 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; 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::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); } }