You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1545 lines
41 KiB
1545 lines
41 KiB
4 years ago
|
/*==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 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 "hsTypes.h"
|
||
|
#include "hsUtils.h"
|
||
|
#include "hsResMgr.h"
|
||
|
#include "hsTimer.h"
|
||
|
#include "hsGeometry3.h"
|
||
|
#include "hsColorRGBA.h"
|
||
|
#include "plProfile.h"
|
||
|
#include "plgDispatch.h"
|
||
|
|
||
|
#include "plAudioSystem.h"
|
||
|
#include "plSound.h"
|
||
|
#include "plWin32Sound.h"
|
||
|
|
||
|
#include "../plAudioCore/plSoundBuffer.h"
|
||
|
#include "../plDrawable/plDrawableGenerator.h"
|
||
|
#include "../pnMessage/plRefMsg.h"
|
||
|
#include "../pnMessage/plTimeMsg.h"
|
||
|
#include "../pnMessage/plAudioSysMsg.h"
|
||
|
#include "../pnMessage/plSoundMsg.h"
|
||
|
#include "../plMessage/plListenerMsg.h"
|
||
|
#include "../plIntersect/plSoftVolume.h"
|
||
|
#include "../plStatusLog/plStatusLog.h"
|
||
|
#include "../plPipeline/plPlates.h"
|
||
|
#include "../pnKeyedObject/plKey.h"
|
||
|
#include "../pnNetCommon/plSDLTypes.h"
|
||
|
#include "../plAvatar/plScalarChannel.h"
|
||
|
#include "../plAvatar/plAGModifier.h"
|
||
|
#include "../pnSceneObject/plSceneObject.h"
|
||
|
#include "../pnSceneObject/plAudioInterface.h"
|
||
|
|
||
|
plProfile_CreateCounterNoReset( "Loaded", "Sound", SoundNumLoaded );
|
||
|
plProfile_CreateCounterNoReset( "Waiting to Die", "Sound", WaitingToDie );
|
||
|
plProfile_CreateAsynchTimer( "Sound Load Time", "Sound", SoundLoadTime );
|
||
|
|
||
|
plGraphPlate *plSound::fDebugPlate = nil;
|
||
|
plSound *plSound::fCurrDebugPlateSound = nil;
|
||
|
hsBool plSound::fLoadOnDemandFlag = true;
|
||
|
hsBool plSound::fLoadFromDiskOnDemand = true;
|
||
|
unsigned plSound::fIncidentalsPlaying = 0;
|
||
|
|
||
|
plSound::plSound() :
|
||
|
fPlaying(false),
|
||
|
fActive(false),
|
||
|
fTime(0),
|
||
|
fMaxFalloff(0),
|
||
|
fMinFalloff(0),
|
||
|
fCurrVolume(0.f),
|
||
|
fOuterVol(0),
|
||
|
fOuterCone(360),
|
||
|
fInnerCone(360),
|
||
|
fLength(0.0f),
|
||
|
fDesiredVol(0.f),
|
||
|
fFading(false),
|
||
|
fRegisteredForTime(false),
|
||
|
fMuted(true),
|
||
|
fFadedVolume(0.f),
|
||
|
fSoftRegion(nil),
|
||
|
fSoftOcclusionRegion(nil),
|
||
|
fSoftVolume(0.f),
|
||
|
fCurrFadeParams(nil),
|
||
|
fRegistered(false),
|
||
|
fDistAttenuation(0.f),
|
||
|
fProperties(0),
|
||
|
fNotHighEnoughPriority(false),
|
||
|
fVirtualStartTime(0),
|
||
|
fOwningSceneObject(nil),
|
||
|
fPriority(0),
|
||
|
fType(plSound::kSoundFX),
|
||
|
fQueued(false),
|
||
|
fLoading(false),
|
||
|
fSynchedStartTimeSec(0),
|
||
|
fMaxVolume(0),
|
||
|
fFreeData(false)
|
||
|
{
|
||
|
plProfile_Inc( SoundNumLoaded );
|
||
|
f3DPosition.Set( 0.f, 0.f, 0.f );
|
||
|
f3DVelocity.Set( 0.f, 0.f, 0.f );
|
||
|
fDataBuffer = nil;
|
||
|
fDataBufferKey = nil;
|
||
|
fPlayOnReactivate = false;
|
||
|
fDataBufferLoaded = false;
|
||
|
}
|
||
|
|
||
|
plSound::~plSound()
|
||
|
{
|
||
|
IStopFade( true );
|
||
|
plProfile_Dec( SoundNumLoaded );
|
||
|
}
|
||
|
|
||
|
void plSound::IPrintDbgMessage( const char *msg, hsBool isError )
|
||
|
{
|
||
|
static plStatusLog *ourLog = nil;
|
||
|
|
||
|
// Print to our log file (create it if necessary)
|
||
|
if( ourLog == nil )
|
||
|
{
|
||
|
// ourLog = plStatusLogMgr::GetInstance().CreateStatusLog( 15,
|
||
|
// "audio.log", plStatusLog::kFilledBackground | plStatusLog::kDeleteForMe | plStatusLog::kAlignToTop );
|
||
|
}
|
||
|
|
||
|
if( isError )
|
||
|
// ourLog->AddLineF( plStatusLog::kRed, "ERROR: %s (%s)", msg, GetKey() ? GetKeyName() : "unkeyed" );
|
||
|
ourLog->AddLineS( "audio.log", plStatusLog::kRed, "ERROR: %s (%s)", msg, GetKey() ? GetKeyName() : "unkeyed" );
|
||
|
else
|
||
|
// ourLog->AddLineF( "%s (%s)", msg, GetKey() ? GetKeyName() : "unkeyed" );
|
||
|
ourLog->AddLineS( "audio.log", "%s (%s)", msg, GetKey() ? GetKeyName() : "unkeyed" );
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////
|
||
|
// Called to send more values to the debug plate, assuming this is the right
|
||
|
// sound. Should be called every time any of the values change, which means
|
||
|
// the best place is inside ISetActualVolume(). Since that's a pure virtual,
|
||
|
// it makes the placement of the call a bit annoying, but oh well.
|
||
|
void plSound::IUpdateDebugPlate( void )
|
||
|
{
|
||
|
if( this == fCurrDebugPlateSound )
|
||
|
{
|
||
|
if( fDebugPlate == nil )
|
||
|
{
|
||
|
plPlateManager::Instance().CreateGraphPlate( &fDebugPlate );
|
||
|
fDebugPlate->SetSize( 0.50, 0.25 );
|
||
|
fDebugPlate->SetPosition( -0.5, 0 );
|
||
|
fDebugPlate->SetDataRange( 0, 100, 100 );
|
||
|
fDebugPlate->SetColors( 0x80202000 );
|
||
|
fDebugPlate->SetTitle( (char *)GetKeyName() ); // Bleah
|
||
|
fDebugPlate->SetLabelText( "Desired", "Curr", "Soft", "Dist" );
|
||
|
}
|
||
|
|
||
|
fDebugPlate->SetVisible( true );
|
||
|
fDebugPlate->AddData( (Int32)( fDesiredVol * 100.f ),
|
||
|
(Int32)( fCurrVolume * 100.f ),
|
||
|
(Int32)( fSoftVolume * 100.f ),
|
||
|
(Int32)( fDistAttenuation * 100.f ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::SetCurrDebugPlate( const plKey soundKey )
|
||
|
{
|
||
|
if( soundKey == nil )
|
||
|
{
|
||
|
fCurrDebugPlateSound = nil;
|
||
|
if( fDebugPlate != nil )
|
||
|
fDebugPlate->SetVisible( false );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fCurrDebugPlateSound = plSound::ConvertNoRef( soundKey->GetObjectPtr() );
|
||
|
if( fDebugPlate != nil )
|
||
|
{
|
||
|
fDebugPlate->ClearData();
|
||
|
fDebugPlate->SetVisible( true );
|
||
|
fDebugPlate->SetTitle( (char *)fCurrDebugPlateSound->GetKeyName() ); // Bleah
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////
|
||
|
// We don't keep track of the current time we should be at, but rather the
|
||
|
// time we started at. Since we're calling SetTime(), we should adjust the
|
||
|
// start time to be accurate for the time we want to be at now. Note: we
|
||
|
// don't actually move the buffer position unless it's loaded, since we
|
||
|
// don't want to force a load on a buffer just from a SetTime() call.
|
||
|
void plSound::SetTime( double t )
|
||
|
{
|
||
|
fVirtualStartTime = hsTimer::GetSysSeconds() - t;
|
||
|
|
||
|
if( IActuallyLoaded() )
|
||
|
ISetActualTime( t );
|
||
|
}
|
||
|
|
||
|
// Support for Fast forward responder
|
||
|
void plSound::FastForwardPlay()
|
||
|
{
|
||
|
if(fProperties & kPropLooping)
|
||
|
{
|
||
|
Play();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::FastForwardToggle()
|
||
|
{
|
||
|
if(fPlaying == true)
|
||
|
{
|
||
|
Stop();
|
||
|
return;
|
||
|
}
|
||
|
FastForwardPlay();
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// Our basic play function. Marks the sound as playing, and if we're actually
|
||
|
// allowed to play, will actually start the sound playing as well.
|
||
|
void plSound::Play()
|
||
|
{
|
||
|
if(fLoading) // if we are loading there is no reason to do this. Play will be called, by Update(), once the data is loaded and this floag is set to false
|
||
|
return;
|
||
|
|
||
|
if( !fActive )
|
||
|
{
|
||
|
// We're not active, so we can't play, but mark to make sure we'll play once we do get activated
|
||
|
fPlayOnReactivate = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fPlaying = true;
|
||
|
|
||
|
if(IPreLoadBuffer(true) == plSoundBuffer::kPending)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fVirtualStartTime = hsTimer::GetSysSeconds(); // When we "started", even if we really don't start
|
||
|
|
||
|
// if the sound system is not active do a fake play so callbacks get sent
|
||
|
if(!plgAudioSys::Active())
|
||
|
{
|
||
|
// Do the (fake) actual play
|
||
|
IActuallyPlay();
|
||
|
}
|
||
|
if( IWillBeAbleToPlay() )
|
||
|
{
|
||
|
IRefreshParams();
|
||
|
|
||
|
if( fFadeInParams.fLengthInSecs > 0 )
|
||
|
{
|
||
|
IStartFade( &fFadeInParams);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// we're NOT fading!!!!
|
||
|
if( fFading )
|
||
|
IStopFade();
|
||
|
SetVolume( fDesiredVol );
|
||
|
}
|
||
|
|
||
|
// Do the actual play
|
||
|
IActuallyPlay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::SynchedPlay(unsigned bytes )
|
||
|
{
|
||
|
if( fFading )
|
||
|
IStopFade();
|
||
|
if(fLoading) // the sound is loading, it will be played when loading is finished
|
||
|
return;
|
||
|
|
||
|
if( !fActive )
|
||
|
{
|
||
|
// We're not active, so we can't play, but mark to make sure we'll play once we do get activated
|
||
|
fPlayOnReactivate = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Mark as playing, since we'll be calling ITryPlay() directly
|
||
|
fPlaying = true;
|
||
|
fPlayOnReactivate = false;
|
||
|
|
||
|
if( IWillBeAbleToPlay() )
|
||
|
{
|
||
|
SetStartPos(bytes);
|
||
|
Play();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////
|
||
|
// Used for synching play state. The state only knows that we're playing
|
||
|
// and what time we started at, so we use that to compute what time we should
|
||
|
// be at and update. Note that we also set our virtual start time to what
|
||
|
// we're given, NOT the current time, 'cause, well, duh, that should be our
|
||
|
// start time!
|
||
|
// So think of it as "Play() but act as if you started at *this* time"...
|
||
|
void plSound::SynchedPlay( hsScalar virtualStartTime )
|
||
|
{
|
||
|
if( fFading )
|
||
|
IStopFade();
|
||
|
|
||
|
ISynchedPlay( virtualStartTime );
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
// Only want to do the fade hack when somebody outside synch()s us.
|
||
|
void plSound::ISynchedPlay( double virtualStartTime )
|
||
|
{
|
||
|
if(fLoading) // the sound is loading, it will be played when loading is finished
|
||
|
return;
|
||
|
|
||
|
// Store our start time
|
||
|
fVirtualStartTime = virtualStartTime;
|
||
|
|
||
|
if( !fActive )
|
||
|
{
|
||
|
// We're not active, so we can't play, but mark to make sure we'll play once we do get activated
|
||
|
fPlayOnReactivate = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Mark as playing, since we'll be calling ITryPlay() directly
|
||
|
fPlaying = true;
|
||
|
fPlayOnReactivate = false;
|
||
|
|
||
|
// Do da synch, which will start us playing
|
||
|
if( IWillBeAbleToPlay() )
|
||
|
{
|
||
|
ISynchToStartTime();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////
|
||
|
// Takes the virtual start time and sets us to the real time we should be at,
|
||
|
// then starts us playing via ITryPlay().
|
||
|
void plSound::ISynchToStartTime( void )
|
||
|
{
|
||
|
if( !plgAudioSys::Active() )
|
||
|
return;
|
||
|
|
||
|
// We don't want to do this until we're actually loaded, since that's when we'll know our
|
||
|
// REAL length (thanks to the inaccuracies of WMA compression)
|
||
|
// LoadSound( IsPropertySet( kPropIs3DSound ) );
|
||
|
// Haha, the GetLength() call will do this for us
|
||
|
|
||
|
// Calculate what time we should be at
|
||
|
double deltaTime = hsTimer::GetSysSeconds() - fVirtualStartTime;
|
||
|
double length = GetLength();
|
||
|
|
||
|
if( deltaTime > length || deltaTime < 0 )
|
||
|
{
|
||
|
// Hmm, our time went past the length of sound, so handle that
|
||
|
if( IsPropertySet( kPropLooping ) )
|
||
|
{
|
||
|
if( length <= 0 )
|
||
|
deltaTime = 0; // Error, attempt to recover
|
||
|
else if( deltaTime < 0 )
|
||
|
{
|
||
|
int numWholeParts = (int)( -deltaTime / length );
|
||
|
deltaTime += length * ( numWholeParts + 1 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int numWholeParts = (int)( deltaTime / length );
|
||
|
deltaTime -= length * (double)numWholeParts;
|
||
|
}
|
||
|
|
||
|
//ISetActualTime( deltaTime );
|
||
|
Play();
|
||
|
}
|
||
|
else
|
||
|
// We already played and stopped virtually, so really mark us as stopped
|
||
|
Stop();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Easy 'nuf...
|
||
|
//ISetActualTime( deltaTime );
|
||
|
Play();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::SetPosition(const hsPoint3 pos)
|
||
|
{
|
||
|
f3DPosition = pos;
|
||
|
}
|
||
|
|
||
|
void plSound::SetVelocity(const hsVector3 vel)
|
||
|
{
|
||
|
f3DVelocity = vel;
|
||
|
}
|
||
|
|
||
|
hsPoint3 plSound::GetPosition( void ) const
|
||
|
{
|
||
|
return f3DPosition;
|
||
|
}
|
||
|
|
||
|
hsVector3 plSound::GetVelocity( void ) const
|
||
|
{
|
||
|
return f3DVelocity;
|
||
|
}
|
||
|
|
||
|
void plSound::SetMin(const int m)
|
||
|
{
|
||
|
fMinFalloff = m;
|
||
|
}
|
||
|
|
||
|
void plSound::SetMax(const int m)
|
||
|
{
|
||
|
fMaxFalloff = m;
|
||
|
}
|
||
|
|
||
|
void plSound::SetOuterVolume(const int v)
|
||
|
{
|
||
|
fOuterVol = v;
|
||
|
}
|
||
|
|
||
|
void plSound::SetConeOrientation( hsScalar x, hsScalar y, hsScalar z )
|
||
|
{
|
||
|
fConeOrientation.Set( x, y, z );
|
||
|
}
|
||
|
|
||
|
void plSound::SetConeAngles( int inner, int outer )
|
||
|
{
|
||
|
fOuterCone = outer;
|
||
|
fInnerCone = inner;
|
||
|
}
|
||
|
|
||
|
int plSound::GetMin() const
|
||
|
{
|
||
|
return fMinFalloff;
|
||
|
}
|
||
|
|
||
|
int plSound::GetMax() const
|
||
|
{
|
||
|
return fMaxFalloff;
|
||
|
}
|
||
|
|
||
|
void plSound::SetVolume(const float v)
|
||
|
{
|
||
|
fDesiredVol = v;
|
||
|
|
||
|
if( !fMuted && !fFading )
|
||
|
fCurrVolume = fDesiredVol;
|
||
|
|
||
|
RefreshVolume();
|
||
|
}
|
||
|
|
||
|
void plSound::RefreshVolume( void )
|
||
|
{
|
||
|
this->ISetActualVolume( fCurrVolume );
|
||
|
}
|
||
|
|
||
|
void plSound::SetMuted( hsBool muted )
|
||
|
{
|
||
|
if( muted != fMuted )
|
||
|
{
|
||
|
fMuted = muted;
|
||
|
if( fMuted )
|
||
|
fCurrVolume = 0.f;
|
||
|
else if( !fFading )
|
||
|
fCurrVolume = fDesiredVol;
|
||
|
|
||
|
RefreshVolume();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::IRefreshParams( void )
|
||
|
{
|
||
|
SetMax( fMaxFalloff );
|
||
|
SetMin( fMinFalloff );
|
||
|
SetOuterVolume( fOuterVol );
|
||
|
SetConeAngles( fInnerCone, fOuterCone );
|
||
|
SetConeOrientation( fConeOrientation.fX, fConeOrientation.fY, fConeOrientation.fZ);
|
||
|
SetPosition( f3DPosition );
|
||
|
SetVelocity( f3DVelocity );
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// The public interface to stopping, which also synchs the state with the
|
||
|
// server.
|
||
|
void plSound::Stop( void )
|
||
|
{
|
||
|
fPlaying = false;
|
||
|
|
||
|
// if the audio data is loading while stop is called we need to make sure the sounds doesn't play, and the data is unloaded.
|
||
|
fPlayOnReactivate = false;
|
||
|
fFreeData = true;
|
||
|
|
||
|
// Do we have an ending fade?
|
||
|
if( fFadeOutParams.fLengthInSecs > 0 && !plgAudioSys::IsRestarting() )
|
||
|
{
|
||
|
IStartFade( &fFadeOutParams );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if( fFading )
|
||
|
IStopFade();
|
||
|
|
||
|
fCurrVolume = 0.f;
|
||
|
ISetActualVolume( fCurrVolume );
|
||
|
IActuallyStop();
|
||
|
}
|
||
|
if(fPlayWhenLoaded)
|
||
|
{
|
||
|
fPlayWhenLoaded = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::IActuallyStop( void )
|
||
|
{
|
||
|
if( fLoadOnDemandFlag && !IsPropertySet( kPropDisableLOD ) && !IsPropertySet( kPropLoadOnlyOnCall ) )
|
||
|
{
|
||
|
// If we're loading on demand, we want to unload on stop
|
||
|
IFreeBuffers();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::Update()
|
||
|
{
|
||
|
if(fLoading)
|
||
|
{
|
||
|
plSoundBuffer::ELoadReturnVal retVal = IPreLoadBuffer(fPlayWhenLoaded);
|
||
|
|
||
|
if(retVal == plSoundBuffer::kError)
|
||
|
{
|
||
|
fLoading = false;
|
||
|
fPlayWhenLoaded = false;
|
||
|
}
|
||
|
if(retVal == plSoundBuffer::kSuccess)
|
||
|
{
|
||
|
fLoading = false;
|
||
|
if(fPlayWhenLoaded)
|
||
|
Play();
|
||
|
fPlayWhenLoaded = false;
|
||
|
|
||
|
// ensure the sound data is released if the sound object was stopped while the audio data was being loaded.
|
||
|
if(fFreeData)
|
||
|
{
|
||
|
fFreeData = false;
|
||
|
FreeSoundData();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hsScalar plSound::IGetChannelVolume( void ) const
|
||
|
{
|
||
|
hsScalar channelVol = plgAudioSys::GetChannelVolume( (plgAudioSys::ASChannel)fType );
|
||
|
|
||
|
// if not using hardware acceleration then apply 2D/3D bias to non 3D sounds
|
||
|
if( !plgAudioSys::Hardware() && !IsPropertySet( kPropIs3DSound ) )
|
||
|
channelVol *= plgAudioSys::Get2D3Dbias();
|
||
|
|
||
|
if( IsPropertySet( kPropDontFade ) )
|
||
|
return channelVol;
|
||
|
|
||
|
return channelVol * plgAudioSys::GetGlobalFadeVolume();
|
||
|
}
|
||
|
|
||
|
void plSound::IStartFade( plFadeParams *params, hsScalar offsetIntoFade )
|
||
|
{
|
||
|
fFading = true;
|
||
|
|
||
|
if( params == &fFadeOutParams )
|
||
|
{
|
||
|
fFadeOutParams.fVolStart = fCurrVolume;
|
||
|
fFadeOutParams.fVolEnd = fFadedVolume;
|
||
|
fCurrFadeParams = &fFadeOutParams;
|
||
|
}
|
||
|
else if( params == &fFadeInParams )
|
||
|
{
|
||
|
fFadeInParams.fVolStart = fCurrVolume; // Hopefully, we got to fFadedVolume, but maybe not
|
||
|
fFadeInParams.fVolEnd = fDesiredVol;
|
||
|
fCurrFadeParams = &fFadeInParams;
|
||
|
plStatusLog::AddLineS("audio.log", "Fading in %s", GetKeyName());
|
||
|
}
|
||
|
else
|
||
|
fCurrFadeParams = params;
|
||
|
|
||
|
fCurrFadeParams->fCurrTime = offsetIntoFade;
|
||
|
ISetActualVolume( fCurrFadeParams->InterpValue() );
|
||
|
|
||
|
if( !fRegisteredForTime )
|
||
|
{
|
||
|
plgDispatch::Dispatch()->RegisterForExactType( plTimeMsg::Index(), GetKey() );
|
||
|
fRegisteredForTime = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::IStopFade( hsBool shuttingDown, hsBool SetVolEnd)
|
||
|
{
|
||
|
if( fCurrFadeParams != nil )
|
||
|
{
|
||
|
if( fCurrFadeParams == &fCoolSoftVolumeTrickParams )
|
||
|
{
|
||
|
plProfile_Dec( WaitingToDie );
|
||
|
}
|
||
|
|
||
|
// This can cause problems if we've exited a soft region and are doing a soft volume fade.
|
||
|
// If the camera pops back into the region of this particular sound this will cause the soft volume to be zero,
|
||
|
// therefore not allowing the sound to play until the listener moves again(triggering another softsound update).
|
||
|
// So if this function is called from UpdateSoftSounds this will not be performed
|
||
|
if(SetVolEnd)
|
||
|
{
|
||
|
if( fCurrFadeParams->fFadeSoftVol )
|
||
|
fSoftVolume = fCurrFadeParams->fVolEnd;
|
||
|
else
|
||
|
fCurrVolume = fCurrFadeParams->fVolEnd;
|
||
|
}
|
||
|
|
||
|
if( !shuttingDown )
|
||
|
ISetActualVolume( fCurrVolume );
|
||
|
|
||
|
fCurrFadeParams->fCurrTime = -1.f;
|
||
|
}
|
||
|
|
||
|
fFading = false;
|
||
|
fCurrFadeParams = nil;
|
||
|
|
||
|
// Fade done, unregister for time message
|
||
|
if( fRegisteredForTime )
|
||
|
{
|
||
|
plgDispatch::Dispatch()->UnRegisterForExactType( plTimeMsg::Index(), GetKey() );
|
||
|
fRegisteredForTime = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hsBool plSound::MsgReceive( plMessage* pMsg )
|
||
|
{
|
||
|
plTimeMsg *time = plTimeMsg::ConvertNoRef( pMsg );
|
||
|
if( time != nil )
|
||
|
{
|
||
|
/// Time message for handling fade ins/outs
|
||
|
if( fCurrFadeParams == nil )
|
||
|
return true;
|
||
|
|
||
|
fCurrFadeParams->fCurrTime += time->DelSeconds();
|
||
|
|
||
|
if( fCurrFadeParams->fCurrTime >= fCurrFadeParams->fLengthInSecs )
|
||
|
{
|
||
|
if( fCurrFadeParams->fFadeSoftVol )
|
||
|
fSoftVolume = fCurrFadeParams->fVolEnd;
|
||
|
else
|
||
|
fCurrVolume = fCurrFadeParams->fVolEnd;
|
||
|
ISetActualVolume( fCurrVolume );
|
||
|
fCurrFadeParams->fCurrTime = -1.f;
|
||
|
|
||
|
// Fade done, unregister for time message
|
||
|
if( fRegisteredForTime )
|
||
|
{
|
||
|
plgDispatch::Dispatch()->UnRegisterForExactType( plTimeMsg::Index(), GetKey() );
|
||
|
fRegisteredForTime = false;
|
||
|
}
|
||
|
|
||
|
// Note: if we're done, and we were fading out, we need to STOP
|
||
|
if( fCurrFadeParams->fStopWhenDone )
|
||
|
{
|
||
|
// REALLY STOP
|
||
|
IActuallyStop();
|
||
|
}
|
||
|
|
||
|
if( fCurrFadeParams == &fCoolSoftVolumeTrickParams )
|
||
|
{
|
||
|
plProfile_Dec( WaitingToDie );
|
||
|
}
|
||
|
|
||
|
// Done with this one!
|
||
|
fCurrFadeParams = nil;
|
||
|
fFading = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Gotta interp
|
||
|
if( fCurrFadeParams->fFadeSoftVol )
|
||
|
fSoftVolume = fCurrFadeParams->InterpValue();
|
||
|
else
|
||
|
fCurrVolume = fCurrFadeParams->InterpValue();
|
||
|
|
||
|
ISetActualVolume( fCurrVolume );
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
plGenRefMsg* refMsg = plGenRefMsg::ConvertNoRef( pMsg );
|
||
|
if( refMsg )
|
||
|
{
|
||
|
if( refMsg->fType == kRefSoftVolume )
|
||
|
{
|
||
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
||
|
{
|
||
|
ISetSoftRegion( plSoftVolume::ConvertNoRef(refMsg->GetRef()) );
|
||
|
return true;
|
||
|
}
|
||
|
else if( refMsg->GetContext() & (plRefMsg::kOnRemove | plRefMsg::kOnDestroy) )
|
||
|
{
|
||
|
ISetSoftRegion( nil );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else if( refMsg->fType == kRefSoftOcclusionRegion )
|
||
|
{
|
||
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
||
|
{
|
||
|
ISetSoftOcclusionRegion( plSoftVolume::ConvertNoRef( refMsg->GetRef() ) );
|
||
|
return true;
|
||
|
}
|
||
|
else if( refMsg->GetContext() & (plRefMsg::kOnRemove | plRefMsg::kOnDestroy) )
|
||
|
{
|
||
|
ISetSoftOcclusionRegion( nil );
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
else if( refMsg->fType == kRefDataBuffer )
|
||
|
{
|
||
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
||
|
{
|
||
|
fDataBuffer = plSoundBuffer::ConvertNoRef( refMsg->GetRef() );
|
||
|
SetLength( fDataBuffer->GetDataLengthInSecs() );
|
||
|
}
|
||
|
else
|
||
|
fDataBuffer = nil;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
else if( refMsg->fType == kRefParentSceneObject )
|
||
|
{
|
||
|
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
|
||
|
fOwningSceneObject = plSceneObject::ConvertNoRef( refMsg->GetRef() );
|
||
|
else
|
||
|
fOwningSceneObject = nil;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
plSoundMsg *pSoundMsg = plSoundMsg::ConvertNoRef( pMsg );
|
||
|
if( pSoundMsg != nil )
|
||
|
{
|
||
|
if( pSoundMsg->Cmd( plSoundMsg::kAddCallbacks ) )
|
||
|
{
|
||
|
AddCallbacks( pSoundMsg );
|
||
|
return true;
|
||
|
}
|
||
|
else if( pSoundMsg->Cmd( plSoundMsg::kRemoveCallbacks ) )
|
||
|
{
|
||
|
RemoveCallbacks( pSoundMsg );
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
plListenerMsg *listenMsg = plListenerMsg::ConvertNoRef( pMsg );
|
||
|
if( listenMsg != nil )
|
||
|
{
|
||
|
if( fSoftOcclusionRegion != nil )
|
||
|
{
|
||
|
// The EAX settings have 0 as the start value and 1 as the end, and since start
|
||
|
// translates to "inside the soft region", it's reversed of what the region gives us
|
||
|
fEAXSettings.SetOcclusionSoftValue( 1.f - fSoftOcclusionRegion->GetListenerStrength() );
|
||
|
IRefreshEAXSettings();
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return plSynchedObject::MsgReceive( pMsg );
|
||
|
}
|
||
|
|
||
|
void plSound::ForceLoad()
|
||
|
{
|
||
|
if( !IsPropertySet( kPropLoadOnlyOnCall ) )
|
||
|
return;
|
||
|
|
||
|
LoadSound( IsPropertySet( kPropIs3DSound ) );
|
||
|
}
|
||
|
|
||
|
void plSound::ForceUnload( void )
|
||
|
{
|
||
|
if( !IsPropertySet( kPropLoadOnlyOnCall ) )
|
||
|
return;
|
||
|
|
||
|
Stop();
|
||
|
IFreeBuffers();
|
||
|
}
|
||
|
|
||
|
bool plSound::ILoadDataBuffer( void )
|
||
|
{
|
||
|
if(!fDataBufferLoaded)
|
||
|
{
|
||
|
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->RefObject();
|
||
|
if(!buffer)
|
||
|
{
|
||
|
hsAssert(false, "unable to load sound buffer");
|
||
|
plStatusLog::AddLineS("audio.log", "Unable to load sound buffer: %s", GetKeyName());
|
||
|
return false;
|
||
|
}
|
||
|
SetLength( buffer->GetDataLengthInSecs() );
|
||
|
fDataBufferLoaded = true;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void plSound::FreeSoundData()
|
||
|
{
|
||
|
if(!fDataBufferKey) return; // for plugins
|
||
|
plSoundBuffer *buffer = (plSoundBuffer *) fDataBufferKey->ObjectIsLoaded();
|
||
|
if(buffer)
|
||
|
{
|
||
|
buffer->UnLoad();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::IUnloadDataBuffer( void )
|
||
|
{
|
||
|
if(fDataBufferLoaded)
|
||
|
{
|
||
|
fDataBufferLoaded = false;
|
||
|
fDataBufferKey->UnRefObject();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// calling preload will cause the sound to play once loaded
|
||
|
plSoundBuffer::ELoadReturnVal plSound::IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental /* = false */ )
|
||
|
{
|
||
|
if(!ILoadDataBuffer())
|
||
|
{
|
||
|
return plSoundBuffer::kError;
|
||
|
}
|
||
|
|
||
|
plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();
|
||
|
|
||
|
if(buffer && buffer->IsValid() )
|
||
|
{
|
||
|
plProfile_BeginTiming( SoundLoadTime );
|
||
|
plSoundBuffer::ELoadReturnVal retVal = buffer->AsyncLoad(buffer->HasFlag(plSoundBuffer::kStreamCompressed) ? plAudioFileReader::kStreamNative : plAudioFileReader::kStreamWAV);
|
||
|
if(retVal == plSoundBuffer::kPending)
|
||
|
{
|
||
|
fPlayWhenLoaded = playWhenLoaded;
|
||
|
fLoading = true;
|
||
|
}
|
||
|
|
||
|
plProfile_EndTiming( SoundLoadTime );
|
||
|
|
||
|
return retVal;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return plSoundBuffer::kError;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const char *plSound::GetFileName( void ) const
|
||
|
{
|
||
|
if(fDataBufferKey->ObjectIsLoaded())
|
||
|
{
|
||
|
return ((plSoundBuffer *)fDataBufferKey->ObjectIsLoaded())->GetFileName();
|
||
|
}
|
||
|
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// WARNING: to do this right, we have to load the data buffer in, which could
|
||
|
// force an early load of the sound if you're just calling this on a whim. So
|
||
|
// it's best to call this right before you're going to load it anyway (or
|
||
|
// when it's already loaded).
|
||
|
// Note that if we already set the length (like at export time), we never need
|
||
|
// to load the sound, so the optimization at export time is all ready to plug-
|
||
|
// and-play...
|
||
|
double plSound::GetLength( void )
|
||
|
{
|
||
|
if( ( (double)fLength == 0.f ) )
|
||
|
ILoadDataBuffer();
|
||
|
|
||
|
return fLength;
|
||
|
}
|
||
|
|
||
|
void plSound::ISetSoftRegion( plSoftVolume *region )
|
||
|
{
|
||
|
/// We're either registering or unregistering
|
||
|
if( fSoftRegion == nil && region != nil )
|
||
|
RegisterOnAudioSys();
|
||
|
else if( fSoftRegion != nil && region == nil )
|
||
|
UnregisterOnAudioSys();
|
||
|
|
||
|
fSoftRegion = region;
|
||
|
fSoftVolume = 0.f; // Set to zero until we can get our processing call
|
||
|
}
|
||
|
|
||
|
void plSound::ISetSoftOcclusionRegion( plSoftVolume *region )
|
||
|
{
|
||
|
/// We're either registering or unregistering for listener messages
|
||
|
if( fSoftOcclusionRegion == nil && region != nil )
|
||
|
{
|
||
|
plgDispatch::Dispatch()->RegisterForExactType( plListenerMsg::Index(), GetKey() );
|
||
|
}
|
||
|
else if( fSoftOcclusionRegion != nil && region == nil )
|
||
|
{
|
||
|
plgDispatch::Dispatch()->UnRegisterForExactType( plListenerMsg::Index(), GetKey() );
|
||
|
}
|
||
|
|
||
|
fSoftOcclusionRegion = region;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// This function calculates our new softVolume value. Used both to update the
|
||
|
// said value and so the audio system can rank us in importance.
|
||
|
hsScalar plSound::CalcSoftVolume( hsBool enable, hsScalar distToListenerSquared )
|
||
|
{
|
||
|
// Do distance-based attenuation ourselves
|
||
|
#if MCN_HACK_OUR_ATTEN
|
||
|
if( IsPropertySet( kPropIs3DSound ) )
|
||
|
{
|
||
|
hsScalar minDist = (hsScalar)GetMin();
|
||
|
if( distToListenerSquared <= minDist * minDist )
|
||
|
{
|
||
|
fDistAttenuation = 1.f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsScalar maxDist = (hsScalar)GetMax();
|
||
|
if( distToListenerSquared >= maxDist * maxDist )
|
||
|
{
|
||
|
fDistAttenuation = 0.f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsScalar d = (hsScalar)sqrt( distToListenerSquared );
|
||
|
fDistAttenuation = minDist / d;
|
||
|
|
||
|
// The following line ramps it to 0 at the maxDistance. Kinda klunky, but good for now I guess...
|
||
|
// fDistAttenuation *= 1.f - ( 1.f / ( maxDist - minDist ) ) * ( d - minDist );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
fDistAttenuation = 1.f;
|
||
|
#endif
|
||
|
// At the last 50% of our distance attenuation (squared, so it really is farther than that),
|
||
|
// ramp down to 0 so we don't get annoying popping when we stop stuff
|
||
|
if( IsPropertySet( kPropIs3DSound ) )
|
||
|
{
|
||
|
hsScalar maxDistSquared = (hsScalar)( GetMax() * GetMax() );
|
||
|
hsScalar distToStartSquared = (hsScalar)(maxDistSquared * 0.50);
|
||
|
|
||
|
if( maxDistSquared < 0.f ) // Happens when the max distance is REALLY big
|
||
|
{
|
||
|
maxDistSquared = distToListenerSquared + 1.f; // :)
|
||
|
distToStartSquared = maxDistSquared;
|
||
|
}
|
||
|
|
||
|
if( distToListenerSquared > maxDistSquared )
|
||
|
fDistAttenuation = 0.f;
|
||
|
else if( distToListenerSquared > distToStartSquared )
|
||
|
fDistAttenuation = ( maxDistSquared - distToListenerSquared ) / ( maxDistSquared - distToStartSquared );
|
||
|
else
|
||
|
fDistAttenuation = 1.f;
|
||
|
|
||
|
fDistToListenerSquared = distToListenerSquared;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fDistAttenuation = 1.f;
|
||
|
fDistToListenerSquared = 0.f;
|
||
|
}
|
||
|
|
||
|
// Attenuate based on our soft region, if we have one
|
||
|
if( !enable )
|
||
|
// We apparently don't know jack. Let the audioSystem's decision rule
|
||
|
fSoftVolume = 0.f;
|
||
|
else if( fSoftRegion != nil )
|
||
|
fSoftVolume = fSoftRegion->GetListenerStrength();
|
||
|
else
|
||
|
fSoftVolume = 1.f;
|
||
|
|
||
|
return fSoftVolume;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Wee function for the audio system. This basically returns the effective
|
||
|
// current volume of this sound. Useful for doing things like ranking all
|
||
|
// sounds based on volume.
|
||
|
hsScalar plSound::GetVolumeRank( void )
|
||
|
{
|
||
|
if( !IsPlaying() && !this->IActuallyPlaying() )
|
||
|
return 0.f;
|
||
|
|
||
|
hsScalar rank = fSoftVolume * fDesiredVol;
|
||
|
|
||
|
if( IsPropertySet( kPropIs3DSound ) )
|
||
|
{
|
||
|
hsScalar minDistSquared = (hsScalar)( GetMin() * GetMin() );
|
||
|
hsScalar maxDistSquared = (hsScalar) (GetMax() * GetMax());
|
||
|
hsPoint3 listenerPos = plgAudioSys::Sys()->GetCurrListenerPos();
|
||
|
if( fDistToListenerSquared > minDistSquared )
|
||
|
{
|
||
|
hsScalar diff = maxDistSquared - minDistSquared;
|
||
|
rank *= fabs((fDistToListenerSquared - maxDistSquared)) / diff;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return rank;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Tests to see whether, if we try to play this sound now, it'll actually
|
||
|
// be able to play. Takes into account whether the sound is within range
|
||
|
// of the listener and the current soft region value.
|
||
|
hsBool plSound::IWillBeAbleToPlay( void )
|
||
|
{
|
||
|
if( fSoftVolume == 0.f )
|
||
|
return false;
|
||
|
|
||
|
return IsWithinRange( plgAudioSys::Sys()->GetCurrListenerPos(), nil );
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Tests to see whether this sound is within range of the position given,
|
||
|
// ignoring soft volumes.
|
||
|
hsBool plSound::IsWithinRange( const hsPoint3 &listenerPos, hsScalar *distSquared )
|
||
|
{
|
||
|
if( !IsPropertySet( plSound::kPropIs3DSound ) )
|
||
|
{
|
||
|
if( distSquared != nil )
|
||
|
*distSquared = 1.f;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
hsVector3 distance;
|
||
|
hsPoint3 soundPos = GetPosition();
|
||
|
|
||
|
distance.Set( &listenerPos, &soundPos );
|
||
|
|
||
|
if( distSquared != nil )
|
||
|
*distSquared = distance.MagnitudeSquared();
|
||
|
|
||
|
if( GetMax() == 1000000000 )
|
||
|
return true;
|
||
|
|
||
|
hsScalar soundRadius = (hsScalar)( GetMax() * GetMax() );
|
||
|
|
||
|
return ( distance.MagnitudeSquared() <= soundRadius ) ? true : false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//// ////////////////////////////////////////////////////////
|
||
|
// Once the soft volume is calculated and our rank is computed, we can
|
||
|
// decide whether to actually enable or not.
|
||
|
// Note: we might have been "enabled" by our Calc call, but the ranking
|
||
|
// still could have disabled us, so we have to specify again whether
|
||
|
// we're enabled.
|
||
|
// Note: if we KNOW we're disabling this sound (out of range), Calc() doesn't
|
||
|
// have to be called at all, and we can simply call this function with
|
||
|
// enable = false.
|
||
|
void plSound::UpdateSoftVolume( hsBool enable, hsBool firstTime )
|
||
|
{
|
||
|
fNotHighEnoughPriority = !enable;
|
||
|
|
||
|
// Don't do any of this special stuff that follows if we're not supposed to be playing
|
||
|
if( IsPlaying() )
|
||
|
{
|
||
|
if( fSoftVolume * fDistAttenuation > 0.f && !fNotHighEnoughPriority )
|
||
|
{
|
||
|
if( fCurrFadeParams == &fCoolSoftVolumeTrickParams )
|
||
|
{
|
||
|
// Stop the fade, but since we are updating the softvolume with the intention of being audible
|
||
|
// tell StopFade not to set the soft volume to zero.
|
||
|
IStopFade(false, false);
|
||
|
}
|
||
|
if( !IActuallyPlaying() )
|
||
|
{
|
||
|
// Must've been stopped from being out of range. Start up again...
|
||
|
|
||
|
// Synch up to our start time.
|
||
|
// If this sound is auto starting and is background music, get the current time so we don't start
|
||
|
// with the play cursor already into the piece.
|
||
|
if(IsPropertySet(kPropAutoStart) && fType == kBackgroundMusic) fVirtualStartTime = hsTimer::GetSysSeconds();
|
||
|
ISynchedPlay( fVirtualStartTime );
|
||
|
}
|
||
|
}
|
||
|
else if( fCurrFadeParams != &fCoolSoftVolumeTrickParams && IActuallyPlaying() )
|
||
|
{
|
||
|
// Start our special trick, courtesy of Brice. Basically, we don't
|
||
|
// stop the sound immediately, but rather let it get to the end of
|
||
|
// the sound and loop once more. This way, if we go away and come back soon
|
||
|
// enough, it will be continuing as we expect it to, but if we wait long enough,
|
||
|
// it'll stop, saving processing time.
|
||
|
|
||
|
// Note: we just do it as a fade because it makes it easier on us that way!
|
||
|
fCoolSoftVolumeTrickParams.fCurrTime = 0.f;
|
||
|
fCoolSoftVolumeTrickParams.fLengthInSecs = firstTime ? 0.f : (hsScalar)fLength + ( (hsScalar)fLength - (hsScalar)GetTime() );
|
||
|
fCoolSoftVolumeTrickParams.fStopWhenDone = true;
|
||
|
fCoolSoftVolumeTrickParams.fFadeSoftVol = true;
|
||
|
fCoolSoftVolumeTrickParams.fType = plFadeParams::kLinear;
|
||
|
fCoolSoftVolumeTrickParams.fVolStart = fSoftVolume; // Don't actually change the volume this way
|
||
|
fCoolSoftVolumeTrickParams.fVolEnd = 0.f;
|
||
|
|
||
|
IStartFade( &fCoolSoftVolumeTrickParams );
|
||
|
|
||
|
plProfile_Inc( WaitingToDie );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
RefreshVolume();
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Returns the current volume, attenuated
|
||
|
hsScalar plSound::QueryCurrVolume( void ) const
|
||
|
{
|
||
|
return IAttenuateActualVolume( fCurrVolume ) * IGetChannelVolume();
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Used by ISetActualVolume(). Does the final attenuation on a volume before
|
||
|
// sending it to the sound processing. Only does soft regions for now.
|
||
|
hsScalar plSound::IAttenuateActualVolume( hsScalar volume ) const
|
||
|
{
|
||
|
if( fNotHighEnoughPriority )
|
||
|
return 0.f;
|
||
|
|
||
|
volume *= fSoftVolume;
|
||
|
|
||
|
if( IsPropertySet( kPropIs3DSound ) )
|
||
|
volume *= fDistAttenuation;
|
||
|
|
||
|
return volume;
|
||
|
}
|
||
|
|
||
|
void plSound::Activate(hsBool forcePlay)
|
||
|
{
|
||
|
// Our actual state...
|
||
|
fActive = true;
|
||
|
|
||
|
// re-create the sound state here:
|
||
|
if( forcePlay || fPlayOnReactivate )
|
||
|
{
|
||
|
ISynchedPlay( hsTimer::GetSysSeconds() );
|
||
|
fPlayOnReactivate = false;
|
||
|
}
|
||
|
|
||
|
RegisterOnAudioSys();
|
||
|
SetMuted(plgAudioSys::IsMuted());
|
||
|
}
|
||
|
|
||
|
void plSound::DeActivate( void )
|
||
|
{
|
||
|
UnregisterOnAudioSys();
|
||
|
|
||
|
if( fActive )
|
||
|
{
|
||
|
if( IsPlaying() )
|
||
|
{
|
||
|
Stop();
|
||
|
fPlayOnReactivate = true;
|
||
|
}
|
||
|
else
|
||
|
fPlayOnReactivate = false;
|
||
|
}
|
||
|
|
||
|
fActive = false;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Tell the audio system about ourselves.
|
||
|
void plSound::RegisterOnAudioSys( void )
|
||
|
{
|
||
|
if( !fRegistered )
|
||
|
{
|
||
|
plgAudioSys::RegisterSoftSound(GetKey());
|
||
|
fRegistered = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Tell the audio system to stop caring about us
|
||
|
void plSound::UnregisterOnAudioSys( void )
|
||
|
{
|
||
|
if( fRegistered )
|
||
|
{
|
||
|
plgAudioSys::UnregisterSoftSound(GetKey());
|
||
|
fRegistered = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Called by the audio system when we've been booted off (audio system is
|
||
|
// shutting down). Normally, we should already be shut down, but in case
|
||
|
// we're not, this function makes sure everything is cleaned up before
|
||
|
// the audio system itself shuts down.
|
||
|
void plSound::ForceUnregisterFromAudioSys( void )
|
||
|
{
|
||
|
DeActivate();
|
||
|
fRegistered = false;
|
||
|
}
|
||
|
|
||
|
void plSound::Read(hsStream* s, hsResMgr* mgr)
|
||
|
{
|
||
|
plSynchedObject::Read(s, mgr);
|
||
|
|
||
|
IRead( s, mgr );
|
||
|
|
||
|
// If we're autostart, start playing
|
||
|
if( IsPropertySet( kPropAutoStart ) )
|
||
|
Play();
|
||
|
|
||
|
// Make sure we synch or don't synch
|
||
|
if( IsPropertySet( kPropLocalOnly ) )
|
||
|
SetLocalOnly(true);
|
||
|
|
||
|
// If we're not playing, but we're going to, and we're going to fade in,
|
||
|
// then our current state is faded out, so set fFading
|
||
|
if( fFadeInParams.fLengthInSecs > 0 && !fPlaying )
|
||
|
fFading = true;
|
||
|
else if( fFadeInParams.fLengthInSecs <= 0 && !fPlaying )
|
||
|
fFading = false;
|
||
|
|
||
|
ILoadDataBuffer(); // make sure our sound buffer is loaded
|
||
|
|
||
|
// Force load on read
|
||
|
if( !fLoadOnDemandFlag || IsPropertySet( kPropDisableLOD ) )
|
||
|
{
|
||
|
LoadSound( IsPropertySet( kPropIs3DSound ) );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Loading on demand, but we still need the length. But that's ok, we'll get it when we get the fDataBuffer ref.
|
||
|
// But we want to preload the data, so go ahead and do that
|
||
|
if( !fLoadFromDiskOnDemand && !IsPropertySet( kPropLoadOnlyOnCall ) && fPriority <= plgAudioSys::GetPriorityCutoff())
|
||
|
{
|
||
|
IPreLoadBuffer(false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void plSound::Write(hsStream* s, hsResMgr* mgr)
|
||
|
{
|
||
|
plSynchedObject::Write(s, mgr);
|
||
|
IWrite( s, mgr );
|
||
|
}
|
||
|
|
||
|
void plSound::IRead( hsStream *s, hsResMgr *mgr )
|
||
|
{
|
||
|
fPlaying = s->ReadBool();
|
||
|
fVirtualStartTime = hsTimer::GetSysSeconds(); // Need if we're autostart
|
||
|
fTime = s->ReadSwapDouble();
|
||
|
fMaxFalloff = s->ReadSwap32();
|
||
|
fMinFalloff = s->ReadSwap32();
|
||
|
s->ReadSwap( &fCurrVolume );
|
||
|
s->ReadSwap( &fDesiredVol );
|
||
|
|
||
|
/// mcn debugging - Thanks to some of my older sound code, it's possible that a few volumes
|
||
|
/// will come in too large. This will only happen with scenes that were exported with that intermediate
|
||
|
/// code, which should be limited to my test scenes locally. Otherwise, things should be fine. This
|
||
|
/// is to compensate for those bogus files. (The fix is to reset the volume in MAX, since the bogusness
|
||
|
/// is in the range of the volume slider itself).
|
||
|
if( fDesiredVol > 1.f )
|
||
|
fDesiredVol = 1.f;
|
||
|
if( fCurrVolume > 1.f )
|
||
|
fCurrVolume = 1.f;
|
||
|
fMaxVolume = fDesiredVol;
|
||
|
|
||
|
fOuterVol = s->ReadSwap32();
|
||
|
fInnerCone = s->ReadSwap32();
|
||
|
fOuterCone = s->ReadSwap32();
|
||
|
s->ReadSwap( &fFadedVolume );
|
||
|
s->ReadSwap( &fProperties );
|
||
|
|
||
|
fType = s->ReadByte();
|
||
|
fPriority = s->ReadByte();
|
||
|
|
||
|
// HACK FOR OLDER EXPORTERS that thought Auto-start meant set fPlaying true
|
||
|
if( fPlaying )
|
||
|
SetProperty( kPropAutoStart, true );
|
||
|
|
||
|
// Read in fade params
|
||
|
fFadeInParams.Read( s );
|
||
|
fFadeOutParams.Read( s );
|
||
|
|
||
|
// Read in soft volume key
|
||
|
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, 0, kRefSoftVolume ), plRefFlags::kActiveRef );
|
||
|
|
||
|
// Read in the data buffer key
|
||
|
fDataBufferKey = mgr->ReadKey( s );
|
||
|
|
||
|
// EAX params
|
||
|
fEAXSettings.Read( s );
|
||
|
|
||
|
// EAX soft keys
|
||
|
mgr->ReadKeyNotifyMe( s, TRACKED_NEW plGenRefMsg( GetKey(), plRefMsg::kOnCreate, 0, kRefSoftOcclusionRegion ), plRefFlags::kActiveRef );
|
||
|
}
|
||
|
|
||
|
void plSound::IWrite( hsStream *s, hsResMgr *mgr )
|
||
|
{
|
||
|
s->WriteBool(fPlaying);
|
||
|
s->WriteSwapDouble(fTime);
|
||
|
s->WriteSwap32(fMaxFalloff);
|
||
|
s->WriteSwap32(fMinFalloff);
|
||
|
s->WriteSwap( fCurrVolume );
|
||
|
s->WriteSwap( fDesiredVol );
|
||
|
s->WriteSwap32(fOuterVol);
|
||
|
s->WriteSwap32(fInnerCone);
|
||
|
s->WriteSwap32(fOuterCone);
|
||
|
s->WriteSwap( fFadedVolume );
|
||
|
s->WriteSwap( fProperties );
|
||
|
s->WriteByte( fType );
|
||
|
s->WriteByte( fPriority );
|
||
|
|
||
|
// Write out fade params
|
||
|
fFadeInParams.Write( s );
|
||
|
fFadeOutParams.Write( s );
|
||
|
|
||
|
// Write out soft volume key
|
||
|
mgr->WriteKey( s, fSoftRegion );
|
||
|
|
||
|
// Write out data buffer key
|
||
|
if(fDataBuffer)
|
||
|
mgr->WriteKey( s, fDataBuffer->GetKey() );
|
||
|
else
|
||
|
mgr->WriteKey( s, fDataBufferKey );
|
||
|
|
||
|
// EAX params
|
||
|
fEAXSettings.Write( s );
|
||
|
|
||
|
// EAX Soft keys
|
||
|
mgr->WriteKey( s, fSoftOcclusionRegion );
|
||
|
}
|
||
|
|
||
|
void plSound::plFadeParams::Read( hsStream *s )
|
||
|
{
|
||
|
s->ReadSwap( &fLengthInSecs );
|
||
|
s->ReadSwap( &fVolStart );
|
||
|
s->ReadSwap( &fVolEnd );
|
||
|
s->ReadSwap( &fType );
|
||
|
s->ReadSwap( &fCurrTime );
|
||
|
s->ReadSwap( &fStopWhenDone );
|
||
|
s->ReadSwap( &fFadeSoftVol );
|
||
|
}
|
||
|
|
||
|
void plSound::plFadeParams::Write( hsStream *s )
|
||
|
{
|
||
|
s->WriteSwap( fLengthInSecs );
|
||
|
s->WriteSwap( fVolStart );
|
||
|
s->WriteSwap( fVolEnd );
|
||
|
s->WriteSwap( fType );
|
||
|
s->WriteSwap( fCurrTime );
|
||
|
s->WriteSwap( fStopWhenDone );
|
||
|
s->WriteSwap( fFadeSoftVol );
|
||
|
}
|
||
|
|
||
|
hsScalar plSound::plFadeParams::InterpValue( void )
|
||
|
{
|
||
|
hsScalar val;
|
||
|
|
||
|
switch( fType )
|
||
|
{
|
||
|
case kLinear:
|
||
|
val = ( ( fCurrTime / fLengthInSecs ) * ( fVolEnd - fVolStart ) ) + fVolStart;
|
||
|
break;
|
||
|
case kLogarithmic:
|
||
|
val = fCurrTime / fLengthInSecs;
|
||
|
val = ( ( val * val ) * ( fVolEnd - fVolStart ) ) + fVolStart;
|
||
|
break;
|
||
|
case kExponential:
|
||
|
val = fCurrTime / fLengthInSecs;
|
||
|
val = ( (hsScalar)sqrt( val ) * ( fVolEnd - fVolStart ) ) + fVolStart;
|
||
|
break;
|
||
|
default:
|
||
|
val = 0.f;
|
||
|
}
|
||
|
return val;
|
||
|
}
|
||
|
|
||
|
void plSound::SetFadeInEffect( plSound::plFadeParams::Type type, hsScalar length )
|
||
|
{
|
||
|
fFadeInParams.fLengthInSecs = length;
|
||
|
fFadeInParams.fType = type;
|
||
|
fFadeInParams.fVolStart = 0.f; // Will be set when activated
|
||
|
fFadeInParams.fVolEnd = 1.f; // Will be set when activated
|
||
|
fFadeInParams.fCurrTime = -1.f;
|
||
|
|
||
|
// If we're not playing, but we're going to, and we're going to fade in,
|
||
|
// then our current state is faded out, so set fFading
|
||
|
if( fFadeInParams.fLengthInSecs > 0 && !fPlaying )
|
||
|
fFading = true;
|
||
|
else if( fFadeInParams.fLengthInSecs <= 0 && !fPlaying )
|
||
|
fFading = false;
|
||
|
}
|
||
|
|
||
|
void plSound::SetFadeOutEffect( plSound::plFadeParams::Type type, hsScalar length )
|
||
|
{
|
||
|
fFadeOutParams.fLengthInSecs = length;
|
||
|
fFadeOutParams.fType = type;
|
||
|
fFadeOutParams.fVolStart = 1.f; // Will be set when activated
|
||
|
fFadeOutParams.fVolEnd = 0.f; // Will be set when activated
|
||
|
fFadeOutParams.fCurrTime = -1.f;
|
||
|
fFadeOutParams.fStopWhenDone = true;
|
||
|
}
|
||
|
|
||
|
plDrawableSpans* plSound::CreateProxy(const hsMatrix44& l2w, hsGMaterial* mat, hsTArray<UInt32>& idx, plDrawableSpans* addTo)
|
||
|
{
|
||
|
plDrawableSpans* myDraw = addTo;
|
||
|
|
||
|
if( fOuterCone < 360 )
|
||
|
{
|
||
|
hsScalar len = (hsScalar)GetMax();
|
||
|
hsScalar halfAng = hsScalarDegToRad(hsScalar(fInnerCone) * 0.5f);
|
||
|
hsScalar radius = len * tanf(halfAng);
|
||
|
if( fInnerCone < 180 )
|
||
|
len = -len;
|
||
|
myDraw = plDrawableGenerator::GenerateConicalDrawable(
|
||
|
radius,
|
||
|
len,
|
||
|
mat,
|
||
|
l2w,
|
||
|
true,
|
||
|
&hsColorRGBA().Set(1.f, 0.5f, 0.5f, 1.f),
|
||
|
&idx,
|
||
|
myDraw);
|
||
|
|
||
|
len = (hsScalar)GetMin();
|
||
|
halfAng = hsScalarDegToRad(hsScalar(fOuterCone) * 0.5f);
|
||
|
radius = len * tanf(halfAng);
|
||
|
if( fOuterCone < 180 )
|
||
|
len = -len;
|
||
|
|
||
|
myDraw = plDrawableGenerator::GenerateConicalDrawable(
|
||
|
radius,
|
||
|
len,
|
||
|
mat,
|
||
|
l2w,
|
||
|
true,
|
||
|
&hsColorRGBA().Set(0.25f, 0.25f, 0.5f, 1.f),
|
||
|
&idx,
|
||
|
myDraw);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
myDraw = plDrawableGenerator::GenerateSphericalDrawable(
|
||
|
hsPoint3(0,0,0),
|
||
|
(hsScalar)GetMin(),
|
||
|
mat,
|
||
|
l2w,
|
||
|
true,
|
||
|
&hsColorRGBA().Set(1.f, 0.5f, 0.5f, 1.f),
|
||
|
&idx,
|
||
|
myDraw);
|
||
|
|
||
|
myDraw = plDrawableGenerator::GenerateSphericalDrawable(
|
||
|
hsPoint3(0,0,0),
|
||
|
(hsScalar)GetMax(),
|
||
|
mat,
|
||
|
l2w,
|
||
|
true,
|
||
|
&hsColorRGBA().Set(0.25f, 0.25f, 0.5f, 1.f),
|
||
|
&idx,
|
||
|
myDraw);
|
||
|
}
|
||
|
return myDraw;
|
||
|
}
|
||
|
|
||
|
|
||
|
// call when state has changed
|
||
|
hsBool plSound::DirtySynchState(const char* sdlName /* kSDLSound */, UInt32 sendFlags)
|
||
|
{
|
||
|
/*
|
||
|
if( sdlName == nil )
|
||
|
sdlName = kSDLSound;
|
||
|
|
||
|
if( fOwningSceneObject != nil )
|
||
|
return fOwningSceneObject->DirtySynchState(sdlName, sendFlags);
|
||
|
*/
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//// plSoundVolumeApplicator /////////////////////////////////////////////////
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void plSoundVolumeApplicator::IApply( const plAGModifier *mod, double time )
|
||
|
{
|
||
|
plScalarChannel *chan = plScalarChannel::ConvertNoRef( fChannel );
|
||
|
if(chan)
|
||
|
{
|
||
|
hsScalar volume = chan->Value( time );
|
||
|
|
||
|
hsScalar digitalVolume = (float)pow( 10.f, volume / 20.f );
|
||
|
|
||
|
// Find the audio interface and thus the plSound from it
|
||
|
plSceneObject *so = mod->GetTarget( 0 );
|
||
|
if( so != nil )
|
||
|
{
|
||
|
const plAudioInterface *ai = so->GetAudioInterface();
|
||
|
if( ai != nil && fIndex < ai->GetNumSounds() )
|
||
|
{
|
||
|
plSound *sound = ai->GetSound( fIndex );
|
||
|
if( sound != nil )
|
||
|
{
|
||
|
sound->SetVolume( digitalVolume );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
plAGApplicator *plSoundVolumeApplicator::CloneWithChannel( plAGChannel *channel )
|
||
|
{
|
||
|
plSoundVolumeApplicator *clone = (plSoundVolumeApplicator *)plAGApplicator::CloneWithChannel( channel );
|
||
|
clone->fIndex = fIndex;
|
||
|
return clone;
|
||
|
}
|
||
|
|
||
|
void plSoundVolumeApplicator::Write( hsStream *stream, hsResMgr *mgr )
|
||
|
{
|
||
|
plAGApplicator::Write( stream, mgr );
|
||
|
stream->WriteSwap32( fIndex );
|
||
|
}
|
||
|
|
||
|
void plSoundVolumeApplicator::Read( hsStream *s, hsResMgr *mgr )
|
||
|
{
|
||
|
plAGApplicator::Read( s, mgr );
|
||
|
fIndex = s->ReadSwap32();
|
||
|
}
|