diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/audio/private/snd_wave_data.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'engine/audio/private/snd_wave_data.cpp')
| -rw-r--r-- | engine/audio/private/snd_wave_data.cpp | 2394 |
1 files changed, 2394 insertions, 0 deletions
diff --git a/engine/audio/private/snd_wave_data.cpp b/engine/audio/private/snd_wave_data.cpp new file mode 100644 index 0000000..c03b524 --- /dev/null +++ b/engine/audio/private/snd_wave_data.cpp @@ -0,0 +1,2394 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "audio_pch.h" +#include "datacache/idatacache.h" +#include "utllinkedlist.h" +#include "utldict.h" +#include "filesystem/IQueuedLoader.h" +#include "cdll_int.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern IVEngineClient *engineClient; +extern IFileSystem *g_pFileSystem; +extern IDataCache *g_pDataCache; +extern double realtime; + +// console streaming buffer implementation, appropriate for high latency and low memory +// shift this many buffers through the wave +#define STREAM_BUFFER_COUNT 2 +// duration of audio samples per buffer, 200ms is 2x the worst frame rate (10Hz) +// the engine then has at least 400ms to deliver a new buffer or pop (assuming 2 buffers) +#define STREAM_BUFFER_TIME 0.200f +// force a single buffer when streaming waves smaller than this +#define STREAM_BUFFER_DATASIZE XBOX_DVD_ECC_SIZE + +// PC single buffering implementation +// UNDONE: Allocate this in cache instead? +#define SINGLE_BUFFER_SIZE 16384 + +// Force a small cache for debugging cache issues. +// #define FORCE_SMALL_MEMORY_CACHE_SIZE ( 6 * 1024 * 1024 ) + +#define DEFAULT_WAV_MEMORY_CACHE ( 16 * 1024 * 1024 ) +#define DEFAULT_XBOX_WAV_MEMORY_CACHE ( 16 * 1024 * 1024 ) +#define TF_XBOX_WAV_MEMORY_CACHE ( 24 * 1024 * 1024 ) // Team Fortress uses a larger cache + +// Dev builds will be missing soundcaches and hitch sometimes, we only care if its being properly launched from steam where sound caches should be complete. +ConVar snd_async_spew_blocking( "snd_async_spew_blocking", "1", 0, "Spew message to console any time async sound loading blocks on file i/o. ( 0=Off, 1=With -steam only, 2=Always" ); +ConVar snd_async_spew( "snd_async_spew", "0", 0, "Spew all async sound reads, including success" ); +ConVar snd_async_fullyasync( "snd_async_fullyasync", "0", 0, "All playback is fully async (sound doesn't play until data arrives)." ); +ConVar snd_async_stream_spew( "snd_async_stream_spew", "0", 0, "Spew streaming info ( 0=Off, 1=streams, 2=buffers" ); + +static bool SndAsyncSpewBlocking() +{ + int pref = snd_async_spew_blocking.GetInt(); + return ( pref >= 2 ) || ( pref == 1 && CommandLine()->FindParm( "-steam" ) != 0 ); +} + +#define SndAlignReads() 1 + +void MaybeReportMissingWav( char const *wav ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +struct asyncwaveparams_t +{ + asyncwaveparams_t() : bPrefetch( false ), bCanBeQueued( false ) {} + + FileNameHandle_t hFilename; // handle to sound item name (i.e. not with sound\ prefix) + int datasize; + int seekpos; + int alignment; + bool bPrefetch; + bool bCanBeQueued; +}; + +//----------------------------------------------------------------------------- +// Purpose: Builds a cache of the data bytes for a specific .wav file +//----------------------------------------------------------------------------- +class CAsyncWaveData +{ +public: + explicit CAsyncWaveData(); + + // APIS required by CManagedDataCacheClient + void DestroyResource(); + CAsyncWaveData *GetData(); + unsigned int Size(); + + static void AsyncCallback( const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err ); + static void QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ); + static CAsyncWaveData *CreateResource( const asyncwaveparams_t ¶ms ); + static unsigned int EstimatedSize( const asyncwaveparams_t ¶ms ); + + void OnAsyncCompleted( const FileAsyncRequest_t* asyncFilePtr, int numReadBytes, FSAsyncStatus_t err ); + bool BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count ); + bool BlockingGetDataPointer( void **ppData ); + void SetAsyncPriority( int priority ); + void StartAsyncLoading( const asyncwaveparams_t& params ); + bool GetPostProcessed(); + void SetPostProcessed( bool proc ); + + bool IsCurrentlyLoading(); + char const *GetFileName(); + + // Data +public: + int m_nDataSize; // bytes requested + int m_nReadSize; // bytes actually read + void *m_pvData; // target buffer + byte *m_pAlloc; // memory of buffer (base may not match) + FileAsyncRequest_t m_async; + FSAsyncControl_t m_hAsyncControl; + float m_start; // time at request invocation + float m_arrival; // time at data arrival + FileNameHandle_t m_hFileNameHandle; + int m_nBufferBytes; // size of any pre-allocated target buffer + BufferHandle_t m_hBuffer; // used to dequeue the buffer after lru + unsigned int m_bLoaded : 1; + unsigned int m_bMissing : 1; + unsigned int m_bPostProcessed : 1; +}; + +//----------------------------------------------------------------------------- +// Purpose: C'tor +//----------------------------------------------------------------------------- +CAsyncWaveData::CAsyncWaveData() : + m_nDataSize( 0 ), + m_nReadSize( 0 ), + m_pvData( 0 ), + m_pAlloc( 0 ), + m_hBuffer( INVALID_BUFFER_HANDLE ), + m_nBufferBytes( 0 ), + m_hAsyncControl( NULL ), + m_bLoaded( false ), + m_bMissing( false ), + m_start( 0.0 ), + m_arrival( 0.0 ), + m_bPostProcessed( false ), + m_hFileNameHandle( 0 ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: // APIS required by CDataLRU +//----------------------------------------------------------------------------- +void CAsyncWaveData::DestroyResource() +{ + if ( IsPC() ) + { + if ( m_hAsyncControl ) + { + if ( !m_bLoaded && !m_bMissing ) + { + // NOTE: We CANNOT call AsyncAbort since if the file is actually being read we'll end + // up still getting a callback, but our this ptr (deleted below) will be feeefeee and we'll trash the heap + // pretty bad. So we call AsyncFinish, which will do a blocking read and will definitely succeed + // Block until we are finished + g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); + } + + g_pFileSystem->AsyncRelease( m_hAsyncControl ); + m_hAsyncControl = NULL; + } + } + + if ( IsX360() ) + { + if ( m_hAsyncControl ) + { + if ( !m_bLoaded && !m_bMissing ) + { + // force an abort + int errStatus = g_pFileSystem->AsyncAbort( m_hAsyncControl ); + if ( errStatus != FSASYNC_ERR_UNKNOWNID ) + { + // must wait for abort to finish before deallocating data + g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); + } + } + g_pFileSystem->AsyncRelease( m_hAsyncControl ); + m_hAsyncControl = NULL; + } + if ( m_hBuffer != INVALID_BUFFER_HANDLE ) + { + // hint the manager that this tracked buffer is invalid + wavedatacache->MarkBufferDiscarded( m_hBuffer ); + } + } + + // delete buffers + if ( IsPC() || !IsX360() ) + { + g_pFileSystem->FreeOptimalReadBuffer( m_pAlloc ); + } + else + { + delete [] m_pAlloc; + } + + delete this; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *CAsyncWaveData::GetFileName() +{ + static char sz[MAX_PATH]; + + if ( m_hFileNameHandle ) + { + if ( g_pFileSystem->String( m_hFileNameHandle, sz, sizeof( sz ) ) ) + { + return sz; + } + } + + Assert( 0 ); + return ""; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CAsyncWaveData +//----------------------------------------------------------------------------- +CAsyncWaveData *CAsyncWaveData::GetData() +{ + return this; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : unsigned int +//----------------------------------------------------------------------------- +unsigned int CAsyncWaveData::Size() +{ + int size = sizeof( *this ); + + if ( IsPC() ) + { + size += m_nDataSize; + } + + if ( IsX360() ) + { + // the data size can shrink during streaming near end of file + // need the real contant size of this object's allocations + size += m_nBufferBytes; + } + + return size; +} + +//----------------------------------------------------------------------------- +// Purpose: Static method for CDataLRU +// Input : ¶ms - +// Output : CAsyncWaveData +//----------------------------------------------------------------------------- +CAsyncWaveData *CAsyncWaveData::CreateResource( const asyncwaveparams_t ¶ms ) +{ + CAsyncWaveData *pData = new CAsyncWaveData; + Assert( pData ); + if ( pData ) + { + if ( IsX360() ) + { + // create buffer now for re-use during streaming process + pData->m_nBufferBytes = AlignValue( params.datasize, params.alignment ); + pData->m_pAlloc = new byte[pData->m_nBufferBytes]; + pData->m_pvData = pData->m_pAlloc; + } + pData->StartAsyncLoading( params ); + } + + return pData; +} + +//----------------------------------------------------------------------------- +// Purpose: Static method +// Input : ¶ms - +// Output : static unsigned int +//----------------------------------------------------------------------------- +unsigned int CAsyncWaveData::EstimatedSize( const asyncwaveparams_t ¶ms ) +{ + int size = sizeof( CAsyncWaveData ); + + if ( IsPC() ) + { + size += params.datasize; + } + if ( IsX360() ) + { + // the expected size of this object's allocations + size += AlignValue( params.datasize, params.alignment ); + } + + return size; +} + +//----------------------------------------------------------------------------- +// Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!! +// Input : asyncFilePtr - +// numReadBytes - +// err - +//----------------------------------------------------------------------------- +void CAsyncWaveData::AsyncCallback(const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t err ) +{ + CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( asyncRequest.pContext ); + Assert( pObject ); + if ( pObject ) + { + pObject->OnAsyncCompleted( &asyncRequest, numReadBytes, err ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Static method, called by thread, don't call anything non-threadsafe from handler!!! +//----------------------------------------------------------------------------- +void CAsyncWaveData::QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) +{ + CAsyncWaveData *pObject = reinterpret_cast< CAsyncWaveData * >( pContext ); + Assert( pObject ); + + pObject->OnAsyncCompleted( NULL, nSize, loaderError == LOADERERROR_NONE ? FSASYNC_OK : FSASYNC_ERR_FILEOPEN ); +} + +//----------------------------------------------------------------------------- +// Purpose: NOTE: THIS IS CALLED FROM A THREAD SO YOU CAN'T CALL INTO ANYTHING NON-THREADSAFE +// such as CUtlSymbolTable/CUtlDict (many of the CUtl* are non-thread safe)!!! +// Input : asyncFilePtr - +// numReadBytes - +// err - +//----------------------------------------------------------------------------- +void CAsyncWaveData::OnAsyncCompleted( const FileAsyncRequest_t *asyncFilePtr, int numReadBytes, FSAsyncStatus_t err ) +{ + if ( IsPC() ) + { + // Take hold of pointer (we can just use delete[] across .dlls because we are using a shared memory allocator...) + if ( err == FSASYNC_OK || err == FSASYNC_ERR_READING ) + { + m_arrival = ( float )Plat_FloatTime(); + + // Take over ptr + m_pAlloc = ( byte * )asyncFilePtr->pData; + if ( SndAlignReads() ) + { + m_async.nOffset = ( m_async.nBytes - m_nDataSize ); + m_async.nBytes -= m_async.nOffset; + m_pvData = ((byte *)m_pAlloc) + m_async.nOffset; + m_nReadSize = numReadBytes - m_async.nOffset; + } + else + { + m_pvData = m_pAlloc; + m_nReadSize = numReadBytes; + } + + // Needs to be post-processed + m_bPostProcessed = false; + + // Finished loading + m_bLoaded = true; + } + else if ( err == FSASYNC_ERR_FILEOPEN ) + { + // SEE NOTE IN FUNCTION COMMENT ABOVE!!! + // Tracker 22905, et al. + // Because this api gets called from the other thread, don't spew warning here as it can + // cause a crash in searching CUtlSymbolTables since they use a global var for a LessFunc context!!! + m_bMissing = true; + } + } + + if ( IsX360() ) + { + m_arrival = (float)Plat_FloatTime(); + + // possibly reading more than intended due to alignment restriction + m_nReadSize = numReadBytes; + if ( m_nReadSize > m_nDataSize ) + { + // clamp to expected, extra data is unreliable + m_nReadSize = m_nDataSize; + } + + if ( err != FSASYNC_OK ) + { + // track as any error + m_bMissing = true; + } + + if ( err != FSASYNC_ERR_FILEOPEN ) + { + // some data got loaded + m_bLoaded = true; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *destbuffer - +// destbufsize - +// startoffset - +// count - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWaveData::BlockingCopyData( void *destbuffer, int destbufsize, int startoffset, int count ) +{ + if ( !m_bLoaded ) + { + Assert( m_hAsyncControl ); + // Force it to finish + // It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal + if ( SndAsyncSpewBlocking() ) + { + // Force it to finish + float st = ( float )Plat_FloatTime(); + g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); + float ed = ( float )Plat_FloatTime(); + Warning( "%f BCD: Async I/O Force %s (%8.2f msec / %8.2f msec total)\n", realtime, GetFileName(), 1000.0f * (float)( ed - st ), 1000.0f * (float)( m_arrival - m_start ) ); + } + else + { + g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); + } + } + + // notify on any error + if ( m_bMissing ) + { + // Only warn once + m_bMissing = false; + + char fn[MAX_PATH]; + if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) ) + { + MaybeReportMissingWav( fn ); + } + } + + if ( !m_bLoaded ) + { + return false; + } + else if ( m_arrival != 0 && snd_async_spew.GetBool() ) + { + DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) ); + m_arrival = 0; + } + + // clamp requested to available + if ( count > m_nReadSize ) + { + count = m_nReadSize - startoffset; + } + + if ( count < 0 ) + { + return false; + } + + // Copy data from stream buffer + Q_memcpy( destbuffer, (char *)m_pvData + ( startoffset - m_async.nOffset ), count ); + + g_pFileSystem->AsyncRelease( m_hAsyncControl ); + m_hAsyncControl = NULL; + return true; +} + + +bool CAsyncWaveData::IsCurrentlyLoading() +{ + if ( m_bLoaded ) + return true; + FSAsyncStatus_t status = g_pFileSystem->AsyncStatus( m_hAsyncControl ); + if ( status == FSASYNC_STATUS_INPROGRESS || status == FSASYNC_OK ) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **ppData - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWaveData::BlockingGetDataPointer( void **ppData ) +{ + Assert( ppData ); + if ( !m_bLoaded ) + { + // Force it to finish + // It could finish between the above line and here, but the AsyncFinish call will just have a bogus id, not a big deal + if ( SndAsyncSpewBlocking() ) + { + float st = ( float )Plat_FloatTime(); + g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); + float ed = ( float )Plat_FloatTime(); + Warning( "%f BlockingGetDataPointer: Async I/O Force %s (%8.2f msec / %8.2f msec total )\n", realtime, GetFileName(), 1000.0f * (float)( ed - st ), 1000.0f * (float)( m_arrival - m_start ) ); + } + else + { + g_pFileSystem->AsyncFinish( m_hAsyncControl, true ); + } + } + + // notify on any error + if ( m_bMissing ) + { + // Only warn once + m_bMissing = false; + + char fn[MAX_PATH]; + if ( g_pFileSystem->String( m_hFileNameHandle, fn, sizeof( fn ) ) ) + { + MaybeReportMissingWav( fn ); + } + } + + if ( !m_bLoaded ) + { + return false; + } + else if ( m_arrival != 0 && snd_async_spew.GetBool() ) + { + DevMsg( "%f Async I/O Read successful %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( m_arrival - m_start ) ); + m_arrival = 0; + } + + *ppData = m_pvData; + + g_pFileSystem->AsyncRelease( m_hAsyncControl ); + m_hAsyncControl = NULL; + + return true; +} + +void CAsyncWaveData::SetAsyncPriority( int priority ) +{ + if ( m_async.priority != priority ) + { + m_async.priority = priority; + g_pFileSystem->AsyncSetPriority( m_hAsyncControl, m_async.priority ); + if ( snd_async_spew.GetBool() ) + { + DevMsg( "%f Async I/O Bumped priority for %s (%8.2f msec)\n", realtime, GetFileName(), 1000.0f * (float)( Plat_FloatTime() - m_start ) ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : params - +//----------------------------------------------------------------------------- +void CAsyncWaveData::StartAsyncLoading( const asyncwaveparams_t& params ) +{ + Assert( IsX360() || ( IsPC() && !m_bLoaded ) ); + + // expected to be relative to the sound\ dir + m_hFileNameHandle = params.hFilename; + + // build the real filename + char szFilename[MAX_PATH]; + Q_snprintf( szFilename, sizeof( szFilename ), "sound\\%s", GetFileName() ); + + int nPriority = 1; + if ( params.bPrefetch ) + { + // lower the priority of prefetched sounds, so they don't block immediate sounds from being loaded + nPriority = 0; + } + + if ( !IsX360() ) + { + m_async.pData = NULL; + if ( SndAlignReads() ) + { + m_async.nOffset = 0; + m_async.nBytes = params.seekpos + params.datasize; + } + else + { + m_async.nOffset = params.seekpos; + m_async.nBytes = params.datasize; + } + } + else + { + Assert( params.datasize > 0 ); + + // using explicit allocated buffer on xbox + m_async.pData = m_pvData; + m_async.nOffset = params.seekpos; + m_async.nBytes = AlignValue( params.datasize, params.alignment ); + } + + m_async.pfnCallback = AsyncCallback; // optional completion callback + m_async.pContext = (void *)this; // caller's unique context + m_async.priority = nPriority; // inter list priority, 0=lowest + m_async.flags = IsX360() ? 0 : FSASYNC_FLAGS_ALLOCNOFREE; + m_async.pszPathID = "GAME"; + + m_bLoaded = false; + m_bMissing = false; + m_nDataSize = params.datasize; + m_start = (float)Plat_FloatTime(); + m_arrival = 0; + m_nReadSize = 0; + m_bPostProcessed = false; + + // The async layer creates a copy of this string, ok to send a local reference + m_async.pszFilename = szFilename; + + char szFullName[MAX_PATH]; + if ( IsX360() && ( g_pFileSystem->GetDVDMode() == DVDMODE_STRICT ) ) + { + // all audio is expected be in zips + // resolve to absolute name now, where path can be filtered to just the zips (fast find, no real i/o) + // otherwise the dvd will do a costly seek for each zip miss due to search path fall through + PathTypeQuery_t pathType; + if ( !g_pFileSystem->RelativePathToFullPath( m_async.pszFilename, m_async.pszPathID, szFullName, sizeof( szFullName ), FILTER_CULLNONPACK, &pathType ) ) + { + // not found, do callback now to handle error + m_async.pfnCallback( m_async, 0, FSASYNC_ERR_FILEOPEN ); + return; + } + m_async.pszFilename = szFullName; + } + + if ( IsX360() && params.bCanBeQueued ) + { + // queued loader takes over + LoaderJob_t loaderJob; + loaderJob.m_pFilename = m_async.pszFilename; + loaderJob.m_pPathID = m_async.pszPathID; + loaderJob.m_pCallback = QueuedLoaderCallback; + loaderJob.m_pContext = (void *)this; + loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + loaderJob.m_pTargetData = m_async.pData; + loaderJob.m_nBytesToRead = m_async.nBytes; + loaderJob.m_nStartOffset = m_async.nOffset; + g_pQueuedLoader->AddJob( &loaderJob ); + return; + } + + MEM_ALLOC_CREDIT(); + + // Commence async I/O + Assert( !m_hAsyncControl ); + g_pFileSystem->AsyncRead( m_async, &m_hAsyncControl ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWaveData::GetPostProcessed() +{ + return m_bPostProcessed; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : proc - +//----------------------------------------------------------------------------- +void CAsyncWaveData::SetPostProcessed( bool proc ) +{ + m_bPostProcessed = proc; +} + +//----------------------------------------------------------------------------- +// Purpose: Implements a cache of .wav / .mp3 data based on filename +//----------------------------------------------------------------------------- +class CAsyncWavDataCache : public IAsyncWavDataCache, + public CManagedDataCacheClient<CAsyncWaveData, asyncwaveparams_t> +{ +public: + CAsyncWavDataCache(); + ~CAsyncWavDataCache() {} + + virtual bool Init( unsigned int memSize ); + virtual void Shutdown(); + + // implementation that treats file as monolithic + virtual memhandle_t AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch = false ); + virtual void PrefetchCache( char const *filename, int datasize, int startpos ); + virtual bool CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ); + virtual bool CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ); + virtual void SetPostProcessed( memhandle_t handle, bool proc ); + virtual void Unload( memhandle_t handle ); + virtual bool GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed ); + virtual bool IsDataLoadCompleted( memhandle_t handle, bool *pIsValid ); + virtual void RestartDataLoad( memhandle_t* handle, char const *filename, int datasize, int startpos ); + virtual bool IsDataLoadInProgress( memhandle_t handle ); + + // Xbox: alternate multi-buffer streaming implementation + virtual StreamHandle_t OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags ); + virtual void CloseStreamedLoad( StreamHandle_t hStream ); + virtual int CopyStreamedDataIntoMemory( StreamHandle_t hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy ); + virtual bool IsStreamedDataReady( StreamHandle_t hStream ); + virtual void MarkBufferDiscarded( BufferHandle_t hBuffer ); + virtual void *GetStreamedDataPointer( StreamHandle_t hStream, bool bSync ); + + virtual void Flush(); + virtual void OnMixBegin(); + virtual void OnMixEnd(); + + void QueueUnlock( const memhandle_t &handle ); + void SpewMemoryUsage( int level ); + + // Cache helpers + bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ); + +private: + void Clear(); + + struct CacheEntry_t + { + CacheEntry_t() : + name( 0 ), + handle( 0 ) + { + } + FileNameHandle_t name; + memhandle_t handle; + }; + + // tags the signature of a buffer inside a rb tree for faster than linear find + struct BufferEntry_t + { + FileNameHandle_t m_hName; + memhandle_t m_hWaveData; + int m_StartPos; + bool m_bCanBeShared; + }; + + static bool BufferHandleLessFunc( const BufferEntry_t& lhs, const BufferEntry_t& rhs ) + { + if ( lhs.m_hName != rhs.m_hName ) + { + return lhs.m_hName < rhs.m_hName; + } + + if ( lhs.m_StartPos != rhs.m_StartPos ) + { + return lhs.m_StartPos < rhs.m_StartPos; + } + + return lhs.m_bCanBeShared < rhs.m_bCanBeShared; + } + + CUtlRBTree< BufferEntry_t, BufferHandle_t > m_BufferList; + + // encapsulates (n) buffers for a streamed wave object + struct StreamedEntry_t + { + FileNameHandle_t m_hName; + memhandle_t m_hWaveData[STREAM_BUFFER_COUNT]; + int m_Front; // buffer index, forever incrementing + int m_NextStartPos; // predicted offset if mixing linearly + int m_DataSize; // length of the data set in bytes + int m_DataStart; // file offset where data set starts + int m_LoopStart; // offset in data set where loop starts + int m_BufferSize; // size of the buffer in bytes + int m_numBuffers; // number of buffers (1 or 2) to march through + int m_SectorSize; // size of sector on stream device + bool m_bSinglePlay; // hint to keep same buffers + }; + CUtlLinkedList< StreamedEntry_t, StreamHandle_t > m_StreamedHandles; + + static bool CacheHandleLessFunc( const CacheEntry_t& lhs, const CacheEntry_t& rhs ) + { + return lhs.name < rhs.name; + } + CUtlRBTree< CacheEntry_t, int > m_CacheHandles; + + memhandle_t FindOrCreateBuffer( asyncwaveparams_t ¶ms, bool bFind ); + bool m_bInitialized; + bool m_bQueueCacheUnlocks; + CUtlVector<memhandle_t> m_unlockQueue; +}; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CAsyncWavDataCache::CAsyncWavDataCache() : + m_CacheHandles( 0, 0, CacheHandleLessFunc ), + m_BufferList( 0, 0, BufferHandleLessFunc ), + m_bInitialized( false ), + m_bQueueCacheUnlocks( false ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::Init( unsigned int memSize ) +{ + if ( m_bInitialized ) + return true; + + if ( IsX360() ) + { + const char *pGame = engineClient->GetGameDirectory(); + if ( !Q_stricmp( Q_UnqualifiedFileName( pGame ), "tf" ) ) + { + memSize = TF_XBOX_WAV_MEMORY_CACHE; + } + else + { + memSize = DEFAULT_XBOX_WAV_MEMORY_CACHE; + } + } + else + { + if ( memSize < DEFAULT_WAV_MEMORY_CACHE ) + { + memSize = DEFAULT_WAV_MEMORY_CACHE; + } + } + +#if FORCE_SMALL_MEMORY_CACHE_SIZE + memSize = FORCE_SMALL_MEMORY_CACHE_SIZE; + Msg( "WARNING CAsyncWavDataCache::Init() forcing small memory cache size: %u\n", memSize ); +#endif + + CCacheClientBaseClass::Init( g_pDataCache, "WaveData", memSize ); + + m_bInitialized = true; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::Shutdown() +{ + if ( !m_bInitialized ) + { + return; + } + + Clear(); + + CCacheClientBaseClass::Shutdown(); + + m_bInitialized = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates initial cache object if it doesn't already exist, starts async loading the actual data +// in any case. +// Input : *filename - +// datasize - +// startpos - +// Output : memhandle_t +//----------------------------------------------------------------------------- +memhandle_t CAsyncWavDataCache::AsyncLoadCache( char const *filename, int datasize, int startpos, bool bIsPrefetch ) +{ + VPROF( "CAsyncWavDataCache::AsyncLoadCache" ); + + FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); + + CacheEntry_t search; + search.name = fnh; + search.handle = 0; + + // find or create the handle + int idx = m_CacheHandles.Find( search ); + if ( idx == m_CacheHandles.InvalidIndex() ) + { + idx = m_CacheHandles.Insert( search ); + Assert( idx != m_CacheHandles.InvalidIndex() ); + } + + CacheEntry_t &entry = m_CacheHandles[idx]; + + // Try and pull it into cache + CAsyncWaveData *data = CacheGet( entry.handle ); + if ( !data ) + { + // Try and reload it + asyncwaveparams_t params; + params.hFilename = fnh; + params.datasize = datasize; + params.seekpos = startpos; + params.bPrefetch = bIsPrefetch; + entry.handle = CacheCreate( params ); + } + + return entry.handle; +} + + +//----------------------------------------------------------------------------- +// Purpose: Reclaim a buffer. A reclaimed resident buffer is ready for play. +//----------------------------------------------------------------------------- +memhandle_t CAsyncWavDataCache::FindOrCreateBuffer( asyncwaveparams_t ¶ms, bool bFind ) +{ + CAsyncWaveData *pWaveData; + BufferEntry_t search; + BufferHandle_t hBuffer; + + search.m_hName = params.hFilename; + search.m_StartPos = params.seekpos; + search.m_bCanBeShared = bFind; + search.m_hWaveData = 0; + + if ( bFind ) + { + // look for an existing buffer that matches exactly (same file, offset, and share) + int iBuffer = m_BufferList.Find( search ); + if ( iBuffer != m_BufferList.InvalidIndex() ) + { + // found + search.m_hWaveData = m_BufferList[iBuffer].m_hWaveData; + if ( snd_async_stream_spew.GetInt() >= 2 ) + { + char tempBuff[MAX_PATH]; + g_pFileSystem->String( params.hFilename, tempBuff, sizeof( tempBuff ) ); + Msg( "Found Buffer: %s, offset: %d\n", tempBuff, params.seekpos ); + } + } + } + + // each resource buffer stays locked (valid) while in use + // a buffering stream is not subject to lru and can rely on it's buffers + // a buffering stream may obsolete it's buffers by reducing the lock count, allowing for lru + pWaveData = CacheLock( search.m_hWaveData ); + if ( !pWaveData ) + { + // not in cache, create and lock + // not found, create buffer and fill with data + search.m_hWaveData = CacheCreate( params, DCAF_LOCK ); + + // add the buffer to our managed list + hBuffer = m_BufferList.Insert( search ); + Assert( hBuffer != m_BufferList.InvalidIndex() ); + + // store the handle into our managed list + // used during a lru discard as a means to keep the list in-sync + pWaveData = CacheGet( search.m_hWaveData ); + pWaveData->m_hBuffer = hBuffer; + } + else + { + // still in cache + // same as requesting it and having it arrive instantly + pWaveData->m_start = pWaveData->m_arrival = (float)Plat_FloatTime(); + } + + return search.m_hWaveData; +} + + +//----------------------------------------------------------------------------- +// Purpose: Load data asynchronously via multi-buffers, returns specialized handle +//----------------------------------------------------------------------------- +StreamHandle_t CAsyncWavDataCache::OpenStreamedLoad( char const *pFileName, int dataSize, int dataStart, int startPos, int loopPos, int bufferSize, int numBuffers, streamFlags_t flags ) +{ + VPROF( "CAsyncWavDataCache::OpenStreamedLoad" ); + + StreamedEntry_t streamedEntry; + StreamHandle_t hStream; + asyncwaveparams_t params; + int i; + + Assert( numBuffers > 0 && numBuffers <= STREAM_BUFFER_COUNT ); + + // queued load mandates one buffer + Assert( !( flags & STREAMED_QUEUEDLOAD ) || numBuffers == 1 ); + + streamedEntry.m_hName = g_pFileSystem->FindOrAddFileName( pFileName ); + streamedEntry.m_Front = 0; + streamedEntry.m_DataSize = dataSize; + streamedEntry.m_DataStart = dataStart; + streamedEntry.m_NextStartPos = startPos + numBuffers * bufferSize; + streamedEntry.m_LoopStart = loopPos; + streamedEntry.m_BufferSize = bufferSize; + streamedEntry.m_numBuffers = numBuffers; + streamedEntry.m_bSinglePlay = ( flags & STREAMED_SINGLEPLAY ) != 0; + streamedEntry.m_SectorSize = ( IsX360() && ( flags & STREAMED_FROMDVD ) ) ? XBOX_DVD_SECTORSIZE : 1; + + // single play streams expect to uniquely own and thus recycle their buffers though the data + // single play streams are guaranteed that their buffers are private and cannot be shared + // a non-single play stream wants persisting buffers and attempts to reclaim a matching buffer + bool bFindBuffer = ( streamedEntry.m_bSinglePlay == false ); + + // initial load populates buffers + // mixing starts after front buffer viable + // buffer rotation occurs after front buffer consumed + // there should be no blocking + params.hFilename = streamedEntry.m_hName; + params.datasize = bufferSize; + params.alignment = streamedEntry.m_SectorSize; + params.bCanBeQueued = ( flags & STREAMED_QUEUEDLOAD ) != 0; + for ( i=0; i<numBuffers; ++i ) + { + params.seekpos = dataStart + startPos + i * bufferSize; + streamedEntry.m_hWaveData[i] = FindOrCreateBuffer( params, bFindBuffer ); + } + + // get a unique handle for each stream request + hStream = m_StreamedHandles.AddToTail( streamedEntry ); + Assert( hStream != m_StreamedHandles.InvalidIndex() ); + + return hStream; +} + + +//----------------------------------------------------------------------------- +// Purpose: Cleanup a streamed load's resources. +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::CloseStreamedLoad( StreamHandle_t hStream ) +{ + VPROF( "CAsyncWavDataCache::CloseStreamedLoad" ); + + if ( hStream == INVALID_STREAM_HANDLE ) + { + return; + } + + int lockCount; + StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; + for ( int i=0; i<streamedEntry.m_numBuffers; ++i ) + { + // multiple streams could be using the same buffer, keeping the lock count nonzero + lockCount = GetCacheSection()->GetLockCount( streamedEntry.m_hWaveData[i] ); + Assert( lockCount >= 1 ); + if ( lockCount > 0 ) + { + lockCount = CacheUnlock( streamedEntry.m_hWaveData[i] ); + } + + if ( streamedEntry.m_bSinglePlay ) + { + // a buffering single play stream has no reason to reuse its own buffers and destroys them + Assert( lockCount == 0 ); + CacheRemove( streamedEntry.m_hWaveData[i] ); + } + } + + m_StreamedHandles.Remove( hStream ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// datasize - +// startpos - +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::PrefetchCache( char const *filename, int datasize, int startpos ) +{ + // Just do an async load, but don't get cache handle + AsyncLoadCache( filename, datasize, startpos, true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *filename - +// datasize - +// startpos - +// *buffer - +// bufsize - +// copystartpos - +// bytestocopy - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::CopyDataIntoMemory( char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ) +{ + VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" ); + + bool bret = false; + + // Add to caching system + AsyncLoadCache( filename, datasize, startpos ); + + FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); + + CacheEntry_t search; + search.name = fnh; + search.handle = 0; + + // Now look it up, it should be in the system + int idx = m_CacheHandles.Find( search ); + if ( idx == m_CacheHandles.InvalidIndex() ) + { + Assert( 0 ); + return bret; + } + + // Now see if the handle has been paged out... + return CopyDataIntoMemory( m_CacheHandles[ idx ].handle, filename, datasize, startpos, buffer, bufsize, copystartpos, bytestocopy, pbPostProcessed ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// *filename - +// datasize - +// startpos - +// *buffer - +// bufsize - +// copystartpos - +// bytestocopy - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::CopyDataIntoMemory( memhandle_t& handle, char const *filename, int datasize, int startpos, void *buffer, int bufsize, int copystartpos, int bytestocopy, bool *pbPostProcessed ) +{ + VPROF( "CAsyncWavDataCache::CopyDataIntoMemory" ); + + *pbPostProcessed = false; + + bool bret = false; + + CAsyncWaveData *data = CacheLock( handle ); + if ( !data ) + { + FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); + + CacheEntry_t search; + search.name = fnh; + search.handle = 0; + + // Now look it up, it should be in the system + int idx = m_CacheHandles.Find( search ); + if ( idx == m_CacheHandles.InvalidIndex() ) + { + Assert( 0 ); + return false; + } + + // Try and reload it + asyncwaveparams_t params; + params.hFilename = fnh; + params.datasize = datasize; + params.seekpos = startpos; + + handle = m_CacheHandles[ idx ].handle = CacheCreate( params ); + data = CacheLock( handle ); + if ( !data ) + { + return bret; + } + } + + // Cache entry exists, but if filesize == 0 then the file itself wasn't on disk... + if ( data->m_nDataSize != 0 ) + { + bret = data->BlockingCopyData( buffer, bufsize, copystartpos, bytestocopy ); + } + + *pbPostProcessed = data->GetPostProcessed(); + + // Release lock + CacheUnlock( handle ); + return bret; +} + + +//----------------------------------------------------------------------------- +// Purpose: Copy from streaming buffers into target memory, never blocks. +//----------------------------------------------------------------------------- +int CAsyncWavDataCache::CopyStreamedDataIntoMemory( int hStream, void *pBuffer, int bufferSize, int copyStartPos, int bytesToCopy ) +{ + VPROF( "CAsyncWavDataCache::CopyStreamedDataIntoMemory" ); + + int actualCopied; + int count; + int i; + int which; + CAsyncWaveData *pWaveData[STREAM_BUFFER_COUNT]; + CAsyncWaveData *pFront; + asyncwaveparams_t params; + int nextStartPos; + int bufferPos; + bool bEndOfFile; + int index; + bool bWaiting; + bool bCompleted; + StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; + + if ( copyStartPos >= streamedEntry.m_DataStart + streamedEntry.m_DataSize ) + { + // at or past end of file + return 0; + } + + for ( i=0; i<streamedEntry.m_numBuffers; ++i ) + { + pWaveData[i] = CacheGetNoTouch( streamedEntry.m_hWaveData[i] ); + Assert( pWaveData[i] ); + } + + // drive the buffering + index = streamedEntry.m_Front; + bEndOfFile = 0; + actualCopied = 0; + bWaiting = false; + while ( 1 ) + { + // try to satisfy from the front + pFront = pWaveData[index % streamedEntry.m_numBuffers]; + bufferPos = copyStartPos - pFront->m_async.nOffset; + + // cache atomic async completion signal off to avoid coherency issues + bCompleted = pFront->m_bLoaded || pFront->m_bMissing; + + if ( snd_async_stream_spew.GetInt() >= 1 ) + { + // interval is the audio block clock rate, the block must be available within this interval + // a faster audio rate or smaller block size implies a smaller interval + // latency is the actual block delivery time + // latency must not exceed the delivery interval or stariving occurs and audio pops + float nowTime = Plat_FloatTime(); + int interval = (int)(1000.0f*(nowTime-pFront->m_start)); + int latency; + if ( bCompleted && pFront->m_bLoaded ) + { + latency = (int)(1000.0f*(pFront->m_arrival-pFront->m_start)); + } + else + { + // buffer has not arrived yet + latency = -1; + } + DevMsg( "Stream:%2d interval:%5dms latency:%5dms offset:%d length:%d (%s)\n", hStream, interval, latency, pFront->m_async.nOffset, pFront->m_nReadSize, pFront->GetFileName() ); + } + + if ( bCompleted && pFront->m_hAsyncControl && ( pFront->m_bLoaded || pFront->m_bMissing) ) + { + g_pFileSystem->AsyncRelease( pFront->m_hAsyncControl ); + pFront->m_hAsyncControl = NULL; + } + + if ( bCompleted && pFront->m_bLoaded ) + { + if ( bufferPos >= 0 && bufferPos < pFront->m_nReadSize ) + { + count = bytesToCopy; + if ( bufferPos + bytesToCopy > pFront->m_nReadSize ) + { + // clamp requested to actual available + count = pFront->m_nReadSize - bufferPos; + } + if ( bufferPos + count > bufferSize ) + { + // clamp requested to caller's buffer dimension + count = bufferSize - bufferPos; + } + + Q_memcpy( pBuffer, (char *)pFront->m_pvData + bufferPos, count ); + + // advance past consumed bytes + actualCopied += count; + copyStartPos += count; + bufferPos += count; + } + } + else if ( bCompleted && pFront->m_bMissing ) + { + // notify on any error + MaybeReportMissingWav( pFront->GetFileName() ); + break; + } + else + { + // data not available + bWaiting = true; + break; + } + + // cycle past obsolete or consumed buffers + if ( bufferPos < 0 || bufferPos >= pFront->m_nReadSize ) + { + // move to next buffer + index++; + if ( index - streamedEntry.m_Front >= streamedEntry.m_numBuffers ) + { + // out of buffers + break; + } + } + + if ( actualCopied == bytesToCopy ) + { + // satisfied request + break; + } + } + + if ( streamedEntry.m_numBuffers > 1 ) + { + // restart consumed buffers + while ( streamedEntry.m_Front < index ) + { + if ( !actualCopied && !bWaiting ) + { + // couldn't return any data because the buffers aren't in the right location + // oh no! caller must be skipping + // due to latency the next buffer position has to start one full buffer ahead of the caller's desired read location + // hopefully only 1 buffer will stutter + nextStartPos = copyStartPos - streamedEntry.m_DataStart + streamedEntry.m_BufferSize; + + // advance past, ready for next possible iteration + copyStartPos += streamedEntry.m_BufferSize; + } + else + { + // get the next forecasted read location + nextStartPos = streamedEntry.m_NextStartPos; + } + + if ( nextStartPos >= streamedEntry.m_DataSize ) + { + // next buffer is at or past end of file + if ( streamedEntry.m_LoopStart >= 0 ) + { + // wrap back around to loop position + nextStartPos = streamedEntry.m_LoopStart; + } + else + { + // advance past consumed buffer + streamedEntry.m_Front++; + + // start no further buffers + break; + } + } + + // still valid data left to read + // snap the buffer position to required alignment + nextStartPos = streamedEntry.m_SectorSize * (nextStartPos/streamedEntry.m_SectorSize); + + // start loading back buffer at future location + params.hFilename = streamedEntry.m_hName; + params.seekpos = streamedEntry.m_DataStart + nextStartPos; + params.datasize = streamedEntry.m_DataSize - nextStartPos; + params.alignment = streamedEntry.m_SectorSize; + if ( params.datasize > streamedEntry.m_BufferSize ) + { + // clamp to buffer size + params.datasize = streamedEntry.m_BufferSize; + } + + // save next start position + streamedEntry.m_NextStartPos = nextStartPos + params.datasize; + + which = streamedEntry.m_Front % streamedEntry.m_numBuffers; + if ( streamedEntry.m_bSinglePlay ) + { + // a single play wave has no reason to persist its buffers into the lru + // reuse buffer and restart until finished + pWaveData[which]->StartAsyncLoading( params ); + } + else + { + // release obsolete buffer to lru management + CacheUnlock( streamedEntry.m_hWaveData[which] ); + // reclaim or create/load the desired buffer + streamedEntry.m_hWaveData[which] = FindOrCreateBuffer( params, true ); + } + + streamedEntry.m_Front++; + } + + if ( bWaiting ) + { + // oh no! data needed is not yet available in front buffer + // caller requesting data faster than can be provided or caller skipped + // can only return what has been copied thus far (could be 0) + return actualCopied; + } + } + + return actualCopied; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the front buffer, optionally block. +// Intended for user of a single buffer stream. +//----------------------------------------------------------------------------- +void *CAsyncWavDataCache::GetStreamedDataPointer( StreamHandle_t hStream, bool bSync ) +{ + void *pData; + CAsyncWaveData *pFront; + int index; + StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; + + index = streamedEntry.m_Front % streamedEntry.m_numBuffers; + pFront = CacheGetNoTouch( streamedEntry.m_hWaveData[index] ); + Assert( pFront ); + if ( !pFront ) + { + // shouldn't happen + return NULL; + } + + if ( !pFront->m_bMissing && pFront->m_bLoaded ) + { + return pFront->m_pvData; + } + + if ( bSync && pFront->BlockingGetDataPointer( &pData ) ) + { + return pData; + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: The front buffer must be valid +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::IsStreamedDataReady( int hStream ) +{ + VPROF( "CAsyncWavDataCache::IsStreamedDataReady" ); + + if ( hStream == INVALID_STREAM_HANDLE ) + { + return false; + } + + StreamedEntry_t &streamedEntry = m_StreamedHandles[hStream]; + + if ( streamedEntry.m_Front ) + { + // already streaming, the buffers better be arriving as expected + return true; + } + + // only the first front buffer must be present + CAsyncWaveData *pFront = CacheGetNoTouch( streamedEntry.m_hWaveData[0] ); + Assert( pFront ); + if ( !pFront ) + { + // shouldn't happen + // let the caller think data is ready, so stream can shutdown + return true; + } + + // regardless of any errors + // errors handled during data fetch + return pFront->m_bLoaded || pFront->m_bMissing; +} + + +//----------------------------------------------------------------------------- +// Purpose: Dequeue the buffer entry (backdoor for list management) +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::MarkBufferDiscarded( BufferHandle_t hBuffer ) +{ + m_BufferList.RemoveAt( hBuffer ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// proc - +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::SetPostProcessed( memhandle_t handle, bool proc ) +{ + CAsyncWaveData *data = CacheGet( handle ); + if ( data ) + { + data->SetPostProcessed( proc ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::Unload( memhandle_t handle ) +{ + // Don't actually unload, just mark it as stale + if ( GetCacheSection() ) + { + GetCacheSection()->Age( handle ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// *filename - +// datasize - +// startpos - +// **pData - +// copystartpos - +// *pbPostProcessed - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::GetDataPointer( memhandle_t& handle, char const *filename, int datasize, int startpos, void **pData, int copystartpos, bool *pbPostProcessed ) +{ + VPROF( "CAsyncWavDataCache::GetDataPointer" ); + + Assert( pbPostProcessed ); + Assert( pData ); + + *pbPostProcessed = false; + + bool bret = false; + *pData = NULL; + + CAsyncWaveData *data = CacheLock( handle ); + if ( !data ) + { + FileNameHandle_t fnh = g_pFileSystem->FindOrAddFileName( filename ); + + CacheEntry_t search; + search.name = fnh; + search.handle = 0; + + int idx = m_CacheHandles.Find( search ); + if ( idx == m_CacheHandles.InvalidIndex() ) + { + Assert( 0 ); + return bret; + } + + // Try and reload it + asyncwaveparams_t params; + params.hFilename = fnh; + params.datasize = datasize; + params.seekpos = startpos; + + handle = m_CacheHandles[ idx ].handle = CacheCreate( params ); + data = CacheLock( handle ); + if ( !data ) + { + return bret; + } + } + + // Cache entry exists, but if filesize == 0 then the file itself wasn't on disk... + if ( data->m_nDataSize != 0 ) + { + if ( datasize != data->m_nDataSize ) + { + // We've had issues where we are called with datasize larger than what we read on disk. + // Ie: datasize is 277,180, data->m_nDataSize is 263,168 + // This can happen due to a corrupted audio cache, but it's more likely that somehow + // we wound up reading the cache data from one language and the file from another. + DevMsg( "Cached datasize != sound datasize %d - %d.\n", datasize, data->m_nDataSize ); +#ifdef STAGING_ONLY + // Adding a STAGING_ONLY debugger break to try and help track this down. Hopefully we'll + // get this crash internally with full debug information instead of just minidump files. + DebuggerBreak(); +#endif + } + else if ( copystartpos < data->m_nDataSize ) + { + if ( data->BlockingGetDataPointer( pData ) ) + { + *pData = (char *)*pData + copystartpos; + bret = true; + } + } + } + + *pbPostProcessed = data->GetPostProcessed(); + + // Release lock at the end of mixing + QueueUnlock( handle ); + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : handle - +// *filename - +// datasize - +// startpos - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::IsDataLoadCompleted( memhandle_t handle, bool *pIsValid ) +{ + VPROF( "CAsyncWavDataCache::IsDataLoadCompleted" ); + + CAsyncWaveData *data = CacheGet( handle ); + if ( !data ) + { + *pIsValid = false; + return false; + } + *pIsValid = true; + // bump the priority + data->SetAsyncPriority( 1 ); + + return data->m_bLoaded; +} + + +void CAsyncWavDataCache::RestartDataLoad( memhandle_t *pHandle, const char *pFilename, int dataSize, int startpos ) +{ + CAsyncWaveData *data = CacheGet( *pHandle ); + if ( !data ) + { + *pHandle = AsyncLoadCache( pFilename, dataSize, startpos ); + } +} + +bool CAsyncWavDataCache::IsDataLoadInProgress( memhandle_t handle ) +{ + CAsyncWaveData *data = CacheGet( handle ); + if ( data ) + { + return data->IsCurrentlyLoading(); + } + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::Flush() +{ + GetCacheSection()->Flush(); + SpewMemoryUsage( 0 ); +} + +void CAsyncWavDataCache::QueueUnlock( const memhandle_t &handle ) +{ + // not queuing right now, just unlock + if ( !m_bQueueCacheUnlocks ) + { + CacheUnlock( handle ); + return; + } + // queue to unlock at the end of mixing + m_unlockQueue.AddToTail( handle ); +} + +void CAsyncWavDataCache::OnMixBegin() +{ + Assert( !m_bQueueCacheUnlocks ); + m_bQueueCacheUnlocks = true; + Assert( m_unlockQueue.Count() == 0 ); +} + +void CAsyncWavDataCache::OnMixEnd() +{ + m_bQueueCacheUnlocks = false; + // flush the unlock queue + for ( int i = 0; i < m_unlockQueue.Count(); i++ ) + { + CacheUnlock( m_unlockQueue[i] ); + } + m_unlockQueue.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CAsyncWavDataCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen ) +{ + CAsyncWaveData *pWaveData = (CAsyncWaveData *)pItem; + Q_strncpy( pDest, pWaveData->GetFileName(), nMaxLen ); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Spew a cache summary to the console +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::SpewMemoryUsage( int level ) +{ + DataCacheStatus_t status; + DataCacheLimits_t limits; + GetCacheSection()->GetStatus( &status, &limits ); + int bytesUsed = status.nBytes; + int bytesTotal = limits.nMaxBytes; + + if ( IsPC() ) + { + float percent = 100.0f * (float)bytesUsed / (float)bytesTotal; + + Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent ); + + if ( level >= 1 ) + { + for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) ) + { + char name[MAX_PATH]; + if ( !g_pFileSystem->String( m_CacheHandles[ i ].name, name, sizeof( name ) ) ) + { + Assert( 0 ); + continue; + } + memhandle_t &handle = m_CacheHandles[ i ].handle; + CAsyncWaveData *data = CacheGetNoTouch( handle ); + if ( data ) + { + Msg( "\t%16.16s : %s\n", Q_pretifymem(data->Size()),name); + } + else + { + Msg( "\t%16.16s : %s\n", "not resident",name); + } + } + Msg( "CAsyncWavDataCache: %i .wavs total %s, %.2f %% of capacity\n", m_CacheHandles.Count(), Q_pretifymem( bytesUsed, 2 ), percent ); + } + } + + if ( IsX360() ) + { + CAsyncWaveData *pData; + BufferEntry_t *pBuffer; + BufferHandle_t h; + float percent; + int lockCount; + + if ( bytesTotal <= 0 ) + { + // unbounded, indeterminate + percent = 0; + bytesTotal = 0; + } + else + { + percent = 100.0f*(float)bytesUsed/(float)bytesTotal; + } + + if ( level >= 1 ) + { + // detail buffers + ConMsg( "Streaming Buffer List:\n" ); + for ( h = m_BufferList.FirstInorder(); h != m_BufferList.InvalidIndex(); h = m_BufferList.NextInorder( h ) ) + { + pBuffer = &m_BufferList[h]; + pData = CacheGetNoTouch( pBuffer->m_hWaveData ); + lockCount = GetCacheSection()->GetLockCount( pBuffer->m_hWaveData ); + + CacheLockMutex(); + if ( pData ) + { + ConMsg( "Start:%7d Length:%7d Lock:%3d %s\n", pData->m_async.nOffset, pData->m_nDataSize, lockCount, pData->GetFileName() ); + } + CacheUnlockMutex(); + } + } + + ConMsg( "CAsyncWavDataCache: %.2f MB used of %.2f MB, %.2f%% of capacity", (float)bytesUsed/(1024.0f*1024.0f), (float)bytesTotal/(1024.0f*1024.0f), percent ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CAsyncWavDataCache::Clear() +{ + for ( int i = m_CacheHandles.FirstInorder(); m_CacheHandles.IsValidIndex(i); i = m_CacheHandles.NextInorder(i) ) + { + CacheEntry_t& dat = m_CacheHandles[i]; + CacheRemove( dat.handle ); + } + m_CacheHandles.RemoveAll(); + + FOR_EACH_LL( m_StreamedHandles, i ) + { + StreamedEntry_t &dat = m_StreamedHandles[i]; + for ( int j=0; j<dat.m_numBuffers; ++j ) + { + GetCacheSection()->BreakLock( dat.m_hWaveData[j] ); + CacheRemove( dat.m_hWaveData[j] ); + } + } + m_StreamedHandles.RemoveAll(); + m_BufferList.RemoveAll(); +} + + +static CAsyncWavDataCache g_AsyncWaveDataCache; +IAsyncWavDataCache *wavedatacache = &g_AsyncWaveDataCache; + +CON_COMMAND( snd_async_flush, "Flush all unlocked async audio data" ) +{ + g_AsyncWaveDataCache.Flush(); +} + +CON_COMMAND( snd_async_showmem, "Show async memory stats" ) +{ + g_AsyncWaveDataCache.SpewMemoryUsage( 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pFileName - +// dataOffset - +// dataSize - +//----------------------------------------------------------------------------- +void PrefetchDataStream( const char *pFileName, int dataOffset, int dataSize ) +{ + if ( IsX360() ) + { + // Xbox streaming buffer implementation does not support this "hinting" + return; + } + + wavedatacache->PrefetchCache( pFileName, dataSize, dataOffset ); +} + +//----------------------------------------------------------------------------- +// Purpose: This is an instance of a stream. +// This contains the file handle and streaming buffer +// The mixer doesn't know the file is streaming. The IWaveData +// abstracts the data access. The mixer abstracts data encoding/format +//----------------------------------------------------------------------------- +class CWaveDataStreamAsync : public IWaveData +{ +public: + CWaveDataStreamAsync( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int fileStart, int fileSize, CSfxTable *sfx, int startOffset ); + ~CWaveDataStreamAsync( void ); + + // return the source pointer (mixer needs this to determine some things like sampling rate) + CAudioSource &Source( void ) { return m_source; } + + // Read data from the source - this is the primary function of a IWaveData subclass + // Get the data from the buffer (or reload from disk) + virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ); + bool IsValid() { return m_bValid; } + virtual bool IsReadyToMix(); + +private: + CWaveDataStreamAsync( const CWaveDataStreamAsync & ); + + //----------------------------------------------------------------------------- + // Purpose: + // Output : byte + //----------------------------------------------------------------------------- + inline byte *GetCachedDataPointer() + { + VPROF( "CWaveDataStreamAsync::GetCachedDataPointer" ); + + CAudioSourceCachedInfo *info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); + if ( !info ) + { + Assert( !"CAudioSourceWave::GetCachedDataPointer info == NULL" ); + return NULL; + } + + return (byte *)info->CachedData(); + } + + char const *GetFileName(); + CAudioSource &m_source; // wave source + IWaveStreamSource *m_pStreamSource; // streaming + int m_sampleSize; // size of a sample in bytes + int m_waveSize; // total number of samples in the file + + int m_bufferSize; // size of buffer in samples + char *m_buffer; + int m_sampleIndex; + int m_bufferCount; + int m_dataStart; + int m_dataSize; + + memhandle_t m_hCache; + StreamHandle_t m_hStream; + FileNameHandle_t m_hFileName; + + bool m_bValid; + CAudioSourceCachedInfoHandle_t m_AudioCacheHandle; + int m_nCachedDataSize; + CSfxTable *m_pSfx; +}; + +CWaveDataStreamAsync::CWaveDataStreamAsync + ( + CAudioSource &source, + IWaveStreamSource *pStreamSource, + const char *pFileName, + int fileStart, + int fileSize, + CSfxTable *sfx, + int startOffset + ) : + m_source( source ), + m_dataStart( fileStart ), + m_dataSize( fileSize ), + m_pStreamSource( pStreamSource ), + m_bValid( false ), + m_hCache( 0 ), + m_hStream( INVALID_STREAM_HANDLE ), + m_hFileName( 0 ), + m_pSfx( sfx ) +{ + m_hFileName = g_pFileSystem->FindOrAddFileName( pFileName ); + + // nothing in the buffer yet + m_sampleIndex = 0; + m_bufferCount = 0; + + if ( IsPC() ) + { + m_buffer = new char[SINGLE_BUFFER_SIZE]; + Q_memset( m_buffer, 0, SINGLE_BUFFER_SIZE ); + } + + m_nCachedDataSize = 0; + + if ( m_dataSize <= 0 ) + { + DevMsg(1, "Can't find streaming wav file: sound\\%s\n", GetFileName() ); + return; + } + + if ( IsPC() ) + { + m_hCache = wavedatacache->AsyncLoadCache( GetFileName(), m_dataSize, m_dataStart ); + + // size of a sample + m_sampleSize = source.SampleSize(); + // size in samples of the buffer + m_bufferSize = SINGLE_BUFFER_SIZE / m_sampleSize; + // size in samples (not bytes) of the wave itself + m_waveSize = fileSize / m_sampleSize; + + m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); + } + + if ( IsX360() ) + { + // size of a sample + m_sampleSize = source.SampleSize(); + // size in samples (not bytes) of the wave itself + m_waveSize = fileSize / m_sampleSize; + + streamFlags_t flags = STREAMED_FROMDVD; + + if ( !Q_strnicmp( pFileName, "music", 5 ) && ( pFileName[5] == '\\' || pFileName[5] == '/') ) + { + // music discards and cycles its buffers + flags |= STREAMED_SINGLEPLAY; + } + else if ( !Q_strnicmp( pFileName, "vo", 2 ) && ( pFileName[2] == '\\' || pFileName[2] == '/' ) && !source.IsSentenceWord() ) + { + // vo discards and cycles its buffers, except for sentence sources, which do recur + flags |= STREAMED_SINGLEPLAY; + } + + int bufferSize; + if ( source.Format() == WAVE_FORMAT_XMA ) + { + // each xma block has its own compression rate + // the buffer must be large enough to cover worst case delivery i/o latency + // the xma mixer expects quantum xma blocks + COMPILE_TIME_ASSERT( ( STREAM_BUFFER_DATASIZE % XMA_BLOCK_SIZE ) == 0 ); + bufferSize = STREAM_BUFFER_DATASIZE; + } + else + { + // calculate a worst case buffer size based on rate + bufferSize = STREAM_BUFFER_TIME*source.SampleRate()*m_sampleSize; + if ( source.Format() == WAVE_FORMAT_ADPCM ) + { + // consider adpcm as 4 bit samples + bufferSize /= 2; + } + + if ( source.IsLooped() ) + { + // lighten the streaming load for looping samples + // doubling the buffer halves the buffer search/load requests + bufferSize *= 2; + } + } + + // streaming buffers obey alignments + bufferSize = AlignValue( bufferSize, XBOX_DVD_SECTORSIZE ); + + // use double buffering + int numBuffers = 2; + + if ( m_dataSize <= STREAM_BUFFER_DATASIZE || m_dataSize <= numBuffers*bufferSize ) + { + // no gain for buffering a small file or multiple buffering + // match the expected transfer with a single buffer + bufferSize = m_dataSize; + numBuffers = 1; + } + + // size in samples of the transfer buffer + m_bufferSize = bufferSize / m_sampleSize; + + // allocate a transfer buffer + // matches the size of the streaming buffer exactly + // ensures that buffers can be filled and then consumed/requeued at the same time + m_buffer = new char[bufferSize]; + + int loopStart; + if ( source.IsLooped() ) + { + int loopBlock; + loopStart = m_pStreamSource->GetLoopingInfo( &loopBlock, NULL, NULL ) * m_sampleSize; + if ( source.Format() == WAVE_FORMAT_XMA ) + { + // xma works in blocks, mixer handles inter-block accurate loop positioning + // block streaming will cycle from the block where the loop occurs + loopStart = loopBlock * XMA_BLOCK_SIZE; + } + } + else + { + // sample not looped + loopStart = -1; + } + + // load the file piecewise through a buffering implementation + m_hStream = wavedatacache->OpenStreamedLoad( pFileName, m_dataSize, m_dataStart, startOffset, loopStart, bufferSize, numBuffers, flags ); + } + + m_bValid = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CWaveDataStreamAsync::~CWaveDataStreamAsync( void ) +{ + if ( IsPC() && m_source.IsPlayOnce() && m_source.CanDelete() ) + { + m_source.SetPlayOnce( false ); // in case it gets used again + wavedatacache->Unload( m_hCache ); + } + + if ( IsX360() ) + { + wavedatacache->CloseStreamedLoad( m_hStream ); + } + + delete [] m_buffer; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +char const *CWaveDataStreamAsync::GetFileName() +{ + static char fn[MAX_PATH]; + + if ( m_hFileName ) + { + if ( g_pFileSystem->String( m_hFileName, fn, sizeof( fn ) ) ) + { + return fn; + } + } + + Assert( 0 ); + return ""; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWaveDataStreamAsync::IsReadyToMix() +{ + if ( IsPC() ) + { + // If not async loaded, start mixing right away + if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() ) + { + return true; + } + + bool bCacheValid; + bool bLoaded = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid ); + if ( !bCacheValid ) + { + wavedatacache->RestartDataLoad( &m_hCache, GetFileName(), m_dataSize, m_dataStart ); + } + return bLoaded; + } + + if ( IsX360() ) + { + return wavedatacache->IsStreamedDataReady( m_hStream ); + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Read data from the source - this is the primary function of a IWaveData subclass +// Get the data from the buffer (or reload from disk) +// Input : **pData - +// sampleIndex - +// sampleCount - +// copyBuf[AUDIOSOURCE_COPYBUF_SIZE] - +// Output : int +//----------------------------------------------------------------------------- +int CWaveDataStreamAsync::ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) +{ + // Current file position + int seekpos = m_dataStart + m_sampleIndex * m_sampleSize; + + // wrap position if looping + if ( m_source.IsLooped() ) + { + sampleIndex = m_pStreamSource->UpdateLoopingSamplePosition( sampleIndex ); + if ( sampleIndex < m_sampleIndex ) + { + // looped back, buffer has no samples yet + m_sampleIndex = sampleIndex; + m_bufferCount = 0; + + // update file position + seekpos = m_dataStart + sampleIndex * m_sampleSize; + } + } + + // UNDONE: This is an error!! + // The mixer playing back the stream tried to go backwards!?!?! + // BUGBUG: Just play the beginning of the buffer until we get to a valid linear position + if ( sampleIndex < m_sampleIndex ) + sampleIndex = m_sampleIndex; + + // calc sample position relative to the current buffer + // m_sampleIndex is the sample position of the first byte of the buffer + sampleIndex -= m_sampleIndex; + + // out of range? refresh buffer + if ( sampleIndex >= m_bufferCount ) + { + // advance one buffer (the file is positioned here) + m_sampleIndex += m_bufferCount; + // next sample to load + sampleIndex -= m_bufferCount; + + // if the remainder is greated than one buffer size, seek over it. Otherwise, read the next chunk + // and leave the remainder as an offset. + + // number of buffers to "skip" (as in the case where we are starting a streaming sound not at the beginning) + int skips = sampleIndex / m_bufferSize; + + // If we are skipping over a buffer, do it with a seek instead of a read. + if ( skips ) + { + // skip directly to next position + m_sampleIndex += sampleIndex; + sampleIndex = 0; + } + + // move the file to the new position + seekpos = m_dataStart + (m_sampleIndex * m_sampleSize); + + // This is the maximum number of samples we could read from the file + m_bufferCount = m_waveSize - m_sampleIndex; + + // past the end of the file? stop the wave. + if ( m_bufferCount <= 0 ) + return 0; + + // clamp available samples to buffer size + if ( m_bufferCount > m_bufferSize ) + m_bufferCount = m_bufferSize; + + if ( IsPC() ) + { + // See if we can load in the intial data right out of the cached data lump instead. + int cacheddatastartpos = ( seekpos - m_dataStart ); + + // FastGet doesn't call into IsPrecachedSound if the handle appears valid... + CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet(); + if ( !info ) + { + // Full recache + info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize ); + } + + bool startupCacheUsed = false; + + if ( info && + ( m_nCachedDataSize > 0 ) && + ( cacheddatastartpos < m_nCachedDataSize ) ) + { + // Get a ptr to the cached data + const byte *cacheddata = info->CachedData(); + if ( cacheddata ) + { + // See how many samples of cached data are available (cacheddatastartpos is zero on the first read) + int availSamples = ( m_nCachedDataSize - cacheddatastartpos ) / m_sampleSize; + + // Clamp to size of our internal buffer + if ( availSamples > m_bufferSize ) + { + availSamples = m_bufferSize; + } + + // Mark how many we are returning + m_bufferCount = availSamples; + // Copy raw sample data directly out of cache + Q_memcpy( m_buffer, ( char * )cacheddata + cacheddatastartpos, availSamples * m_sampleSize ); + + startupCacheUsed = true; + } + } + + // Not in startup cache, grab data from async cache loader (will block if data hasn't arrived yet) + if ( !startupCacheUsed ) + { + bool postprocessed = false; + + // read in the max bufferable, available samples + if ( !wavedatacache->CopyDataIntoMemory( + m_hCache, + GetFileName(), + m_dataSize, + m_dataStart, + m_buffer, + sizeof( m_buffer ), + seekpos, + m_bufferCount * m_sampleSize, + &postprocessed ) ) + { + return 0; + } + + // do any conversion the source needs (mixer will decode/decompress) + if ( !postprocessed ) + { + // Note that we don't set the postprocessed flag on the underlying data, since for streaming we're copying the + // original data into this buffer instead. + m_pStreamSource->UpdateSamples( m_buffer, m_bufferCount ); + } + } + } + + if ( IsX360() ) + { + if ( m_hStream != INVALID_STREAM_HANDLE ) + { + // request available data, may get less + // drives the buffering + m_bufferCount = wavedatacache->CopyStreamedDataIntoMemory( + m_hStream, + m_buffer, + m_bufferSize * m_sampleSize, + seekpos, + m_bufferCount * m_sampleSize ); + // convert to number of samples in the buffer + m_bufferCount /= m_sampleSize; + } + else + { + return 0; + } + + // do any conversion now the source needs (mixer will decode/decompress) on this buffer + m_pStreamSource->UpdateSamples( m_buffer, m_bufferCount ); + } + } + + // If we have some samples in the buffer that are within range of the request + // Use unsigned comparisons so that if sampleIndex is somehow negative that + // will be treated as out of range. + if ( (unsigned)sampleIndex < (unsigned)m_bufferCount ) + { + // Get the desired starting sample + *pData = (void *)&m_buffer[sampleIndex * m_sampleSize]; + + // max available + int available = m_bufferCount - sampleIndex; + // clamp available to max requested + if ( available > sampleCount ) + available = sampleCount; + + return available; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Iterator for wave data (this is to abstract streaming/buffering) +//----------------------------------------------------------------------------- +class CWaveDataMemoryAsync : public IWaveData +{ +public: + CWaveDataMemoryAsync( CAudioSource &source ); + ~CWaveDataMemoryAsync( void ) {} + CAudioSource &Source( void ) { return m_source; } + + virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ); + virtual bool IsReadyToMix(); + +private: + CAudioSource &m_source; // pointer to source +}; + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &source - +//----------------------------------------------------------------------------- +CWaveDataMemoryAsync::CWaveDataMemoryAsync( CAudioSource &source ) : + m_source(source) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : **pData - +// sampleIndex - +// sampleCount - +// copyBuf[AUDIOSOURCE_COPYBUF_SIZE] - +// Output : int +//----------------------------------------------------------------------------- +int CWaveDataMemoryAsync::ReadSourceData( void **pData, int sampleIndex, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] ) +{ + return m_source.GetOutputData( pData, sampleIndex, sampleCount, copyBuf ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CWaveDataMemoryAsync::IsReadyToMix() +{ + if ( !m_source.IsAsyncLoad() && !snd_async_fullyasync.GetBool() ) + { + // Wait until we're pending at least + if ( m_source.GetCacheStatus() == CAudioSource::AUDIO_NOT_LOADED ) + { + return false; + } + return true; + } + + if ( m_source.IsCached() ) + { + return true; + } + + if ( IsPC() ) + { + // Msg( "Waiting for data '%s'\n", m_source.GetFileName() ); + m_source.CacheLoad(); + } + + if ( IsX360() ) + { + // expected to be resident and valid, otherwise being called prior to load + Assert( 0 ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &source - +// *pStreamSource - +// &io - +// *pFileName - +// dataOffset - +// dataSize - +// Output : IWaveData +//----------------------------------------------------------------------------- +IWaveData *CreateWaveDataStream( CAudioSource &source, IWaveStreamSource *pStreamSource, const char *pFileName, int dataStart, int dataSize, CSfxTable *pSfx, int startOffset ) +{ + CWaveDataStreamAsync *pStream = new CWaveDataStreamAsync( source, pStreamSource, pFileName, dataStart, dataSize, pSfx, startOffset ); + if ( !pStream || !pStream->IsValid() ) + { + delete pStream; + pStream = NULL; + } + return pStream; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &source - +// Output : IWaveData +//----------------------------------------------------------------------------- +IWaveData *CreateWaveDataMemory( CAudioSource &source ) +{ + CWaveDataMemoryAsync *mem = new CWaveDataMemoryAsync( source ); + return mem; +} |