/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include
#include
#include "HeadSpin.h"
#include "hsGeometry3.h"
#include "hsTimer.h"
#include "hsResMgr.h"
#include "plgDispatch.h"
#include "plProfile.h"
#include "plWin32Sound.h"
#include "plAudioSystem.h"
#include "plDSoundBuffer.h"
#include "plWavFile.h"
#include "../plAudible/plWinAudible.h"
#include "../plNetMessage/plNetMessage.h"
#include "../pnNetCommon/plNetApp.h"
#include "../pnMessage/plSoundMsg.h"
#include "../pnMessage/plEventCallbackMsg.h"
#include "../plPipeline/plPlates.h"
#include "../plStatusLog/plStatusLog.h"
plProfile_CreateMemCounter("Sounds", "Memory", MemSounds);
plProfile_Extern(SoundPlaying);
plWin32Sound::plWin32Sound() :
fFailed(false),
fPositionInited(false),
fAwaitingPosition(false),
fTotalBytes(0),
fReallyPlaying(false),
fChannelSelect(0),
fDSoundBuffer(nil)
{
}
plWin32Sound::~plWin32Sound()
{
}
void plWin32Sound::Activate( hsBool forcePlay )
{
if( fFailed )
return;
plSound::Activate( forcePlay );
}
void plWin32Sound::DeActivate()
{
plSound::DeActivate();
IFreeBuffers();
}
void plWin32Sound::IFreeBuffers( void )
{
if( fDSoundBuffer != nil )
{
delete fDSoundBuffer;
fDSoundBuffer = nil;
plProfile_DelMem(MemSounds, fTotalBytes);
fTotalBytes = 0;
}
fPositionInited = false;
fAwaitingPosition = false;
}
void plWin32Sound::Update()
{
plSound::Update();
}
void plWin32Sound::IActuallyPlay( void )
{
//plSound::Play();
if (!fDSoundBuffer && plgAudioSys::Active())
LoadSound( IsPropertySet( kPropIs3DSound ) );
if(!fLoading )
{
if (fDSoundBuffer && plgAudioSys::Active() )
{
// Sometimes base/derived classes can be annoying
IDerivedActuallyPlay();
RefreshVolume();
}
else
{
// If we can't load (for ex., if audio is off), then we act like we played and then stopped
// really fast. Thus, we need to send *all* of our callbacks off immediately and then Stop().
UInt32 i;
for( i = 0; i < fSoundEvents.GetCount(); i++ )
{
fSoundEvents[ i ]->SendCallbacks();
}
// Now stop, 'cause we played really really really really fast
fPlaying = false;
fPlayOnReactivate = IsPropertySet( kPropLooping );
IActuallyStop();
}
}
}
void plWin32Sound::IActuallyStop()
{
if( fReallyPlaying )
{
if( fDSoundBuffer != nil )
{
if(IsPropertySet(kPropIncidental))
{
--fIncidentalsPlaying;
}
fDSoundBuffer->Stop();
plStatusLog::AddLineS("impacts.log", "Stopping %s", GetKeyName());
}
fReallyPlaying = false;
}
else
{
if( fDSoundBuffer != nil && fDSoundBuffer->IsPlaying() )
{
plStatusLog::AddLineS( "audio.log", 0xffff0000, "WARNING: BUFFER FLAGGED AS STOPPED BUT NOT STOPPED - %s", GetKey() ? GetKeyName() : nil );
fDSoundBuffer->Stop();
}
}
// send callbacks
plSoundEvent *event = IFindEvent( plSoundEvent::kStop );
if( event != nil )
{
event->SendCallbacks();
}
plSound::IActuallyStop();
}
plSoundMsg* plWin32Sound::GetStatus(plSoundMsg* pMsg)
{
plSoundMsg* pReply = TRACKED_NEW plSoundMsg;
pReply->AddReceiver( pMsg->GetSender() );
pReply->SetCmd(plSoundMsg::kStatusReply);
pReply->fLoop = IsPropertySet( kPropLooping );
pReply->fPlaying = IsPlaying();
return pReply;
}
void plWin32Sound::SetMin( const int m )
{
plSound::SetMin(m);
if(fDSoundBuffer)
{
fDSoundBuffer->SetMinDistance(m);
}
}
void plWin32Sound::SetMax( const int m )
{
plSound::SetMax(m);
if( fDSoundBuffer )
{
fDSoundBuffer->SetMaxDistance( m );
}
}
void plWin32Sound::SetOuterVolume( const int v )
{
plSound::SetOuterVolume(v);
if(fDSoundBuffer)
{
fDSoundBuffer->SetConeOutsideVolume(v);
}
}
void plWin32Sound::SetConeAngles( int inner, int outer )
{
plSound::SetConeAngles(inner, outer);
if(fDSoundBuffer)
{
fDSoundBuffer->SetConeAngles(inner, outer);
}
}
void plWin32Sound::SetConeOrientation( hsScalar x, hsScalar y, hsScalar z )
{
plSound::SetConeOrientation(x, y, z);
if(fDSoundBuffer)
{
fDSoundBuffer->SetConeOrientation(x, z, y);
}
}
void plWin32Sound::SetVelocity( const hsVector3 vel )
{
plSound::SetVelocity(vel);
if( fDSoundBuffer)
fDSoundBuffer->SetVelocity(vel.fX, vel.fZ, vel.fY);
}
void plWin32Sound::SetPosition( const hsPoint3 pos )
{
plSound::SetPosition(pos);
if(fDSoundBuffer)
{
// in openal sounds that are mono are played as positional. Since gui's may be positioned way off in space, the sound may not be audible.
// doing this allows us to play mono gui sounds and still hear them, since this attaches the sound to the listener.
if(fType == kGUISound)
{
hsPoint3 listenerPos = plgAudioSys::Sys()->GetCurrListenerPos();
fDSoundBuffer->SetPosition(listenerPos.fX, listenerPos.fZ, listenerPos.fY);
}
else
{
fDSoundBuffer->SetPosition(pos.fX, pos.fZ, pos.fY);
}
}
fPositionInited = true;
if( fAwaitingPosition )
{
// If this is set, then we tried to set the volume before the position. Since
// this results in some ghastly sound popping, we wait until we set the position
// (here), and then call our volume again
RefreshVolume();
fAwaitingPosition = false;
}
}
void plWin32Sound::ISetActualVolume(const float volume)
{
float vol = IAttenuateActualVolume( volume ) * IGetChannelVolume();
if( fDSoundBuffer )
{
if( fPositionInited || !IsPropertySet( kPropIs3DSound ) )
{
fDSoundBuffer->SetScalarVolume( vol );
}
else
{
// If position isn't inited, we don't want to set the volume yet,
// so set this flag so we know to set the volume once we DO get the position
fAwaitingPosition = true;
}
}
IUpdateDebugPlate(); // Byte me.
}
//////////////////////////////////////////////////////////////
// The base class will make sure all our params are correct and up-to-date,
// but before it does its work, we have to make sure our buffer is ready to
// be updated.
void plWin32Sound::IRefreshParams( void )
{
if (!fDSoundBuffer && plgAudioSys::Active())
LoadSound( IsPropertySet( kPropIs3DSound ) );
else
{
// There is a gap between the ds buffer stopping and the sound being marked as stopped.
// If the sound is asked to play again during this time frame it will never actually be
// played because it is still marked as playing. Not only that, but we'll lose a hardware voice too.
// This will fix that by starting it up again.
if(plgAudioSys::Active())
fDSoundBuffer->Play();
}
plSound::IRefreshParams();
}
void plWin32Sound::IRefreshEAXSettings( hsBool force )
{
if( fDSoundBuffer != nil )
fDSoundBuffer->SetEAXSettings( &GetEAXSettings(), force );
}
void plWin32Sound::IAddCallback( plEventCallbackMsg *pMsg )
{
plSoundEvent::Types type = plSoundEvent::GetTypeFromCallbackMsg( pMsg );
plSoundEvent *event;
if( type == plSoundEvent::kTime )
{
UInt32 byteTime = ( fDSoundBuffer != nil ) ? fDSoundBuffer->GetBufferBytePos( pMsg->fEventTime ) : 0;
event = IFindEvent( type, byteTime );
if( event == nil )
{
// Add a new sound event for this guy
event = TRACKED_NEW plSoundEvent( type, byteTime, this );
//fDSoundBuffer->AddPosNotify( byteTime );
fSoundEvents.Append( event );
}
}
else
{
event = IFindEvent( type );
if( event == nil )
{
// Add a new sound event for this guy
event = TRACKED_NEW plSoundEvent( type, this );
fSoundEvents.Append( event );
}
}
event->AddCallback( pMsg );
}
void plWin32Sound::IRemoveCallback( plEventCallbackMsg *pMsg )
{
plSoundEvent::Types type = plSoundEvent::GetTypeFromCallbackMsg( pMsg );
for(int i = 0; i < fSoundEvents.GetCount(); ++i)
{
if( fSoundEvents[ i ]->GetType() == type )
{
if( fSoundEvents[ i ]->RemoveCallback( pMsg ) )
{
if( fSoundEvents[ i ]->GetNumCallbacks() == 0 )
{
//if( fSoundEvents[ i ]->GetType() == plSoundEvent::kTime )
//fDSoundBuffer->RemovePosNotify( fSoundEvents[ i ]->GetTime() );
delete fSoundEvents[ i ];
fSoundEvents.Remove( i );
}
break;
}
}
}
}
plSoundEvent *plWin32Sound::IFindEvent( plSoundEvent::Types type, UInt32 bytePos )
{
for(int i = 0; i < fSoundEvents.GetCount(); ++i )
{
if( fSoundEvents[ i ]->GetType() == type )
{
if( type != plSoundEvent::kTime || bytePos == fSoundEvents[ i ]->GetTime() )
return fSoundEvents[ i ];
}
}
return nil;
}
void plWin32Sound::RemoveCallbacks(plSoundMsg* pSoundMsg)
{
for(int i = 0; i < pSoundMsg->GetNumCallbacks(); ++i )
IRemoveCallback( pSoundMsg->GetEventCallback( i ) );
}
void plWin32Sound::AddCallbacks(plSoundMsg* pSoundMsg)
{
for(int i = 0; i < pSoundMsg->GetNumCallbacks(); ++i )
IAddCallback( pSoundMsg->GetEventCallback( i ) );
}
hsBool plWin32Sound::MsgReceive( plMessage* pMsg )
{
plEventCallbackMsg *e = plEventCallbackMsg::ConvertNoRef( pMsg );
if( e != nil )
{
if( e->fEvent == kStop )
{
fPlaying = false;
fPlayOnReactivate = false; // Just to make sure...
// Do we have an ending fade?
if( fFadeOutParams.fLengthInSecs > 0 )
{
IStartFade( &fFadeOutParams );
plSoundEvent *event = IFindEvent( plSoundEvent::kStop );
if( event != nil )
event->SendCallbacks();
}
else
{
if( fFading )
IStopFade();
fCurrVolume = 0.f;
this->ISetActualVolume( fCurrVolume );
}
this->IActuallyStop();
return true;
}
}
return plSound::MsgReceive( pMsg );
}
void plWin32Sound::IRead( hsStream *s, hsResMgr *mgr )
{
plSound::IRead( s, mgr );
fChannelSelect = s->ReadByte();
}
void plWin32Sound::IWrite( hsStream *s, hsResMgr *mgr )
{
plSound::IWrite( s, mgr );
s->WriteByte( fChannelSelect );
}