/*==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
#include
#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;
float 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, float 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, float 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_t plAudioSystem::fMaxNumSounds = 16;
int32_t 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)
{
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))
{
bool 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 ////////////////////////////////////////////////////////////////////
bool plAudioSystem::Init()
{
plgAudioSys::fRestarting = false;
static bool 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();
bool 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();
}
bool 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_t 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( bool b )
{
fActive = b;
if( fActive )
{
// Clear to send activate message (if listener not inited yet, delay until then)
plgDispatch::MsgSend( 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 = 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;
float distSquared, rank;
plSoftSoundNode *sortedList = nil;
int32_t 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_t 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;
default: 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().c_str()
);
}
else
{
fDebugActiveSoundDisplay->AddLineF(
color,
"%d %1.2f %1.2f %s",
sound->GetPriority(),
sortedList->fRank,
sound->GetVolume() ? sound->GetVolumeRank() / sound->GetVolume() : 0,
sound->GetKeyName().c_str()
);
}
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().c_str()
);
}
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;
}
bool 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( (float)((currTime-fStartFade) / fFadeLength) );
}
}
return true;
}
if (plAudioSysMsg* pASMsg = plAudioSysMsg::ConvertNoRef( msg ))
{
if (pASMsg->GetAudFlag() == plAudioSysMsg::kPing && fListenerInit)
{
plAudioSysMsg* pMsg = 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().c_str());
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().c_str());
}
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;
bool plgAudioSys::fInit = false;
bool plgAudioSys::fActive = false;
bool plgAudioSys::fUseHardware = false;
bool plgAudioSys::fMuted = true;
bool plgAudioSys::fDelayedActivate = false;
bool plgAudioSys::fEnableEAX = false;
float plgAudioSys::fChannelVolumes[ kNumChannels ] = { 1.f, 1.f, 1.f, 1.f, 1.f, 1.f };
float plgAudioSys::f2D3DBias = 0.75f;
uint32_t plgAudioSys::fDebugFlags = 0;
float plgAudioSys::fStreamingBufferSize = 2.f;
float plgAudioSys::fStreamFromRAMCutoff = 10.f;
uint8_t plgAudioSys::fPriorityCutoff = 9; // We cut off sounds above this priority
bool plgAudioSys::fEnableExtendedLogs = false;
float plgAudioSys::fGlobalFadeVolume = 1.f;
bool plgAudioSys::fLogStreamingUpdates = false;
std::string plgAudioSys::fDeviceName;
bool plgAudioSys::fRestarting = false;
bool plgAudioSys::fMutedStateChange = false;
void plgAudioSys::Init()
{
fSys = new plAudioSystem;
fSys->RegisterAs( kAudioSystem_KEY );
plgDispatch::Dispatch()->RegisterForExactType( plAudioSysMsg::Index(), fSys->GetKey() );
plgDispatch::Dispatch()->RegisterForExactType( plRenderMsg::Index(), fSys->GetKey() );
if(fMuted)
SetGlobalFadeVolume(0.0f);
if( fDelayedActivate )
Activate( true );
}
void plgAudioSys::SetActive(bool b)
{
fActive = b;
}
void plgAudioSys::SetMuted( bool b )
{
fMuted = b;
fMutedStateChange = true;
if(fMuted)
SetGlobalFadeVolume(0.0f);
else
SetGlobalFadeVolume(1.0);
}
void plgAudioSys::SetUseHardware(bool b)
{
fUseHardware = b;
if( fActive )
Restart();
}
void plgAudioSys::EnableEAX( bool 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(bool 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() )
{
// Cannot init audio system. Don't activate
return;
}
fInit = true;
fSys->SetActive( true );
if( !IsMuted() )
{
SetMuted( true );
plAudioSysMsg *msg = 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( new plAudioSysMsg( plAudioSysMsg::kDeActivate ) );
// Send ourselves a shutdown message, so that the deactivates get processed first
fSys->fWaitingForShutdown = true;
plAudioSysMsg *msg = new plAudioSysMsg( plAudioSysMsg::kDestroy );
msg->SetBCastFlag( plMessage::kBCastByExactType, false );
msg->Send( fSys->GetKey() );
// fSys->Shutdown();
fInit = false;
}
void plgAudioSys::SetChannelVolume( ASChannel chan, float vol )
{
fChannelVolumes[ chan ] = vol;
}
void plgAudioSys::SetGlobalFadeVolume( float vol )
{
if(!fMuted)
fGlobalFadeVolume = vol;
else
fGlobalFadeVolume = 0;
}
float plgAudioSys::GetChannelVolume( ASChannel chan )
{
return fChannelVolumes[ chan ];
}
void plgAudioSys::NextDebugSound( void )
{
fSys->NextDebugSound();
}
void plgAudioSys::Set2D3DBias( float bias )
{
f2D3DBias = bias;
}
float plgAudioSys::Get2D3Dbias()
{
return f2D3DBias;
}
void plgAudioSys::SetDeviceName(const char *device, bool 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;
}
bool plgAudioSys::SupportsEAX(const char *deviceName)
{
if(fSys)
{
return fSys->SupportsEAX(deviceName);
}
return false;
}
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);
}
}