/*==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 < http : //www.gnu.org/licenses/>.
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 3 ds 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 , 3 ds 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>
# ifdef EAX_SDK_AVAILABLE
# include <eax.h>
# 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 < DeviceDescriptor > : : 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 ! = 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_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 ( hsBool 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...<sigh>
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 ;
}
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 ;
}
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 ( ( 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 ;
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 ;
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
hsBool plgAudioSys : : fEnableExtendedLogs = false ;
float 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 = new plAudioSystem ;
fSys - > RegisterAs ( kAudioSystem_KEY ) ;
plgDispatch : : Dispatch ( ) - > RegisterForExactType ( plAudioSysMsg : : Index ( ) , fSys - > GetKey ( ) ) ;
plgDispatch : : Dispatch ( ) - > RegisterForExactType ( plRenderMsg : : Index ( ) , fSys - > GetKey ( ) ) ;
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 = 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 , 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 ) ;
}
}