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.

471 lines
12 KiB

/*==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 );
}