|
|
|
/*==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 <direct.h>
|
|
|
|
#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 "../plAudioCore/plSrtFileReader.h"
|
|
|
|
#include "../pnMessage/plEventCallbackMsg.h"
|
|
|
|
#include "../pnMessage/plSoundMsg.h"
|
|
|
|
#include "../plMessage/plSubtitleMsg.h"
|
|
|
|
#include "../plNetMessage/plNetMessage.h"
|
|
|
|
#include "../pnNetCommon/plNetApp.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()
|
|
|
|
{
|
|
|
|
plSoundBuffer* buf = GetDataBuffer();
|
|
|
|
if (plgAudioSys::AreSubtitlesEnabled() && buf != nullptr) {
|
|
|
|
plSrtFileReader* srtReader = buf->GetSrtReader();
|
|
|
|
if (srtReader != nullptr) {
|
|
|
|
while (plSrtEntry* nextEntry = srtReader->GetNextEntryStartingBeforeTime((uint32_t)(GetActualTimeSec() * 1000.0f))) {
|
|
|
|
// add a plSubtitleMsg to go... to whoever is listening (probably the KI)
|
|
|
|
plSubtitleMsg* msg = new plSubtitleMsg(nextEntry->GetSubtitleText(), nextEntry->GetSpeakerName());
|
|
|
|
msg->Send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plSound::Update();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plWin32Sound::IActuallyPlay( void )
|
|
|
|
{
|
|
|
|
//plSound::Play();
|
|
|
|
if (!fDSoundBuffer && plgAudioSys::Active())
|
|
|
|
LoadSound( IsPropertySet( kPropIs3DSound ) );
|
|
|
|
if(!fLoading )
|
|
|
|
{
|
|
|
|
if (fDSoundBuffer && plgAudioSys::Active() )
|
|
|
|
{
|
|
|
|
if (!fReallyPlaying && fSynchedStartTimeSec > 0) {
|
|
|
|
// advance past any subtitles that would end before the synched start time
|
|
|
|
// not sure when this actually happens...
|
|
|
|
plSoundBuffer* buf = GetDataBuffer();
|
|
|
|
if (buf != nullptr) {
|
|
|
|
plSrtFileReader* srtReader = buf->GetSrtReader();
|
|
|
|
if (srtReader != nullptr) {
|
|
|
|
srtReader->AdvanceToTime(fSynchedStartTimeSec * 1000.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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(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 );
|
|
|
|
}
|