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