/*==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 .
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
#include "hsTypes.h"
#include "plSoundBuffer.h"
#include "hsStream.h"
#include "hsUtils.h"
#include
#include "plgDispatch.h"
#include "hsResMgr.h"
#include "plSrtFileReader.h"
#include "../pnMessage/plRefMsg.h"
#include "../plFile/plFileUtils.h"
#include "../plFile/hsFiles.h"
#include "../plUnifiedTime/plUnifiedTime.h"
#include "../pnUtils/pnUtils.h"
#include "../plStatusLog/plStatusLog.h"
#include "hsTimer.h"
static bool s_running;
static LISTDECL(plSoundBuffer, link) s_loading;
static CCritSect s_critsect;
static CEvent s_event(kEventAutoReset);
static void GetFullPath( const char filename[], char *destStr )
{
char path[ kFolderIterator_MaxPath ];
if( strchr( filename, '\\' ) != nil )
strcpy( path, filename );
else
sprintf( path, "sfx\\%s", filename );
strcpy( destStr, path );
}
//// IGetReader //////////////////////////////////////////////////////////////
// Makes sure the sound is ready to load without any extra processing (like
// decompression or the like), then opens a reader for it.
// fullpath tells the function whether to append 'sfx' to the path or not (we don't want to do this if were providing the full path)
static plAudioFileReader *CreateReader( hsBool fullpath, const char filename[], plAudioFileReader::StreamType type, plAudioCore::ChannelSelect channel )
{
char path[512];
if(fullpath) GetFullPath(filename, path);
else
strcpy(path, filename);
plAudioFileReader* reader = plAudioFileReader::CreateReader(path, channel, type);
if( reader == nil || !reader->IsValid() )
{
delete reader;
return nil;
}
return reader;
}
// our loading thread
static void LoadCallback(void *)
{
LISTDECL(plSoundBuffer, link) templist;
while(s_running)
{
s_critsect.Enter();
{
while(plSoundBuffer *buffer = s_loading.Head())
{
templist.Link(buffer);
}
}
s_critsect.Leave();
if(!templist.Head())
{
s_event.Wait(kEventWaitForever);
}
else
{
plAudioFileReader *reader = nil;
plSrtFileReader* srtReader = nullptr;
while(plSoundBuffer *buffer = templist.Head())
{
if(buffer->GetData())
{
const char* srcFilename = buffer->GetFileName();
reader = CreateReader(true, srcFilename, buffer->GetAudioReaderType(), buffer->GetReaderSelect());
if( reader )
{
unsigned readLen = buffer->GetAsyncLoadLength() ? buffer->GetAsyncLoadLength() : buffer->GetDataLength();
reader->Read( readLen, buffer->GetData() );
buffer->SetAudioReader(reader); // give sound buffer reader, since we may need it later
plSrtFileReader* srtReader = buffer->GetSrtReader();
if (srtReader != nullptr && (strcmp(srtReader->GetCurrentAudioFileName(), srcFilename) == 0)) {
// same file we were playing before, so start the SRT feed over instead of deleting and reloading
srtReader->StartOver();
} else {
std::unique_ptr newSrtFileReader(new plSrtFileReader(srcFilename));
if (newSrtFileReader->ReadFile())
buffer->SetSrtReader(newSrtFileReader.release());
}
}
else
{
buffer->SetError();
}
}
templist.Unlink(buffer);
buffer->SetLoaded(true);
}
}
}
// we need to be sure that all buffers are removed from our load list when shutting this thread down or we will hang,
// since the sound buffer will wait to be destroyed until it is marked as loaded
s_critsect.Enter();
{
while(plSoundBuffer *buffer = s_loading.Head())
{
buffer->SetLoaded(true);
s_loading.Unlink(buffer);
}
}
s_critsect.Leave();
}
void plSoundBuffer::Init()
{
s_running = true;
_beginthread(LoadCallback, 0, 0);
}
void plSoundBuffer::Shutdown()
{
s_running = false;
s_event.Signal();
}
//// Constructor/Destructor //////////////////////////////////////////////////
plSoundBuffer::plSoundBuffer()
{
IInitBuffer();
}
plSoundBuffer::plSoundBuffer( const char *fileName, UInt32 flags )
{
IInitBuffer();
SetFileName( fileName );
fFlags = flags;
fValid = IGrabHeaderInfo();
}
plSoundBuffer::~plSoundBuffer()
{
// if we are loading a sound we need to wait for the loading thread to be completely done processing this buffer.
// otherwise it may try to access this buffer after it's been deleted
if(fLoading)
{
while(!fLoaded)
{
Sleep(10);
}
}
ASSERT(!link.IsLinked());
delete [] fFileName;
UnLoad();
delete fSrtReader;
}
void plSoundBuffer::IInitBuffer()
{
fError = false;
fValid = false;
fFileName = nil;
fData = nil;
fDataLength = 0;
fFlags = 0;
fDataRead = 0;
fReader = nil;
fSrtReader = nullptr;
fLoaded = 0;
fLoading = false;
fHeader.fFormatTag = 0;
fHeader.fNumChannels = 0;
fHeader.fNumSamplesPerSec = 0;
fHeader.fAvgBytesPerSec = 0;
fHeader.fBlockAlign = 0;
fHeader.fBitsPerSample = 0;
}
//// GetDataLengthInSecs /////////////////////////////////////////////////////
hsScalar plSoundBuffer::GetDataLengthInSecs( void ) const
{
return (hsScalar)fDataLength / (hsScalar)fHeader.fAvgBytesPerSec;
}
//// Read/Write //////////////////////////////////////////////////////////////
void plSoundBuffer::Read( hsStream *s, hsResMgr *mgr )
{
hsKeyedObject::Read( s, mgr );
s->ReadSwap( &fFlags );
s->ReadSwap( &fDataLength );
fFileName = s->ReadSafeString();
s->ReadSwap( &fHeader.fFormatTag );
s->ReadSwap( &fHeader.fNumChannels );
s->ReadSwap( &fHeader.fNumSamplesPerSec );
s->ReadSwap( &fHeader.fAvgBytesPerSec );
s->ReadSwap( &fHeader.fBlockAlign );
s->ReadSwap( &fHeader.fBitsPerSample );
fValid = false;
if( !( fFlags & kIsExternal ) )
{
fData = TRACKED_NEW UInt8[ fDataLength ];
if( fData == nil )
fFlags |= kIsExternal;
else
{
s->Read( fDataLength, fData );
fValid = true;
SetLoaded(true);
}
}
else
{
fData = nil;
// fValid = EnsureInternal();
fValid = true;
}
}
void plSoundBuffer::Write( hsStream *s, hsResMgr *mgr )
{
hsKeyedObject::Write( s, mgr );
if( fData == nil )
fFlags |= kIsExternal;
s->WriteSwap( fFlags );
s->WriteSwap( fDataLength );
// Truncate the path to just a file name on write
if( fFileName != nil )
{
char *nameOnly = strrchr( fFileName, '\\' );
if( nameOnly != nil )
s->WriteSafeString( nameOnly + 1 );
else
s->WriteSafeString( fFileName );
}
else
s->WriteSafeString( nil );
s->WriteSwap( fHeader.fFormatTag );
s->WriteSwap( fHeader.fNumChannels );
s->WriteSwap( fHeader.fNumSamplesPerSec );
s->WriteSwap( fHeader.fAvgBytesPerSec );
s->WriteSwap( fHeader.fBlockAlign );
s->WriteSwap( fHeader.fBitsPerSample );
if( !( fFlags & kIsExternal ) )
s->Write( fDataLength, fData );
}
//// SetFileName /////////////////////////////////////////////////////////////
void plSoundBuffer::SetFileName( const char *name )
{
if(fLoading)
{
hsAssert(false, "Unable to set SoundBuffer filename");
return;
}
delete [] fFileName;
if( name != nil )
fFileName = hsStrcpy( name );
else
fFileName = nil;
// Data is no longer valid
UnLoad();
}
//// GetReaderSelect /////////////////////////////////////////////////////////
// Translates our flags into the ChannelSelect enum for plAudioFileReader
plAudioCore::ChannelSelect plSoundBuffer::GetReaderSelect( void ) const
{
if( fFlags & kOnlyLeftChannel )
return plAudioCore::kLeft;
else if( fFlags & kOnlyRightChannel )
return plAudioCore::kRight;
else
return plAudioCore::kAll;
}
//// IGetFullPath ////////////////////////////////////////////////////////////
// Construct our current full path to our sound.
void plSoundBuffer::IGetFullPath( char *destStr )
{
if(!fFileName)
{
*destStr = 0;
return;
}
char path[ kFolderIterator_MaxPath ];
if( strchr( fFileName, '\\' ) != nil )
strcpy( path, fFileName );
else
sprintf( path, "sfx\\%s", fFileName );
strcpy( destStr, path );
}
//============================================================================
// Asyncload will queue up a buffer for loading in our loading list the first time it is called.
// It will load in "length" number of bytes, if length is non zero. If length is zero the entire file will be loaded
// When called subsequent times it will check to see if the data has been loaded.
// Returns kPending while still loading the file. Returns kSuccess when the data has been loaded.
// While a file is loading(fLoading == true, and fLoaded == false) a buffer, no paremeters of the buffer should be modified.
plSoundBuffer::ELoadReturnVal plSoundBuffer::AsyncLoad(plAudioFileReader::StreamType type, unsigned length /* = 0 */ )
{
if(!s_running)
return kError; // we cannot load the data since the load thread is no longer running
if(!fLoading && !fLoaded)
{
fAsyncLoadLength = length;
fStreamType = type;
if(fData == nil )
{
fData = TRACKED_NEW UInt8[ fAsyncLoadLength ? fAsyncLoadLength : fDataLength ];
if( fData == nil )
return kError;
}
s_critsect.Enter();
{
fLoading = true;
s_loading.Link(this);
}
s_critsect.Leave();
s_event.Signal();
}
if(fLoaded)
{
if(fLoading) // ensures we only do this stuff one time
{
ELoadReturnVal retVal = kSuccess;
if(fError)
{
retVal = kError;
fError = false;
}
if(fReader)
{
fHeader = fReader->GetHeader();
SetDataLength(fReader->GetDataSize());
}
fFlags &= ~kIsExternal;
fLoading = false;
return retVal;
}
return kSuccess;
}
return kPending;
}
//// ForceNonInternal ////////////////////////////////////////////////////////
// destroys loaded, and frees data
void plSoundBuffer::UnLoad( void )
{
if(fLoaded)
int i = 0;
if(fLoading)
return;
if(fReader)
fReader->Close();
delete fReader;
fReader = nil;
delete [] fData;
fData = nil;
SetLoaded(false);
fFlags |= kIsExternal;
}
//// IRoundDataPos ///////////////////////////////////////////////////////////
void plSoundBuffer::RoundDataPos( UInt32 &pos )
{
UInt32 extra = pos & ( fHeader.fBlockAlign - 1 );
pos -= extra;
}
// transfers ownership to caller
plAudioFileReader *plSoundBuffer::GetAudioReader()
{
plAudioFileReader * reader = fReader;
fReader = nil;
return reader;
}
// WARNING: called by the loader thread(only)
// the reader will be handed off for later use. This is useful for streaming sound if we want to load the first chunk of data
// and the continue streaming the file from disk.
void plSoundBuffer::SetAudioReader(plAudioFileReader *reader)
{
if(fReader)
fReader->Close();
delete fReader;
fReader = reader;
}
void plSoundBuffer::SetLoaded(bool loaded)
{
fLoaded = loaded;
}
void plSoundBuffer::SetSrtReader(plSrtFileReader* reader)
{
delete fSrtReader;
fSrtReader = reader;
}
/*****************************************************************************
*
* for plugins only
*
***/
//// SetInternalData /////////////////////////////////////////////////////////
void plSoundBuffer::SetInternalData( plWAVHeader &header, UInt32 length, UInt8 *data )
{
if(fLoading) return;
fFileName = nil;
fHeader = header;
fFlags = 0;
fDataLength = length;
fData = TRACKED_NEW UInt8[ length ];
memcpy( fData, data, length );
fValid = true;
}
//// EnsureInternal //////////////////////////////////////////////////////////
// for plugins only
plSoundBuffer::ELoadReturnVal plSoundBuffer::EnsureInternal()
{
if( fData == nil )
{
fData = TRACKED_NEW UInt8[fDataLength ];
if( fData == nil )
return kError;
}
if(!fReader)
fReader = IGetReader(true);
//else
// fReader->Open();
if( fReader == nil )
return kError;
unsigned readLen = fDataLength;
if( !fReader->Read( readLen, fData ) )
{
delete [] fData;
fData = nil;
return kError;
}
if(fReader)
{
fReader->Close();
delete fReader;
fReader = nil;
}
return kSuccess;
}
//// IGrabHeaderInfo /////////////////////////////////////////////////////////
hsBool plSoundBuffer::IGrabHeaderInfo( void )
{
static char path[ 512 ];
if( fFileName != nil )
{
IGetFullPath( path );
// Go grab from the WAV file
if(!fReader)
{
fReader = plAudioFileReader::CreateReader(path, GetReaderSelect(), plAudioFileReader::kStreamNative);
if( fReader == nil || !fReader->IsValid() )
{
delete fReader;
fReader = nil;
return false;
}
}
fHeader = fReader->GetHeader();
fDataLength = fReader->GetDataSize();
RoundDataPos( fDataLength );
fReader->Close();
delete fReader;
fReader = nil;
}
return true;
}
//// IGetReader //////////////////////////////////////////////////////////////
// Makes sure the sound is ready to load without any extra processing (like
// decompression or the like), then opens a reader for it.
// fullpath tells the function whether to append 'sfx' to the path or not (we don't want to do this if were providing the full path)
plAudioFileReader *plSoundBuffer::IGetReader( hsBool fullpath )
{
char path[512];
if(fullpath) IGetFullPath(path);
else
strcpy(path, fFileName);
// Go grab from the WAV file
plAudioFileReader::StreamType type = plAudioFileReader::kStreamWAV;
if (HasFlag(kStreamCompressed))
type = plAudioFileReader::kStreamNative;
plAudioFileReader* reader = plAudioFileReader::CreateReader(path, GetReaderSelect(), type);
if( reader == nil || !reader->IsValid() )
{
delete reader;
return nil;
}
fHeader = reader->GetHeader();
fDataLength = reader->GetDataSize();
RoundDataPos( fDataLength );
return reader;
}