/*==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 3 ds 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 , 3 ds 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 "HeadSpin.h"
# include "hsTimer.h"
# include "hsGeometry3.h"
# include "plgDispatch.h"
# include "plProfile.h"
# include "plWin32Sound.h"
# include "plWin32StreamingSound.h"
# include "plDSoundBuffer.h"
# include "plAudioSystem.h"
# include "plAudioCore/plAudioFileReader.h"
# include "plAudioCore/plSoundBuffer.h"
# include "plAudioCore/plSoundDeswizzler.h"
# include "pnMessage/plSoundMsg.h"
# include "pnMessage/plEventCallbackMsg.h"
# include "plStatusLog/plStatusLog.h"
# if HS_BUILD_FOR_WIN32
# include <direct.h>
# else
# include <unistd.h>
# endif
# define STREAMING_UPDATE_MS 200
plProfile_Extern ( MemSounds ) ;
plProfile_CreateAsynchTimer ( " Stream Shove Time " , " Sound " , StreamSndShoveTime ) ;
plProfile_CreateAsynchTimer ( " Stream Swizzle Time " , " Sound " , StreamSwizzleTime ) ;
plProfile_Extern ( SoundLoadTime ) ;
plWin32StreamingSound : : plWin32StreamingSound ( ) :
fDataStream ( nil ) ,
fBlankBufferFillCounter ( 0 ) ,
fDeswizzler ( nil ) ,
fStreamType ( kNoStream ) ,
fLastStreamingUpdate ( 0 ) ,
fStopping ( false ) ,
fPlayWhenStopped ( false ) ,
fStartPos ( 0 )
{
fBufferLengthInSecs = plgAudioSys : : GetStreamingBufferSize ( ) ;
}
plWin32StreamingSound : : ~ plWin32StreamingSound ( )
{
/// Call before we delete our dataStream
DeActivate ( ) ;
IUnloadDataBuffer ( ) ;
delete fDataStream ;
delete fDeswizzler ;
}
void plWin32StreamingSound : : DeActivate ( )
{
plWin32Sound : : DeActivate ( ) ;
}
// Change the filename used by this streaming sound, so we can play a different file
void plWin32StreamingSound : : SetFilename ( const char * filename , bool isCompressed )
{
fNewFilename = filename ;
fIsCompressed = isCompressed ;
fFailed = false ; // just in case the last sound failed to load turn this off so it can try to load the new sound
}
//////////////////////////////////////////////////////////////
// Override, 'cause we don't want to actually LOAD the sound for streaming,
// just make sure it's decompressed and such and ready to stream.
plSoundBuffer : : ELoadReturnVal plWin32StreamingSound : : IPreLoadBuffer ( bool playWhenLoaded , bool isIncidental /* = false */ )
{
if ( fPlayWhenStopped )
return plSoundBuffer : : kPending ;
bool sfxPath = fNewFilename . IsValid ( ) ? false : true ;
if ( fDataStream ! = nil & & ! fNewFilename . IsValid ( ) )
return plSoundBuffer : : kSuccess ; // Already loaded
if ( ! ILoadDataBuffer ( ) )
{
return plSoundBuffer : : kError ;
}
plSoundBuffer * buffer = ( plSoundBuffer * ) fDataBufferKey - > ObjectIsLoaded ( ) ;
if ( ! buffer )
return plSoundBuffer : : kError ;
// The databuffer also needs to know if the source is compressed or not
if ( fNewFilename . IsValid ( ) )
{
buffer - > SetFileName ( fNewFilename ) ;
buffer - > SetFlag ( plSoundBuffer : : kStreamCompressed , fIsCompressed ) ;
fNewFilename = " " ;
if ( fReallyPlaying )
{
fPlayWhenStopped = true ;
return plSoundBuffer : : kPending ;
}
}
if ( buffer - > IsValid ( ) )
{
plAudioFileReader : : StreamType type = plAudioFileReader : : kStreamNative ;
fStreamType = kStreamCompressed ;
if ( ! buffer - > HasFlag ( plSoundBuffer : : kStreamCompressed ) )
{
if ( buffer - > GetDataLengthInSecs ( ) > plgAudioSys : : GetStreamFromRAMCutoff ( ) )
{
fStreamType = kStreamFromDisk ;
type = plAudioFileReader : : kStreamWAV ;
}
else
{
fStreamType = kStreamFromRAM ;
type = plAudioFileReader : : kStreamRAM ;
}
}
if ( ! fStartPos )
{
if ( buffer - > AsyncLoad ( type , isIncidental ? 0 : STREAMING_BUFFERS * STREAM_BUFFER_SIZE ) = = plSoundBuffer : : kPending )
{
fPlayWhenLoaded = playWhenLoaded ;
fLoading = true ;
return plSoundBuffer : : kPending ;
}
}
fSrcFilename = buffer - > GetFileName ( ) ;
bool streamCompressed = ( buffer - > HasFlag ( plSoundBuffer : : kStreamCompressed ) ! = 0 ) ;
delete fDataStream ;
fDataStream = buffer - > GetAudioReader ( ) ;
if ( ! fDataStream )
{
plAudioCore : : ChannelSelect select = buffer - > GetReaderSelect ( ) ;
bool streamCompressed = ( buffer - > HasFlag ( plSoundBuffer : : kStreamCompressed ) ! = 0 ) ;
/// Open da file
plFileName strPath = plFileSystem : : GetCWD ( ) ;
if ( sfxPath )
strPath = plFileName : : Join ( strPath , " sfx " ) ;
strPath = plFileName : : Join ( strPath , fSrcFilename ) ;
fDataStream = plAudioFileReader : : CreateReader ( strPath , select , type ) ;
}
if ( fDataStream = = nil | | ! fDataStream - > IsValid ( ) )
{
delete fDataStream ;
fDataStream = nil ;
return plSoundBuffer : : kError ;
}
IPrintDbgMessage ( plFormat ( " Readied file {} for streaming " , fSrcFilename ) . c_str ( ) ) ;
// dont free sound data until we have a chance to use it in load sound
return fDataStream ? plSoundBuffer : : kSuccess : plSoundBuffer : : kError ;
}
plStatusLog : : AddLineS ( " audio.log " , " EnsureLoadable failed for streaming sound %d " , fDataBufferKey - > GetName ( ) . c_str ( ) ) ;
return plSoundBuffer : : kError ;
}
void plWin32StreamingSound : : IFreeBuffers ( void )
{
plWin32Sound : : IFreeBuffers ( ) ;
if ( fLoadFromDiskOnDemand & & ! IsPropertySet ( kPropLoadOnlyOnCall ) )
{
// if the audio system has just restarted, dont delete the datastream. This way we can pick up where we left off instead of restarting the sound.
if ( ! plgAudioSys : : IsRestarting ( ) )
{
// we are deleting the stream, we must release the sound data.
FreeSoundData ( ) ;
delete fDataStream ;
fDataStream = nil ;
}
fSrcFilename = " " ;
}
}
///////////////////////////////////////////////////////////////////
// Overload from plSound. Basically sets up the streaming file and fills the
// first half of our buffer. We'll fill the rest of the buffer as we get
// notifications for such.
bool plWin32StreamingSound : : LoadSound ( bool is3D )
{
if ( fFailed )
return false ;
if ( ! plgAudioSys : : Active ( ) | | fDSoundBuffer )
return false ;
if ( fPriority > plgAudioSys : : GetPriorityCutoff ( ) )
return false ; // Don't set the failed flag, just return
// Debug flag #1
if ( is3D & & fChannelSelect > 0 & & plgAudioSys : : IsDebugFlagSet ( plgAudioSys : : kDisableRightSelect ) )
{
// Force a fail
fFailed = true ;
return false ;
}
plSoundBuffer : : ELoadReturnVal retVal = IPreLoadBuffer ( true ) ;
if ( retVal = = plSoundBuffer : : kPending )
return true ;
if ( retVal = = plSoundBuffer : : kError )
{
plString str = plFormat ( " Unable to open streaming source {} " ,
fDataBufferKey - > GetName ( ) ) ;
IPrintDbgMessage ( str . c_str ( ) , true ) ;
fFailed = true ;
return false ;
}
SetProperty ( kPropIs3DSound , is3D ) ;
plWAVHeader header = fDataStream - > GetHeader ( ) ;
uint32_t bufferSize = ( uint32_t ) ( fBufferLengthInSecs * header . fAvgBytesPerSec ) ;
// Debug flag #2
if ( is3D & & fChannelSelect = = 0 & & header . fNumChannels > 1 & & plgAudioSys : : IsDebugFlagSet ( plgAudioSys : : kDisableLeftSelect ) )
{
// Force a fail
fFailed = true ;
return false ;
}
// Actually create the buffer now (always looping)
fDSoundBuffer = new plDSoundBuffer ( bufferSize , header , is3D , IsPropertySet ( kPropLooping ) , false , true ) ;
if ( ! fDSoundBuffer - > IsValid ( ) )
{
fDataStream - > Close ( ) ;
delete fDataStream ;
fDataStream = nil ;
delete fDSoundBuffer ;
fDSoundBuffer = nil ;
plString str = plFormat ( " Can't create sound buffer for {}.wav. This could happen if the wav file is a stereo file. "
" Stereo files are not supported on 3D sounds. If the file is not stereo then please report this error. " ,
GetFileName ( ) ) ;
IPrintDbgMessage ( str . c_str ( ) , true ) ;
fFailed = true ;
return false ;
}
fTotalBytes = ( uint32_t ) bufferSize ;
plProfile_NewMem ( MemSounds , fTotalBytes ) ;
plSoundBuffer * buffer = ( plSoundBuffer * ) fDataBufferKey - > ObjectIsLoaded ( ) ;
if ( ! buffer )
return false ;
bool setupSource = true ;
if ( ! buffer - > GetData ( ) | | fStartPos )
{
if ( fStartPos & & fStartPos < = fDataStream - > NumBytesLeft ( ) )
{
fDataStream - > SetPosition ( fStartPos ) ;
plStatusLog : : AddLineS ( " syncaudio.log " , " startpos %d " , fStartPos ) ;
}
// if we get here we are not starting from the beginning of the sound. We still have an audio loaded and need to pick up where we left off
if ( ! fDSoundBuffer - > SetupStreamingSource ( fDataStream ) )
{
setupSource = false ;
}
}
else
{
// this sound is starting from the beginning. Get the data and start it.
if ( ! fDSoundBuffer - > SetupStreamingSource ( buffer - > GetData ( ) , buffer - > GetAsyncLoadLength ( ) ) )
{
setupSource = false ;
}
}
if ( ! setupSource )
{
fDataStream - > Close ( ) ;
delete fDataStream ;
fDataStream = nil ;
delete fDSoundBuffer ;
fDSoundBuffer = nil ;
plStatusLog : : AddLineS ( " audio.log " , " Could not play streaming sound, no voices left %s " , GetKeyName ( ) . c_str ( ) ) ;
return false ;
}
FreeSoundData ( ) ;
IRefreshEAXSettings ( true ) ;
// Debug info
plString dbg = plFormat ( " Streaming {}. " , fSrcFilename ) ;
IPrintDbgMessage ( dbg . c_str ( ) ) ;
plStatusLog : : AddLineS ( " audioTimes.log " , 0xffffffff , " Streaming %4.2f secs of %s " ,
fDataStream - > GetLengthInSecs ( ) , GetKey ( ) - > GetUoid ( ) . GetObjectName ( ) . c_str ( ) ) ;
// Get pertinent info
SetLength ( ( float ) fDataStream - > GetLengthInSecs ( ) ) ;
// Set up our deswizzler, if necessary
delete fDeswizzler ;
if ( fDataStream - > GetHeader ( ) . fNumChannels ! = header . fNumChannels )
fDeswizzler = new plSoundDeswizzler ( ( uint32_t ) ( fBufferLengthInSecs * fDataStream - > GetHeader ( ) . fAvgBytesPerSec ) ,
( uint8_t ) ( fDataStream - > GetHeader ( ) . fNumChannels ) ,
header . fBitsPerSample / 8 ) ;
else
fDeswizzler = nil ;
// LEAVE THE WAV FILE OPEN! (We *are* streaming, after all :)
return true ;
}
void plWin32StreamingSound : : IStreamUpdate ( )
{
if ( ! fReallyPlaying )
return ;
if ( hsTimer : : GetMilliSeconds ( ) - fLastStreamingUpdate < STREAMING_UPDATE_MS ) // filter out update requests so we aren't doing this more that we need to
return ;
plProfile_BeginTiming ( StreamSndShoveTime ) ;
if ( fDSoundBuffer )
{
if ( fDSoundBuffer - > BuffersQueued ( ) = = 0 & & fDataStream - > NumBytesLeft ( ) = = 0 & & ! fDSoundBuffer - > IsLooping ( ) )
{
// If we are fading out it's possible that we will hit this multiple times causing this sound to try and fade out multiple times, never allowing it to actually stop
if ( ! fStopping )
{
fStopping = true ;
Stop ( ) ;
plProfile_EndTiming ( StreamSndShoveTime ) ;
}
return ;
}
if ( ! fDSoundBuffer - > StreamingFillBuffer ( fDataStream ) )
{
plStatusLog : : AddLineS ( " audio.log " , " %s Streaming buffer fill failed " , GetKeyName ( ) . c_str ( ) ) ;
}
}
plProfile_EndTiming ( StreamSndShoveTime ) ;
}
void plWin32StreamingSound : : Update ( )
{
plWin32Sound : : Update ( ) ;
IStreamUpdate ( ) ;
}
void plWin32StreamingSound : : IDerivedActuallyPlay ( void )
{
fStopping = false ;
if ( ! fReallyPlaying )
{
for ( ; ; )
{
if ( fSynchedStartTimeSec )
{
// if we are synching to another sound this is our latency time
fDSoundBuffer - > SetTimeOffsetSec ( ( float ) ( hsTimer : : GetSeconds ( ) - fSynchedStartTimeSec ) ) ;
fSynchedStartTimeSec = 0 ;
}
if ( IsPropertySet ( kPropIncidental ) )
{
if ( fIncidentalsPlaying > = MAX_INCIDENTALS )
break ;
+ + fIncidentalsPlaying ;
}
fDSoundBuffer - > Play ( ) ;
fReallyPlaying = true ;
break ;
}
}
/// Send start callbacks
plSoundEvent * event = IFindEvent ( plSoundEvent : : kStart ) ;
if ( event ! = nil )
event - > SendCallbacks ( ) ;
}
void plWin32StreamingSound : : IActuallyStop ( )
{
fStopping = false ;
plWin32Sound : : IActuallyStop ( ) ;
if ( fPlayWhenStopped )
{
fPlayWhenStopped = false ;
Play ( ) ;
}
}
unsigned plWin32StreamingSound : : GetByteOffset ( )
{
if ( fDataStream & & fDSoundBuffer )
{
unsigned bytesQueued = fDSoundBuffer - > BuffersQueued ( ) * STREAM_BUFFER_SIZE ;
unsigned offset = fDSoundBuffer - > GetByteOffset ( ) ;
long byteoffset = ( ( fDataStream - > GetDataSize ( ) - fDataStream - > NumBytesLeft ( ) ) - bytesQueued ) + offset ;
return byteoffset < 0 ? fDataStream - > GetDataSize ( ) - std : : abs ( byteoffset ) : byteoffset ;
}
return 0 ;
}
float plWin32StreamingSound : : GetActualTimeSec ( )
{
if ( fDataStream & & fDSoundBuffer )
return fDSoundBuffer - > bytePosToMSecs ( fDataStream - > NumBytesLeft ( ) ) / 1000.0f ;
return 0.0f ;
}
void plWin32StreamingSound : : SetStartPos ( unsigned bytes )
{
fStartPos = bytes ;
}
void plWin32StreamingSound : : ISetActualTime ( double t )
{
//fStartTimeSec = 0;
if ( fDataStream & & fDSoundBuffer )
fDataStream - > SetPosition ( fDSoundBuffer - > GetBufferBytePos ( ( float ) t ) ) ;
//else
// fStartTimeSec = t;
}
bool plWin32StreamingSound : : MsgReceive ( plMessage * pMsg )
{
return plWin32Sound : : MsgReceive ( pMsg ) ;
}
void plWin32StreamingSound : : IRemoveCallback ( plEventCallbackMsg * pCBMsg )
{
plWin32Sound : : IRemoveCallback ( pCBMsg ) ;
}
void plWin32StreamingSound : : IAddCallback ( plEventCallbackMsg * pCBMsg )
{
if ( plSoundEvent : : GetTypeFromCallbackMsg ( pCBMsg ) ! = plSoundEvent : : kStop & &
plSoundEvent : : GetTypeFromCallbackMsg ( pCBMsg ) ! = plSoundEvent : : kStart )
{
hsAssert ( false , " Streaming sounds only support start and stop callbacks at this time. " ) ;
return ;
}
plWin32Sound : : IAddCallback ( pCBMsg ) ;
}