summaryrefslogtreecommitdiff
path: root/engine/audio/private/snd_wave_source.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/audio/private/snd_wave_source.cpp')
-rw-r--r--engine/audio/private/snd_wave_source.cpp2816
1 files changed, 2816 insertions, 0 deletions
diff --git a/engine/audio/private/snd_wave_source.cpp b/engine/audio/private/snd_wave_source.cpp
new file mode 100644
index 0000000..38c70a0
--- /dev/null
+++ b/engine/audio/private/snd_wave_source.cpp
@@ -0,0 +1,2816 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "audio_pch.h"
+#include "snd_mp3_source.h"
+#include "utlsymbol.h"
+#include "checksum_crc.h"
+#include "../../host.h"
+#include "xwvfile.h"
+#include "filesystem/IQueuedLoader.h"
+#include "tier1/lzmaDecoder.h"
+#include "tier2/fileutils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// This determines how much data to pre-cache (will invalidate per-map caches if changed).
+#define SND_ASYNC_LOOKAHEAD_SECONDS ( 0.125f )
+
+extern ConVar snd_async_spew_blocking;
+ConVar snd_async_minsize("snd_async_minsize", "262144");
+
+// #define DEBUG_CHUNKS
+
+//-----------------------------------------------------------------------------
+// Purpose: Report chunk error
+// Input : id - chunk FOURCC
+//-----------------------------------------------------------------------------
+void ChunkError( unsigned int id )
+{
+#if defined( DEBUG_CHUNKS ) && defined( _DEBUG )
+ if ( id == WAVE_LIST || id == WAVE_FACT )
+ {
+ // unused chunks, not an error
+ return;
+ }
+
+ char tmp[256];
+ char idname[5];
+ idname[4] = 0;
+ memcpy( idname, &id, 4 );
+
+ Q_snprintf( tmp, sizeof( tmp ), "Unhandled chunk %s\n", idname );
+ Plat_DebugString( tmp );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Determine a true sample count for an ADPCM blob
+//-----------------------------------------------------------------------------
+int ADPCMSampleCount( ADPCMWAVEFORMAT *pFormat, int length )
+{
+ // determine a true sample count
+ int nChannels = LittleWord( pFormat->wfx.nChannels );
+ int wSamplesPerBlock = LittleWord( pFormat->wSamplesPerBlock );
+
+ int blockSize = (( wSamplesPerBlock - 2) * nChannels ) / 2;
+ blockSize += 7 * nChannels;
+
+ int blockCount = length / blockSize;
+ int blockRem = length % blockSize;
+
+ // total samples in complete blocks
+ int sampleCount = blockCount * wSamplesPerBlock;
+
+ // add remaining in a short block
+ if ( blockRem )
+ {
+ sampleCount += wSamplesPerBlock - (((blockSize - blockRem) * 2) / nChannels );
+ }
+
+ return sampleCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Init to empty wave
+//-----------------------------------------------------------------------------
+CAudioSourceWave::CAudioSourceWave( CSfxTable *pSfx )
+{
+ m_format = 0;
+ m_pHeader = NULL;
+ m_nHeaderSize = 0;
+
+ // no looping
+ m_loopStart = -1;
+
+ m_sampleSize = 1;
+ m_sampleCount = 0;
+ m_bits = 0;
+ m_channels = 0;
+ m_dataStart = 0;
+ m_dataSize = 0;
+ m_rate = 0;
+
+ m_refCount = 0;
+
+ m_pSfx = pSfx;
+#ifdef _DEBUG
+ if ( m_pSfx )
+ m_pDebugName = strdup( m_pSfx->getname() );
+#endif
+
+ m_bNoSentence = false;
+ m_pTempSentence = NULL;
+ m_nCachedDataSize = 0;
+ m_bIsPlayOnce = false;
+ m_bIsSentenceWord = false;
+
+ m_numDecodedSamples = 0;
+}
+
+CAudioSourceWave::CAudioSourceWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info )
+{
+ m_pSfx = pSfx;
+#ifdef _DEBUG
+ if ( m_pSfx )
+ m_pDebugName = strdup( m_pSfx->getname() );
+#endif
+
+ m_refCount = 0;
+
+ m_pHeader = NULL;
+ m_nHeaderSize = 0;
+
+ if ( info->HeaderData() )
+ {
+ m_pHeader = new char[ info->HeaderSize() ];
+ Assert( m_pHeader );
+ Q_memcpy( m_pHeader, info->HeaderData(), info->HeaderSize() );
+ m_nHeaderSize = info->HeaderSize();
+ }
+
+ m_bits = info->Bits();
+ m_channels = info->Channels();
+ m_sampleSize = info->SampleSize();
+ m_format = info->Format();
+ m_dataStart = info->DataStart();
+ m_dataSize = info->DataSize();
+ m_rate = info->SampleRate();
+ m_loopStart = info->LoopStart();
+ m_sampleCount = info->SampleCount();
+ m_numDecodedSamples = m_sampleCount;
+
+ if ( m_format == WAVE_FORMAT_ADPCM && m_pHeader )
+ {
+ m_numDecodedSamples = ADPCMSampleCount( (ADPCMWAVEFORMAT *)m_pHeader, m_sampleCount );
+ }
+
+ m_bNoSentence = false;
+ m_pTempSentence = NULL;
+ m_nCachedDataSize = 0;
+ m_bIsPlayOnce = false;
+ m_bIsSentenceWord = false;
+}
+
+
+CAudioSourceWave::~CAudioSourceWave( void )
+{
+#if _DEBUG
+ if ( !CanDelete() )
+ Assert(0);
+#endif
+
+ // for non-standard waves, we store a copy of the header in RAM
+ delete[] m_pHeader;
+ delete m_pTempSentence;
+}
+
+int CAudioSourceWave::GetType( void )
+{
+ return AUDIO_SOURCE_WAV;
+}
+
+void CAudioSourceWave::GetCacheData( CAudioSourceCachedInfo *info )
+{
+ Assert( info->Type() == CAudioSource::AUDIO_SOURCE_WAV );
+
+ byte tempbuf[ 32768 ];
+ int datalen = 0;
+ // NOTE GetStartupData has side-effects (...) hence the unconditional call
+ if ( GetStartupData( tempbuf, sizeof( tempbuf ), datalen ) &&
+ info->s_bIsPrecacheSound &&
+ datalen > 0 )
+ {
+ byte *data = new byte[ datalen ];
+ Q_memcpy( data, tempbuf, datalen );
+ info->SetCachedDataSize( datalen );
+ info->SetCachedData( data );
+ }
+
+ info->SetBits( m_bits );
+ info->SetChannels( m_channels );
+ info->SetSampleSize( m_sampleSize );
+ info->SetFormat( m_format );
+ info->SetDataStart( m_dataStart ); // offset of wave data chunk
+ info->SetDataSize( m_dataSize ); // size of wave data chunk
+ info->SetSampleRate( m_rate );
+ info->SetLoopStart( m_loopStart );
+ info->SetSampleCount( m_sampleCount );
+
+ if ( m_pTempSentence )
+ {
+ CSentence *scopy = new CSentence;
+ *scopy = *m_pTempSentence;
+ info->SetSentence( scopy );
+
+ // Wipe it down to basically nothing
+ delete m_pTempSentence;
+ m_pTempSentence = NULL;
+ }
+
+ if ( m_pHeader && m_nHeaderSize > 0 )
+ {
+ byte *data = new byte[ m_nHeaderSize ];
+ Q_memcpy( data, m_pHeader, m_nHeaderSize );
+ info->SetHeaderSize( m_nHeaderSize );
+ info->SetHeaderData( data );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+char const *CAudioSourceWave::GetFileName()
+{
+ return m_pSfx ? m_pSfx->GetFileName() : "NULL m_pSfx";
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAudioSourceWave::IsAsyncLoad()
+{
+ VPROF("CAudioSourceWave::IsAsyncLoad");
+
+ if ( ( IsPC() || !IsX360() ) && !m_AudioCacheHandle.IsValid() )
+ {
+ m_AudioCacheHandle.Get( GetType(), m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
+ }
+
+ // If there's a bit of "cached data" then we don't have to lazy/async load (we still async load the remaining data,
+ // but we run from the cache initially)
+ if ( m_dataSize > snd_async_minsize.GetInt() )
+ return true;
+ return ( m_nCachedDataSize > 0 ) ? false : true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::CheckAudioSourceCache()
+{
+ if ( IsX360() )
+ {
+ // 360 does not use audio cache files
+ return;
+ }
+
+ Assert( m_pSfx );
+
+ if ( !m_pSfx || !m_pSfx->IsPrecachedSound() )
+ {
+ return;
+ }
+
+ // This will "re-cache" this if it's not in this level's cache already
+ m_AudioCacheHandle.Get( GetType(), true, m_pSfx, &m_nCachedDataSize );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Init the wave data.
+// Input : *pHeaderBuffer - the RIFF fmt chunk
+// headerSize - size of that chunk
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::Init( const char *pHeaderBuffer, int headerSize )
+{
+ const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)pHeaderBuffer;
+
+ // copy the relevant header data
+ m_format = LittleWord( pHeader->wFormatTag );
+ m_bits = LittleWord( pHeader->wBitsPerSample );
+ m_rate = LittleDWord( pHeader->nSamplesPerSec );
+ m_channels = LittleWord( pHeader->nChannels );
+ m_sampleSize = (m_bits * m_channels)/8;
+
+ // this can never be zero -- other functions divide by this.
+ // this should never happen, but avoid crashing
+ if ( m_sampleSize <= 0 )
+ {
+ m_sampleSize = 1;
+ }
+
+ if ( m_format == WAVE_FORMAT_ADPCM )
+ {
+ // For non-standard waves (like ADPCM) store the header, it has the decoding coefficients
+ m_pHeader = new char[headerSize];
+ memcpy( m_pHeader, pHeader, headerSize );
+ m_nHeaderSize = headerSize;
+
+ // treat ADPCM sources as a file of bytes. They are decoded by the mixer
+ m_sampleSize = 1;
+ }
+}
+
+
+int CAudioSourceWave::SampleRate( void )
+{
+ return m_rate;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Size of each sample
+// Output :
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::SampleSize( void )
+{
+ return m_sampleSize;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Total number of samples in this source
+// Output : int
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::SampleCount( void )
+{
+ // caller wants real samples
+ return m_numDecodedSamples;
+}
+
+int CAudioSourceWave::Format( void )
+{
+ return m_format;
+}
+
+int CAudioSourceWave::DataSize( void )
+{
+ return m_dataSize;
+}
+
+bool CAudioSourceWave::IsVoiceSource()
+{
+ if ( GetSentence() )
+ {
+ if ( GetSentence()->GetVoiceDuck() )
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Do any sample conversion
+// For 8 bit PCM, convert to signed because the mixing routine assumes this
+// Input : *pData - pointer to sample data
+// sampleCount - number of samples
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ConvertSamples( char *pData, int sampleCount )
+{
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ if ( m_bits == 8 )
+ {
+ for ( int i = 0; i < sampleCount*m_channels; i++ )
+ {
+ *pData = (unsigned char)((int)((unsigned)*pData) - 128);
+ pData++;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Parse base chunks
+// Input : &walk - riff file to parse
+// : chunkName - name of the chunk to parse
+//-----------------------------------------------------------------------------
+// UNDONE: Move parsing loop here and drop each chunk into a virtual function
+// instead of this being virtual.
+void CAudioSourceWave::ParseChunk( IterateRIFF &walk, int chunkName )
+{
+ switch( chunkName )
+ {
+ case WAVE_CUE:
+ ParseCueChunk( walk );
+ break;
+ case WAVE_SAMPLER:
+ ParseSamplerChunk( walk );
+ break;
+ case WAVE_VALVEDATA:
+ ParseSentence( walk );
+ break;
+ default:
+ // unknown and don't care
+ ChunkError( walk.ChunkName() );
+ break;
+ }
+}
+
+bool CAudioSourceWave::IsLooped( void )
+{
+ return (m_loopStart >= 0) ? true : false;
+}
+
+bool CAudioSourceWave::IsStereoWav( void )
+{
+ return (m_channels == 2) ? true : false;
+}
+
+bool CAudioSourceWave::IsStreaming( void )
+{
+ return false;
+}
+
+
+int CAudioSourceWave::GetCacheStatus( void )
+{
+ return AUDIO_IS_LOADED;
+}
+
+void CAudioSourceWave::CacheLoad( void )
+{
+}
+
+void CAudioSourceWave::CacheUnload( void )
+{
+}
+
+int CAudioSourceWave::ZeroCrossingBefore( int sample )
+{
+ return sample;
+}
+
+
+int CAudioSourceWave::ZeroCrossingAfter( int sample )
+{
+ return sample;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : &walk -
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ParseSentence( IterateRIFF &walk )
+{
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ buf.EnsureCapacity( walk.ChunkSize() );
+ walk.ChunkRead( buf.Base() );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
+
+ m_pTempSentence = new CSentence();
+ Assert( m_pTempSentence );
+
+ m_pTempSentence->InitFromDataChunk( buf.Base(), buf.TellPut() );
+
+ // Throws all phonemes into one word, discards sentence memory, etc.
+ m_pTempSentence->MakeRuntimeOnly();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CSentence
+//-----------------------------------------------------------------------------
+CSentence *CAudioSourceWave::GetSentence( void )
+{
+ if ( IsX360() )
+ {
+ return m_pTempSentence;
+ }
+
+ // Already checked and this wav doesn't have sentence data...
+ if ( m_bNoSentence == true )
+ {
+ return NULL;
+ }
+
+ // Look up sentence from cache
+ CAudioSourceCachedInfo *info = m_AudioCacheHandle.FastGet();
+ if ( !info )
+ {
+ info = m_AudioCacheHandle.Get( CAudioSource::AUDIO_SOURCE_WAV, m_pSfx->IsPrecachedSound(), m_pSfx, &m_nCachedDataSize );
+ }
+ Assert( info );
+ if ( !info )
+ {
+ m_bNoSentence = true;
+ return NULL;
+ }
+
+ CSentence *sentence = info->Sentence();
+ if ( !sentence )
+ {
+ m_bNoSentence = true;
+ return NULL;
+ }
+
+ if ( sentence->m_bIsValid )
+ {
+ return sentence;
+ }
+
+ m_bNoSentence = true;
+
+ return NULL;
+}
+
+const char *CAudioSourceWave::GetName()
+{
+ return m_pSfx ? m_pSfx->getname() : NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Load a native xaudio or legacy wav
+//-----------------------------------------------------------------------------
+bool CAudioSourceWave::GetXboxAudioStartupData()
+{
+ CUtlBuffer buf;
+ char fileName[MAX_PATH];
+ char tempFileName[MAX_PATH];
+
+ MEM_ALLOC_CREDIT();
+
+ // try native optimal xma wav file first
+ Q_StripExtension( m_pSfx->GetFileName(), tempFileName, sizeof( tempFileName ) );
+ Q_snprintf( fileName, sizeof( fileName ), "sound\\%s.360.wav", tempFileName );
+ if ( !g_pFullFileSystem->ReadFile( fileName, "GAME", buf, sizeof( xwvHeader_t ) ) )
+ {
+ // not found, not supported
+ return false;
+ }
+ else
+ {
+ xwvHeader_t* pHeader = (xwvHeader_t *)buf.Base();
+ if ( pHeader->id != XWV_ID || pHeader->version != XWV_VERSION )
+ {
+ return false;
+ }
+
+ if ( pHeader->format == XWV_FORMAT_XMA )
+ {
+ m_format = WAVE_FORMAT_XMA;
+ }
+ else if ( pHeader->format == XWV_FORMAT_PCM )
+ {
+ m_format = WAVE_FORMAT_PCM;
+ }
+ else
+ {
+ // unknown
+ return false;
+ }
+
+ m_rate = pHeader->GetSampleRate();
+ m_channels = pHeader->channels;
+ m_dataStart = pHeader->dataOffset;
+ m_dataSize = pHeader->dataSize;
+
+ m_loopStart = pHeader->loopStart;
+ m_loopBlock = pHeader->loopBlock;
+ m_numLeadingSamples = pHeader->numLeadingSamples;
+ m_numTrailingSamples = pHeader->numTrailingSamples;
+
+ if ( m_format == WAVE_FORMAT_XMA )
+ {
+ // xma is compressed blocks, trick to fool system to treat data as bytes, not samples
+ // unfortunate, but callers must know xma context and provide offsets in samples or bytes
+ m_bits = 16;
+ m_sampleSize = 1;
+ m_sampleCount = m_dataSize;
+ }
+ else
+ {
+ m_bits = 16;
+ m_sampleSize = sizeof( short ) * m_channels;
+ m_sampleCount = m_dataSize / m_sampleSize;
+ }
+
+ // keep true decoded samples because cannot be easily determined
+ m_numDecodedSamples = pHeader->numDecodedSamples;
+
+ m_bNoSentence = true;
+
+ CUtlBuffer fileBuffer;
+ if ( pHeader->staticDataSize )
+ {
+ // get optional data
+ if ( !g_pFullFileSystem->ReadFile( fileName, "GAME", fileBuffer, pHeader->staticDataSize, sizeof( xwvHeader_t ) ) )
+ {
+ return false;
+ }
+
+ unsigned char *pData = (unsigned char *)fileBuffer.Base() + sizeof( xwvHeader_t );
+ if ( pHeader->GetSeekTableSize() )
+ {
+ // store off the seek table
+ m_nHeaderSize = pHeader->GetSeekTableSize();
+ m_pHeader = new char[m_nHeaderSize];
+ V_memcpy( m_pHeader, pData, m_nHeaderSize );
+
+ // advance past optional seek table
+ pData += m_nHeaderSize;
+ }
+
+ if ( pHeader->vdatSize )
+ {
+ m_pTempSentence = new CSentence();
+ Assert( m_pTempSentence );
+ m_bNoSentence = false;
+
+ // vdat is precompiled into minimal binary format and possibly compressed
+ if ( CLZMA::IsCompressed( pData ) )
+ {
+ // uncompress binary vdat and restore
+ CUtlBuffer targetBuffer;
+ int originalSize = CLZMA::GetActualSize( pData );
+ targetBuffer.EnsureCapacity( originalSize );
+ CLZMA::Uncompress( pData, (unsigned char *)targetBuffer.Base() );
+ targetBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, originalSize );
+ m_pTempSentence->CacheRestoreFromBuffer( targetBuffer );
+ }
+ else
+ {
+ m_pTempSentence->CacheRestoreFromBuffer( fileBuffer );
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Bastardized construction routine. This is just to avoid complex
+// constructor functions so code can be shared more easily by sub-classes
+// Input : *pFormatBuffer - RIFF header
+// formatSize - header size
+// &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::Setup( const char *pFormatBuffer, int formatSize, IterateRIFF &walk )
+{
+ Init( pFormatBuffer, formatSize );
+
+ while ( walk.ChunkAvailable() )
+ {
+ ParseChunk( walk, walk.ChunkName() );
+ walk.ChunkNext();
+ }
+}
+
+
+bool CAudioSourceWave::GetStartupData( void *dest, int destsize, int& bytesCopied )
+{
+ bytesCopied = 0;
+
+ char formatBuffer[1024];
+ const char *pName = m_pSfx->GetFileName();
+ InFileRIFF riff( pName, *g_pSndIO );
+
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ return false;
+ }
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ int format = 0;
+ int formatSize = 0;
+
+ // This chunk must be first as it contains the wave's format
+ // break out when we've parsed it
+ while ( walk.ChunkAvailable() && format == 0 )
+ {
+ switch( walk.ChunkName() )
+ {
+ case WAVE_FMT:
+ {
+ if ( walk.ChunkSize() <= sizeof( formatBuffer ) )
+ {
+ walk.ChunkRead( formatBuffer );
+ formatSize = walk.ChunkSize();
+ format = ((WAVEFORMATEX *)formatBuffer)->wFormatTag;
+ if( ((WAVEFORMATEX *)formatBuffer)->wBitsPerSample > 16)
+ {
+ Warning("Unsupported %d-bit wave file %s\n", (int)((WAVEFORMATEX *)formatBuffer)->wBitsPerSample, pName);
+ }
+ }
+ }
+ break;
+ default:
+ {
+ ChunkError( walk.ChunkName() );
+ }
+ break;
+ }
+ walk.ChunkNext();
+ }
+
+ // Not really a WAVE file or no format chunk, bail
+ if ( !format )
+ {
+ return false;
+ }
+
+ Setup( formatBuffer, formatSize, walk );
+
+ if ( !m_dataStart || !m_dataSize )
+ {
+ // failed during setup
+ return false;
+ }
+
+ // requesting precache snippet as leader for streaming startup latency
+ if ( destsize )
+ {
+ int file = g_pSndIO->open( m_pSfx->GetFileName() );
+ if ( !file )
+ {
+ return false;
+ }
+
+ int bytesNeeded = m_channels * ( m_bits >> 3 ) * m_rate * SND_ASYNC_LOOKAHEAD_SECONDS;
+
+ // Round to multiple of 4
+ bytesNeeded = ( bytesNeeded + 3 ) & ~3;
+
+ bytesCopied = min( destsize, m_dataSize );
+ bytesCopied = min( bytesNeeded, bytesCopied );
+
+ g_pSndIO->seek( file, m_dataStart );
+ g_pSndIO->read( dest, bytesCopied, file );
+ g_pSndIO->close( file );
+
+ // some samples need to be converted
+ ConvertSamples( (char *)dest, ( bytesCopied / m_sampleSize ) );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parses loop information from a cue chunk
+// Input : &walk - RIFF iterator
+// Output : int loop start position
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ParseCueChunk( IterateRIFF &walk )
+{
+ // Cue chunk as specified by RIFF format
+ // see $/research/jay/sound/riffnew.htm
+ struct
+ {
+ unsigned int dwName;
+ unsigned int dwPosition;
+ unsigned int fccChunk;
+ unsigned int dwChunkStart;
+ unsigned int dwBlockStart;
+ unsigned int dwSampleOffset;
+ } cue_chunk;
+
+ int cueCount;
+
+ // assume that the cue chunk stored in the wave is the start of the loop
+ // assume only one cue chunk, UNDONE: Test this assumption here?
+ cueCount = walk.ChunkReadInt();
+ if ( cueCount > 0 )
+ {
+ walk.ChunkReadPartial( &cue_chunk, sizeof(cue_chunk) );
+ m_loopStart = LittleLong( cue_chunk.dwSampleOffset );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parses loop information from a 'smpl' chunk
+// Input : &walk - RIFF iterator
+// Output : int loop start position
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ParseSamplerChunk( IterateRIFF &walk )
+{
+ // Sampler chunk for MIDI instruments
+ // Parse loop info from this chunk too
+ struct SampleLoop
+ {
+ unsigned int dwIdentifier;
+ unsigned int dwType;
+ unsigned int dwStart;
+ unsigned int dwEnd;
+ unsigned int dwFraction;
+ unsigned int dwPlayCount;
+ };
+
+ struct
+ {
+ unsigned int dwManufacturer;
+ unsigned int dwProduct;
+ unsigned int dwSamplePeriod;
+ unsigned int dwMIDIUnityNote;
+ unsigned int dwMIDIPitchFraction;
+ unsigned int dwSMPTEFormat;
+ unsigned int dwSMPTEOffset;
+ unsigned int cSampleLoops;
+ unsigned int cbSamplerData;
+ struct SampleLoop Loops[1];
+ } samplerChunk;
+
+ // assume that the loop end is the sample end
+ // assume that only the first loop is relevant
+
+ walk.ChunkReadPartial( &samplerChunk, sizeof(samplerChunk) );
+ if ( LittleLong( samplerChunk.cSampleLoops ) > 0 )
+ {
+ // only support normal forward loops
+ if ( LittleLong( samplerChunk.Loops[0].dwType ) == 0 )
+ {
+ m_loopStart = LittleLong( samplerChunk.Loops[0].dwStart );
+ }
+#ifdef _DEBUG
+ else
+ {
+ Msg("Unknown sampler chunk type %d on %s\n", LittleLong( samplerChunk.Loops[0].dwType ), m_pSfx->GetFileName() );
+ }
+#endif
+ }
+ // else discard - this is some other non-loop sampler data we don't support
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: get the wave header
+//-----------------------------------------------------------------------------
+void *CAudioSourceWave::GetHeader( void )
+{
+ return m_pHeader;
+}
+
+//-----------------------------------------------------------------------------
+// Gets the looping information. Some parameters are interpreted based on format
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples )
+{
+ if ( pLoopBlock )
+ {
+ // for xma, the block that contains the loop point
+ *pLoopBlock = m_loopBlock;
+ }
+
+ if ( pNumLeadingSamples )
+ {
+ // for xma, the number of leading samples at the loop block to discard
+ *pNumLeadingSamples = m_numLeadingSamples;
+ }
+
+ if ( pNumTrailingSamples )
+ {
+ // for xma, the number of trailing samples at the final block to discard
+ *pNumTrailingSamples = m_numTrailingSamples;
+ }
+
+ // the loop point in samples
+ return m_loopStart;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: wrap the position wrt looping
+// Input : samplePosition - absolute position
+// Output : int - looped position
+//-----------------------------------------------------------------------------
+int CAudioSourceWave::ConvertLoopedPosition( int samplePosition )
+{
+ if ( m_format == WAVE_FORMAT_XMA )
+ {
+ // xma mixer interprets loops and *always* sends a corrected position
+ return samplePosition;
+ }
+
+ // if the wave is looping and we're past the end of the sample
+ // convert to a position within the loop
+ // At the end of the loop, we return a short buffer, and subsequent call
+ // will loop back and get the rest of the buffer
+ if ( m_loopStart >= 0 && samplePosition >= m_sampleCount )
+ {
+ // size of loop
+ int loopSize = m_sampleCount - m_loopStart;
+ // subtract off starting bit of the wave
+ samplePosition -= m_loopStart;
+
+ if ( loopSize )
+ {
+ // "real" position in memory (mod off extra loops)
+ samplePosition = m_loopStart + (samplePosition % loopSize);
+ }
+ // ERROR? if no loopSize
+ }
+
+ return samplePosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: remove the reference for the mixer getting deleted
+// Input : *pMixer -
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ReferenceRemove( CAudioMixer *pMixer )
+{
+ m_refCount--;
+
+ if ( m_refCount == 0 && ( ( IsPC() && IsPlayOnce() ) || ( IsX360() && IsStreaming() ) ) )
+ {
+ SetPlayOnce( false ); // in case it gets used again
+ CacheUnload();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a mixer reference
+// Input : *pMixer -
+//-----------------------------------------------------------------------------
+void CAudioSourceWave::ReferenceAdd( CAudioMixer *pMixer )
+{
+ m_refCount++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: return true if no mixers reference this source
+//-----------------------------------------------------------------------------
+bool CAudioSourceWave::CanDelete( void )
+{
+ if ( m_refCount > 0 )
+ return false;
+
+ return true;
+}
+
+
+// CAudioSourceMemWave is a bunch of wave data that is all in memory.
+// To use it:
+// - derive from CAudioSourceMemWave
+// - call CAudioSourceWave::Init with a WAVEFORMATEX
+// - set m_sampleCount.
+// - implement GetDataPointer
+class CAudioSourceMemWave : public CAudioSourceWave
+{
+public:
+ CAudioSourceMemWave();
+ CAudioSourceMemWave( CSfxTable *pSfx );
+ CAudioSourceMemWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
+ virtual ~CAudioSourceMemWave();
+
+ // These are all implemented by CAudioSourceMemWave.
+ virtual CAudioMixer* CreateMixer( int initialStreamPosition = 0 );
+ virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ virtual int ZeroCrossingBefore( int sample );
+ virtual int ZeroCrossingAfter( int sample );
+
+ virtual int GetCacheStatus( void );
+ virtual void CacheLoad( void );
+ virtual void CacheUnload( void );
+
+ // by definition, should already be in memory
+ virtual void Prefetch() {}
+
+ virtual void ParseChunk( IterateRIFF &walk, int chunkName );
+ void ParseDataChunk( IterateRIFF &walk );
+
+protected:
+
+ // Whoeover derives must implement this.
+ virtual char *GetDataPointer( void );
+
+ memhandle_t m_hCache;
+ StreamHandle_t m_hStream;
+
+private:
+ CAudioSourceMemWave( const CAudioSourceMemWave & ); // not implemented, not accessible
+};
+
+
+CAudioSourceMemWave::CAudioSourceMemWave() :
+ CAudioSourceWave( NULL )
+{
+ m_hCache = 0;
+ m_hStream = INVALID_STREAM_HANDLE;
+}
+
+CAudioSourceMemWave::CAudioSourceMemWave( CSfxTable *pSfx ) :
+ CAudioSourceWave( pSfx )
+{
+ m_hCache = 0;
+ m_hStream = INVALID_STREAM_HANDLE;
+
+ if ( IsX360() )
+ {
+ bool bValid = GetXboxAudioStartupData();
+ if ( !bValid )
+ {
+ // failed, substitute placeholder
+ pSfx->m_bUseErrorFilename = true;
+ bValid = GetXboxAudioStartupData();
+ if ( bValid )
+ {
+ DevWarning( "Failed to load sound \"%s\", substituting \"%s\"\n", pSfx->getname(), pSfx->GetFileName() );
+ }
+ }
+
+ if ( bValid )
+ {
+ // a 360 memory wave is a critical resource kept locked in memory, load its data now
+ CacheLoad();
+ }
+ }
+}
+
+CAudioSourceMemWave::CAudioSourceMemWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
+ CAudioSourceWave( pSfx, info )
+{
+ m_hCache = 0;
+ m_hStream = INVALID_STREAM_HANDLE;
+}
+
+CAudioSourceMemWave::~CAudioSourceMemWave()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a mixer and initializes it with an appropriate mixer
+//-----------------------------------------------------------------------------
+CAudioMixer *CAudioSourceMemWave::CreateMixer( int initialStreamPosition )
+{
+ CAudioMixer *pMixer = CreateWaveMixer( CreateWaveDataMemory(*this), m_format, m_channels, m_bits, initialStreamPosition );
+
+ return pMixer;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : **pData - output pointer to samples
+// samplePosition - position (in samples not bytes)
+// sampleCount - number of samples (not bytes)
+// Output : int - number of samples available
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ // handle position looping
+ samplePosition = ConvertLoopedPosition( samplePosition );
+
+ // how many samples are available (linearly not counting looping)
+ int totalSampleCount = m_sampleCount - samplePosition;
+
+ // may be asking for a sample out of range, clip at zero
+ if ( totalSampleCount < 0 )
+ {
+ totalSampleCount = 0;
+ }
+
+ // clip max output samples to max available
+ if ( sampleCount > totalSampleCount )
+ {
+ sampleCount = totalSampleCount;
+ }
+
+ // byte offset in sample database
+ samplePosition *= m_sampleSize;
+
+ // if we are returning some samples, store the pointer
+ if ( sampleCount )
+ {
+ // Starting past end of "preloaded" data, just use regular cache
+ if ( samplePosition >= m_nCachedDataSize )
+ {
+ *pData = GetDataPointer();
+ }
+ else
+ {
+ if ( IsPC() || !IsX360() )
+ {
+ // Start async loader if we haven't already done so
+ CacheLoad();
+
+ // Return less data if we are about to run out of uncached data
+ if ( samplePosition + ( sampleCount * m_sampleSize ) >= m_nCachedDataSize )
+ {
+ sampleCount = ( m_nCachedDataSize - samplePosition ) / m_sampleSize;
+ }
+
+ // Point at preloaded/cached data from .cache file for now
+ *pData = GetCachedDataPointer();
+ }
+ else
+ {
+ // for 360, memory wave data should have already been loaded and locked in cache
+ Assert( 0 );
+ }
+ }
+
+ if ( *pData )
+ {
+ *pData = (char *)*pData + samplePosition;
+ }
+ else
+ {
+ // End of data or some other problem
+ sampleCount = 0;
+ }
+ }
+
+ return sampleCount;
+}
+
+
+// Hardcoded macros to test for zero crossing
+#define ZERO_X_8(b) ((b)<2 && (b)>-2)
+#define ZERO_X_16(b) ((b)<512 && (b)>-512)
+
+//-----------------------------------------------------------------------------
+// Purpose: Search backward for a zero crossing starting at sample
+// Input : sample - starting point
+// Output : position of zero crossing
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::ZeroCrossingBefore( int sample )
+{
+ char *pWaveData = GetDataPointer();
+
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ if ( m_bits == 8 )
+ {
+ char *pData = pWaveData + sample * m_sampleSize;
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_8(*pData) )
+ zero = true;
+ else
+ {
+ sample--;
+ pData--;
+ }
+ }
+ }
+ else
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_8(*pData) && ZERO_X_8(pData[1]) )
+ zero = true;
+ else
+ {
+ sample--;
+ pData--;
+ }
+ }
+ }
+ }
+ else
+ {
+ short *pData = (short *)(pWaveData + sample * m_sampleSize);
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) )
+ zero = true;
+ else
+ {
+ pData--;
+ sample--;
+ }
+ }
+ }
+ else
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) && ZERO_X_16(pData[1]) )
+ zero = true;
+ else
+ {
+ sample--;
+ pData--;
+ }
+ }
+ }
+ }
+ }
+ return sample;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Search forward for a zero crossing
+// Input : sample - starting point
+// Output : position of found zero crossing
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::ZeroCrossingAfter( int sample )
+{
+ char *pWaveData = GetDataPointer();
+
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ if ( m_bits == 8 )
+ {
+ char *pData = pWaveData + sample * m_sampleSize;
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample < SampleCount() && !zero )
+ {
+ if ( ZERO_X_8(*pData) )
+ zero = true;
+ else
+ {
+ sample++;
+ pData++;
+ }
+ }
+ }
+ else
+ {
+ while ( sample < SampleCount() && !zero )
+ {
+ if ( ZERO_X_8(*pData) && ZERO_X_8(pData[1]) )
+ zero = true;
+ else
+ {
+ sample++;
+ pData++;
+ }
+ }
+ }
+ }
+ else
+ {
+ short *pData = (short *)(pWaveData + sample * m_sampleSize);
+ bool zero = false;
+
+ if ( m_channels == 1 )
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) )
+ zero = true;
+ else
+ {
+ pData++;
+ sample++;
+ }
+ }
+ }
+ else
+ {
+ while ( sample > 0 && !zero )
+ {
+ if ( ZERO_X_16(*pData) && ZERO_X_16(pData[1]) )
+ zero = true;
+ else
+ {
+ sample++;
+ pData++;
+ }
+ }
+ }
+ }
+ }
+ return sample;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parse chunks with unique processing to in-memory waves
+// Input : &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::ParseChunk( IterateRIFF &walk, int chunkName )
+{
+ switch( chunkName )
+ {
+ // this is the audio data
+ case WAVE_DATA:
+ ParseDataChunk( walk );
+ return;
+ }
+
+ CAudioSourceWave::ParseChunk( walk, chunkName );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: reads the actual sample data and parses it
+// Input : &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::ParseDataChunk( IterateRIFF &walk )
+{
+ m_dataStart = walk.ChunkFilePosition() + 8;
+ m_dataSize = walk.ChunkSize();
+
+ // 360 streaming model loads data later, but still needs critical member setup
+ char *pData = NULL;
+ if ( IsPC() || !IsX360() )
+ {
+ pData = GetDataPointer();
+ if ( !pData )
+ {
+ Error( "CAudioSourceMemWave (%s): GetDataPointer() failed.", m_pSfx ? m_pSfx->GetFileName() : "m_pSfx = NULL" );
+ }
+
+ // load them into memory (bad!!, this is a duplicate read of the data chunk)
+ walk.ChunkRead( pData );
+ }
+
+ if ( m_format == WAVE_FORMAT_PCM )
+ {
+ // number of samples loaded
+ m_sampleCount = m_dataSize / m_sampleSize;
+ m_numDecodedSamples = m_sampleCount;
+ }
+ else if ( m_format == WAVE_FORMAT_ADPCM )
+ {
+ // The ADPCM mixers treat the wave source as a flat file of bytes.
+ // Since each "sample" is a byte (this is a flat file), the number of samples is the file size
+ m_sampleCount = m_dataSize;
+ m_sampleSize = 1;
+
+ // file says 4, output is 16
+ m_bits = 16;
+
+ m_numDecodedSamples = ADPCMSampleCount( (ADPCMWAVEFORMAT *)m_pHeader, m_dataSize );
+ }
+
+ // some samples need to be converted
+ if ( pData )
+ {
+ ConvertSamples( pData, m_sampleCount );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+int CAudioSourceMemWave::GetCacheStatus( void )
+{
+ VPROF("CAudioSourceMemWave::GetCacheStatus");
+
+ if ( IsPC() || !IsX360() )
+ {
+ // NOTE: This will start the load if it isn't started
+ bool bCacheValid;
+ bool bCompleted = wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid );
+ if ( !bCacheValid )
+ {
+ wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(), m_dataSize, m_dataStart );
+ }
+ if ( bCompleted )
+ return AUDIO_IS_LOADED;
+ if ( wavedatacache->IsDataLoadInProgress( m_hCache ) )
+ return AUDIO_LOADING;
+ }
+ else
+ {
+ return wavedatacache->IsStreamedDataReady( m_hStream ) ? AUDIO_IS_LOADED : AUDIO_NOT_LOADED;
+ }
+
+ return AUDIO_NOT_LOADED;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::CacheLoad( void )
+{
+ if ( IsPC() || !IsX360() )
+ {
+ // Commence lazy load?
+ if ( m_hCache != 0 )
+ {
+ bool bCacheValid;
+ wavedatacache->IsDataLoadCompleted( m_hCache, &bCacheValid );
+ if ( !bCacheValid )
+ {
+ wavedatacache->RestartDataLoad( &m_hCache, m_pSfx->GetFileName(), m_dataSize, m_dataStart );
+ }
+ return;
+ }
+
+ m_hCache = wavedatacache->AsyncLoadCache( m_pSfx->GetFileName(), m_dataSize, m_dataStart );
+ }
+ else
+ {
+ if ( m_hStream == INVALID_STREAM_HANDLE )
+ {
+ // memory wave is resident
+ const char *pFilename = m_pSfx->GetFileName();
+ streamFlags_t streamFlags = STREAMED_FROMDVD;
+ char szFilename[MAX_PATH];
+ if ( m_format == WAVE_FORMAT_XMA || m_format == WAVE_FORMAT_PCM )
+ {
+ V_strcpy_safe( szFilename, pFilename );
+ V_SetExtension( szFilename, ".360.wav", sizeof( szFilename ) );
+ pFilename = szFilename;
+
+ // memory resident xma waves use the queued loader
+ // restricting to XMA due to not correctly running a post ConvertSamples, which is not an issue for XMA
+ if ( g_pQueuedLoader->IsMapLoading() )
+ {
+ // hint the wave data cache
+ streamFlags |= STREAMED_QUEUEDLOAD;
+ }
+ }
+
+ // open stream to load as a single monolithic buffer
+ m_hStream = wavedatacache->OpenStreamedLoad( pFilename, m_dataSize, m_dataStart, 0, -1, m_dataSize, 1, streamFlags );
+ if ( m_hStream != INVALID_STREAM_HANDLE && !( streamFlags & STREAMED_QUEUEDLOAD ) )
+ {
+ // block and finish load, convert data once right now
+ char *pWaveData = (char *)wavedatacache->GetStreamedDataPointer( m_hStream, true );
+ if ( pWaveData )
+ {
+ ConvertSamples( pWaveData, m_dataSize/m_sampleSize );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceMemWave::CacheUnload( void )
+{
+ if ( IsPC() || !IsX360() )
+ {
+ if ( m_hCache != 0 )
+ {
+ wavedatacache->Unload( m_hCache );
+ }
+ }
+ else
+ {
+ if ( m_hStream != INVALID_STREAM_HANDLE )
+ {
+ wavedatacache->CloseStreamedLoad( m_hStream );
+ m_hStream = INVALID_STREAM_HANDLE;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char
+//-----------------------------------------------------------------------------
+char *CAudioSourceMemWave::GetDataPointer( void )
+{
+ char *pWaveData = NULL;
+
+ if ( IsPC() || !IsX360() )
+ {
+ bool bSamplesConverted = false;
+
+ if ( m_hCache == 0 )
+ {
+ // not in cache, start loading
+ CacheLoad();
+ }
+
+ // mount the requested data, blocks if necessary
+ wavedatacache->GetDataPointer(
+ m_hCache,
+ m_pSfx->GetFileName(),
+ m_dataSize,
+ m_dataStart,
+ (void **)&pWaveData,
+ 0,
+ &bSamplesConverted );
+
+ // If we have reloaded data from disk (async) and we haven't converted the samples yet, do it now
+ // FIXME: Is this correct for stereo wavs?
+ if ( pWaveData && !bSamplesConverted )
+ {
+ ConvertSamples( pWaveData, m_dataSize/m_sampleSize );
+ wavedatacache->SetPostProcessed( m_hCache, true );
+ }
+ }
+ else
+ {
+ if ( m_hStream != INVALID_STREAM_HANDLE )
+ {
+ // expected to be valid, unless failure during setup
+ pWaveData = (char *)wavedatacache->GetStreamedDataPointer( m_hStream, true );
+ }
+ }
+
+ return pWaveData;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Wave source for streaming wave files
+// UNDONE: Handle looping
+//-----------------------------------------------------------------------------
+class CAudioSourceStreamWave : public CAudioSourceWave, public IWaveStreamSource
+{
+public:
+ CAudioSourceStreamWave( CSfxTable *pSfx );
+ CAudioSourceStreamWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info );
+ ~CAudioSourceStreamWave();
+
+ CAudioMixer *CreateMixer( int initialStreamPosition = 0 );
+ int GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );
+ void ParseChunk( IterateRIFF &walk, int chunkName );
+ bool IsStreaming( void ) { return true; }
+
+ virtual int GetCacheStatus( void );
+
+ // IWaveStreamSource
+ virtual int UpdateLoopingSamplePosition( int samplePosition )
+ {
+ return ConvertLoopedPosition( samplePosition );
+ }
+ virtual void UpdateSamples( char *pData, int sampleCount )
+ {
+ ConvertSamples( pData, sampleCount );
+ }
+ virtual int GetLoopingInfo( int *pLoopBlock, int *pNumLeadingSamples, int *pNumTrailingSamples )
+ {
+ return CAudioSourceWave::GetLoopingInfo( pLoopBlock, pNumLeadingSamples, pNumTrailingSamples );
+ }
+
+ virtual void Prefetch();
+
+ virtual int SampleToStreamPosition( int samplePosition );
+ virtual int StreamToSamplePosition( int streamPosition );
+
+private:
+ CAudioSourceStreamWave( const CAudioSourceStreamWave & ); // not implemented, not accessible
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Save a copy of the file name for instances to open later
+// Input : *pFileName - filename
+//-----------------------------------------------------------------------------
+CAudioSourceStreamWave::CAudioSourceStreamWave( CSfxTable *pSfx ) : CAudioSourceWave( pSfx )
+{
+ m_pSfx = pSfx;
+ m_dataStart = -1;
+ m_dataSize = 0;
+ m_sampleCount = 0;
+
+ if ( IsX360() )
+ {
+ bool bValid = GetXboxAudioStartupData();
+ if ( !bValid )
+ {
+ // failed, substitute placeholder
+ pSfx->m_bUseErrorFilename = true;
+ bValid = GetXboxAudioStartupData();
+ if ( bValid )
+ {
+ DevWarning( "Failed to load sound \"%s\", substituting \"%s\"\n", pSfx->getname(), pSfx->GetFileName() );
+ }
+ }
+ }
+}
+
+CAudioSourceStreamWave::CAudioSourceStreamWave( CSfxTable *pSfx, CAudioSourceCachedInfo *info ) :
+ CAudioSourceWave( pSfx, info )
+{
+ m_pSfx = pSfx;
+ m_dataStart = info->DataStart();
+ m_dataSize = info->DataSize();
+
+ m_sampleCount = info->SampleCount();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: free the filename buffer
+//-----------------------------------------------------------------------------
+CAudioSourceStreamWave::~CAudioSourceStreamWave( void )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create an instance (mixer & wavedata) of this sound
+// Output : CAudioMixer * - pointer to the mixer
+//-----------------------------------------------------------------------------
+CAudioMixer *CAudioSourceStreamWave::CreateMixer( int initialStreamPosition )
+{
+ char fileName[MAX_PATH];
+ const char *pFileName = m_pSfx->GetFileName();
+ if ( IsX360() && ( m_format == WAVE_FORMAT_XMA || m_format == WAVE_FORMAT_PCM ) )
+ {
+ V_strcpy_safe( fileName, pFileName );
+ V_SetExtension( fileName, ".360.wav", sizeof( fileName ) );
+ pFileName = fileName;
+
+ // for safety, validate the initial stream position
+ // not trusting save/load
+ if ( m_format == WAVE_FORMAT_XMA )
+ {
+ if ( ( initialStreamPosition % XBOX_DVD_SECTORSIZE ) ||
+ ( initialStreamPosition % XMA_BLOCK_SIZE ) ||
+ ( initialStreamPosition >= m_dataSize ) )
+ {
+ initialStreamPosition = 0;
+ }
+ }
+ }
+
+ // BUGBUG: Source constructs the IWaveData, mixer frees it, fix this?
+ IWaveData *pWaveData = CreateWaveDataStream( *this, static_cast<IWaveStreamSource *>(this), pFileName, m_dataStart, m_dataSize, m_pSfx, initialStreamPosition );
+ if ( pWaveData )
+ {
+ CAudioMixer *pMixer = CreateWaveMixer( pWaveData, m_format, m_channels, m_bits, initialStreamPosition );
+ if ( pMixer )
+ {
+ return pMixer;
+ }
+
+ // no mixer, delete the stream buffer/instance
+ delete pWaveData;
+ }
+
+ return NULL;
+}
+
+void CAudioSourceStreamWave::Prefetch()
+{
+ PrefetchDataStream( m_pSfx->GetFileName(), m_dataStart, m_dataSize );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAudioSourceStreamWave::SampleToStreamPosition( int samplePosition )
+{
+ if ( IsPC() )
+ {
+ // not for PC
+ Assert( 0 );
+ return 0;
+ }
+
+ if ( m_format != WAVE_FORMAT_XMA || !m_nHeaderSize )
+ {
+ // not in the expected format or lacking the seek table
+ return 0;
+ }
+
+ // Run through the seek table to find the block closest to the desired sample.
+ // Each seek table entry is the index (counting from the beginning of the file)
+ // of the first sample in the corresponding block, but there's no entry for the
+ // first block (since the index would always be zero).
+ int *pSeekTable = (int*)m_pHeader;
+ int packet = 0;
+ for ( int i = 0; i < m_nHeaderSize/(int)sizeof( int ); ++i )
+ {
+ if ( samplePosition < pSeekTable[i] )
+ {
+ packet = i;
+ break;
+ }
+ }
+
+ int streamPosition = ( packet == 0 ) ? 0 : ( packet - 1 ) * 2048;
+ return streamPosition;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CAudioSourceStreamWave::StreamToSamplePosition( int streamPosition )
+{
+ if ( IsPC() )
+ {
+ // not for PC
+ Assert( 0 );
+ return 0;
+ }
+
+ if ( m_format != WAVE_FORMAT_XMA || !m_nHeaderSize )
+ {
+ // not in the expected format or lacking the seek table
+ return 0;
+ }
+
+ int packet = streamPosition/2048;
+ if ( packet <= 0 )
+ {
+ return 0;
+ }
+ if ( packet > m_nHeaderSize/(int)sizeof( int ) )
+ {
+ return m_numDecodedSamples;
+ }
+
+ return ((int*)m_pHeader)[packet - 1];
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Parse a stream wave file chunk
+// unlike the in-memory file, don't load the data, just get a reference to it.
+// Input : &walk - RIFF file
+//-----------------------------------------------------------------------------
+void CAudioSourceStreamWave::ParseChunk( IterateRIFF &walk, int chunkName )
+{
+ // NOTE: It would be nice to break out of parsing once we have the data start and
+ // save seeking over the whole file. But to do so, the other needed chunks must occur
+ // before the DATA chunk. But, that is not standard and breaks most other wav parsers.
+
+ switch( chunkName )
+ {
+ case WAVE_DATA:
+ // data starts at chunk + 8 (chunk name, chunk size = 2*4=8 bytes)
+ // don't load the data, just know where it is so each instance
+ // can load it later
+ m_dataStart = walk.ChunkFilePosition() + 8;
+ m_dataSize = walk.ChunkSize();
+ m_sampleCount = m_dataSize / m_sampleSize;
+ return;
+ }
+ CAudioSourceWave::ParseChunk( walk, chunkName );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: This is not implemented here. This source has no data. It is the
+// WaveData's responsibility to load/serve the data
+//-----------------------------------------------------------------------------
+int CAudioSourceStreamWave::GetOutputData( void **pData, int samplePosition, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ return 0;
+}
+
+int CAudioSourceStreamWave::GetCacheStatus( void )
+{
+ if ( !m_dataSize || !m_dataStart )
+ {
+ // didn't get precached properly
+ return AUDIO_NOT_LOADED;
+ }
+
+ return AUDIO_IS_LOADED;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a wave audio source (streaming or in memory)
+// Input : *pName - file name (NOTE: CAUDIOSOURCE KEEPS A POINTER TO pSfx)
+// streaming - if true, don't load, stream each instance
+// Output : CAudioSource * - a new source
+//-----------------------------------------------------------------------------
+CAudioSource *CreateWave( CSfxTable *pSfx, bool bStreaming )
+{
+ Assert( pSfx );
+
+#if defined( _DEBUG )
+ // For some reason you can't usually do pSfx->getname() in the dev studio debugger, so for convenience we'll grab the name
+ // here in debug builds at least...
+ char const *pName = pSfx->getname();
+ NOTE_UNUSED( pName );
+#endif
+
+ CAudioSourceWave *pWave = NULL;
+
+ if ( IsPC() || !IsX360() )
+ {
+ // Caching should always work, so if we failed to cache, it's a problem reading the file data, etc.
+ bool bIsMapSound = pSfx->IsPrecachedSound();
+ CAudioSourceCachedInfo *pInfo = audiosourcecache->GetInfo( CAudioSource::AUDIO_SOURCE_WAV, bIsMapSound, pSfx );
+
+ if ( pInfo && pInfo->Type() != CAudioSource::AUDIO_SOURCE_UNK )
+ {
+ // create the source from this file
+ if ( bStreaming )
+ {
+ pWave = new CAudioSourceStreamWave( pSfx, pInfo );
+ }
+ else
+ {
+ pWave = new CAudioSourceMemWave( pSfx, pInfo );
+ }
+ }
+ }
+ else
+ {
+ // 360 does not use audio cache system
+ // create the desired type
+ if ( bStreaming )
+ {
+ pWave = new CAudioSourceStreamWave( pSfx );
+ }
+ else
+ {
+ pWave = new CAudioSourceMemWave( pSfx );
+ }
+ }
+
+ if ( pWave && !pWave->Format() )
+ {
+ // lack of format indicates failure
+ delete pWave;
+ pWave = NULL;
+ }
+
+ return pWave;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper for CreateWave()
+//-----------------------------------------------------------------------------
+CAudioSource *Audio_CreateStreamedWave( CSfxTable *pSfx )
+{
+#if defined( MP3_SUPPORT )
+ if ( Audio_IsMP3( pSfx->GetFileName() ) )
+ {
+ return Audio_CreateStreamedMP3( pSfx );
+ }
+#endif
+
+ return CreateWave( pSfx, true );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Wrapper for CreateWave()
+//-----------------------------------------------------------------------------
+CAudioSource *Audio_CreateMemoryWave( CSfxTable *pSfx )
+{
+#if defined( MP3_SUPPORT )
+ if ( Audio_IsMP3( pSfx->GetFileName() ) )
+ {
+ return Audio_CreateMemoryMP3( pSfx );
+ }
+#endif
+
+ return CreateWave( pSfx, false );
+}
+
+float GetMP3Duration_Helper( char const *filename );
+static float Audio_GetMP3Duration( char const *pName )
+{
+ // Deduce from file
+ return GetMP3Duration_Helper( pName );
+}
+
+
+void MaybeReportMissingWav( char const *wav )
+{
+ static CUtlSymbolTable wavErrors;
+
+ CUtlSymbol sym;
+ sym = wavErrors.Find( wav );
+ if ( UTL_INVAL_SYMBOL == sym )
+ {
+ // See if file exists
+ if ( g_pFullFileSystem->FileExists( wav ) )
+ {
+ DevWarning( "Bad Audio file '%s'\n", wav );
+ }
+ else
+ {
+ DevWarning( "Missing wav file '%s'\n", wav );
+ }
+ wavErrors.AddString( wav );
+ }
+}
+
+static float Audio_GetWaveDuration( char const *pName )
+{
+ if ( IsX360() )
+ {
+ // should have precached
+ return 0;
+ }
+
+ char formatBuffer[1024];
+ WAVEFORMATEX *pfmt = (WAVEFORMATEX *)formatBuffer;
+
+ InFileRIFF riff( pName, *g_pSndIO );
+
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ MaybeReportMissingWav( pName );
+ return 0.0f;
+ }
+
+ // set up the iterator for the whole file (root RIFF is a chunk)
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ int format = 0;
+ int formatSize = 0;
+ int sampleCount = 0;
+
+ // This chunk must be first as it contains the wave's format
+ // break out when we've parsed it
+ while ( walk.ChunkAvailable() && ( format == 0 || sampleCount == 0 ) )
+ {
+ switch( walk.ChunkName() )
+ {
+ case WAVE_FMT:
+ if ( walk.ChunkSize() <= sizeof( formatBuffer ) )
+ {
+ walk.ChunkRead( formatBuffer );
+ formatSize = walk.ChunkSize();
+ format = LittleWord( pfmt->wFormatTag );
+ }
+ break;
+ case WAVE_DATA:
+ if ( format != 0 )
+ {
+ int dataSize = walk.ChunkSize();
+ if ( format == WAVE_FORMAT_ADPCM )
+ {
+ // Dummy size for now
+ sampleCount = dataSize;
+ }
+ else
+ {
+ sampleCount = dataSize / ( LittleWord( pfmt->wBitsPerSample ) >> 3 );
+ }
+ }
+ break;
+ default:
+ ChunkError( walk.ChunkName() );
+ break;
+ }
+ walk.ChunkNext();
+ }
+
+ // Not really a WAVE file or no format chunk, bail
+ if ( !format || !sampleCount )
+ return 0.0f;
+
+ float sampleRate = LittleDWord( pfmt->nSamplesPerSec );
+
+ if ( format == WAVE_FORMAT_ADPCM )
+ {
+ // Determine actual duration
+ sampleCount = ADPCMSampleCount( (ADPCMWAVEFORMAT *)formatBuffer, sampleCount );
+ }
+
+ return (float)sampleCount / sampleRate;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Fast method for determining duration of .wav/.mp3, exposed to server as well
+// Input : *pName -
+// Output : float
+//-----------------------------------------------------------------------------
+float AudioSource_GetSoundDuration( char const *pName )
+{
+#if defined( MP3_SUPPORT )
+ if ( Audio_IsMP3( pName ) )
+ {
+ return Audio_GetMP3Duration( pName );
+ }
+#endif
+
+ CSfxTable *pSound = S_PrecacheSound( pName );
+ if ( pSound )
+ {
+ return AudioSource_GetSoundDuration( pSound );
+ }
+
+ return Audio_GetWaveDuration( pName );
+}
+
+float AudioSource_GetSoundDuration( CSfxTable *pSfx )
+{
+ if ( pSfx && pSfx->pSource )
+ {
+ return (float)pSfx->pSource->SampleCount() / (float)pSfx->pSource->SampleRate();
+ }
+
+ return 0;
+}
+
+CAudioSourceCachedInfo::CAudioSourceCachedInfo() :
+ infolong( 0 ),
+ flagsbyte( 0 ),
+ m_dataStart( 0 ),
+ m_dataSize( 0 ),
+ m_loopStart( 0 ),
+ m_sampleCount( 0 ),
+ m_usCachedDataSize( 0 ),
+ m_pCachedData( 0 ),
+ m_usHeaderSize( 0 ),
+ m_pHeader( 0 ),
+ m_pSentence( 0 )
+{
+}
+
+CAudioSourceCachedInfo& CAudioSourceCachedInfo::operator =( const CAudioSourceCachedInfo& src )
+{
+ if ( this == &src )
+ return *this;
+
+ infolong = src.infolong;
+ flagsbyte = src.flagsbyte;
+ SetDataStart( src.DataStart() );
+ SetDataSize( src.DataSize() );
+ SetLoopStart( src.LoopStart() );
+ SetSampleCount( src.SampleCount() );
+
+ CSentence *scopy = NULL;
+ if ( src.Sentence() )
+ {
+ scopy = new CSentence();
+ *scopy = *src.Sentence();
+ }
+ SetSentence( scopy );
+
+ byte *data = NULL;
+
+ Assert( src.CachedDataSize() == 0 || src.CachedData() );
+
+ m_usCachedDataSize = 0;
+
+ if ( src.CachedData() && src.CachedDataSize() > 0 )
+ {
+ SetCachedDataSize( src.CachedDataSize() );
+ data = new byte[ src.CachedDataSize() ];
+ Assert( data );
+ Q_memcpy( data, src.CachedData(), src.CachedDataSize() );
+ }
+
+ SetCachedData( data );
+
+ data = NULL;
+
+ Assert( src.HeaderSize() == 0 || src.HeaderData() );
+
+ m_usHeaderSize = 0;
+
+ if ( src.HeaderData() && src.HeaderSize() > 0 )
+ {
+ SetHeaderSize( src.HeaderSize() );
+ data = new byte[ src.HeaderSize() ];
+ Assert( data );
+ Q_memcpy( data, src.HeaderData(), src.HeaderSize() );
+ }
+
+ SetHeaderData( data );
+
+ return *this;
+}
+
+CAudioSourceCachedInfo::CAudioSourceCachedInfo( const CAudioSourceCachedInfo& src )
+{
+ if ( this == &src )
+ {
+ Assert( 0 );
+ return;
+ }
+
+ infolong = src.infolong;
+ flagsbyte = src.flagsbyte;
+ SetDataStart( src.DataStart() );
+ SetDataSize( src.DataSize() );
+ SetLoopStart( src.LoopStart() );
+ SetSampleCount( src.SampleCount() );
+
+ CSentence *scopy = NULL;
+ if ( src.Sentence() )
+ {
+ scopy = new CSentence();
+ *scopy = *src.Sentence();
+ }
+ SetSentence( scopy );
+
+ byte *data = NULL;
+
+ Assert( src.CachedDataSize() == 0 || src.CachedData() );
+
+ m_usCachedDataSize = 0;
+
+ if ( src.CachedData() && src.CachedDataSize() > 0 )
+ {
+ SetCachedDataSize( src.CachedDataSize() );
+ data = new byte[ src.CachedDataSize() ];
+ Assert( data );
+ Q_memcpy( data, src.CachedData(), src.CachedDataSize() );
+ }
+
+ SetCachedData( data );
+
+ data = NULL;
+
+ Assert( src.HeaderSize() == 0 || src.HeaderData() );
+
+ m_usHeaderSize = 0;
+
+ if ( src.HeaderData() && src.HeaderSize() > 0 )
+ {
+ SetHeaderSize( src.HeaderSize() );
+ data = new byte[ src.HeaderSize() ];
+ Assert( data );
+ Q_memcpy( data, src.HeaderData(), src.HeaderSize() );
+ }
+
+ SetHeaderData( data );
+}
+
+CAudioSourceCachedInfo::~CAudioSourceCachedInfo()
+{
+ Clear();
+}
+
+void CAudioSourceCachedInfo::Clear()
+{
+ infolong = 0;
+ flagsbyte = 0;
+ m_dataStart = 0;
+ m_dataSize = 0;
+ m_loopStart = 0;
+ m_sampleCount = 0;
+
+ delete m_pSentence;
+ m_pSentence = NULL;
+
+ delete[] m_pCachedData;
+ m_pCachedData = NULL;
+ m_usCachedDataSize = 0;
+
+ delete[] m_pHeader;
+ m_pHeader = NULL;
+ m_usHeaderSize = 0;
+}
+
+void CAudioSourceCachedInfo::RemoveData()
+{
+ delete[] m_pCachedData;
+ m_pCachedData = NULL;
+ m_usCachedDataSize = 0;
+ flags.m_bCachedData = false;
+}
+
+void CAudioSourceCachedInfo::Save( CUtlBuffer& buf )
+{
+ buf.PutInt( infolong );
+ buf.PutChar( flagsbyte );
+ buf.PutInt( m_dataStart );
+ buf.PutInt( m_dataSize );
+ buf.PutInt( m_loopStart );
+ buf.PutInt( m_sampleCount );
+
+ if ( flags.m_bSentence )
+ {
+ m_pSentence->CacheSaveToBuffer( buf, CACHED_SENTENCE_VERSION );
+ }
+
+ Assert( m_usCachedDataSize < 65535 );
+
+ if ( flags.m_bCachedData && m_pCachedData )
+ {
+ buf.PutInt( m_usCachedDataSize );
+ buf.Put( m_pCachedData, m_usCachedDataSize );
+ }
+
+ Assert( m_usHeaderSize <= 32767 );
+
+ if ( flags.m_bHeader )
+ {
+ buf.PutShort( m_usHeaderSize );
+ buf.Put( m_pHeader, m_usHeaderSize );
+ }
+}
+
+void CAudioSourceCachedInfo::Restore( CUtlBuffer& buf )
+{
+ // Wipe any old data!!!
+ Clear();
+
+ infolong = buf.GetInt();
+ flagsbyte = buf.GetChar();
+ m_dataStart = buf.GetInt();
+ m_dataSize = buf.GetInt();
+ m_loopStart = buf.GetInt();
+ m_sampleCount = buf.GetInt();
+ if ( flags.m_bSentence )
+ {
+ m_pSentence = new CSentence();
+ Assert( m_pSentence );
+ m_pSentence->CacheRestoreFromBuffer( buf );
+ }
+
+ if ( flags.m_bCachedData )
+ {
+ m_usCachedDataSize = buf.GetInt();
+ Assert( m_usCachedDataSize > 0 && m_usCachedDataSize < 65535 );
+ if ( m_usCachedDataSize > 0 )
+ {
+ byte *data = new byte[ m_usCachedDataSize ];
+ buf.Get( data, m_usCachedDataSize );
+ SetCachedData( data );
+ }
+ }
+
+ if ( flags.m_bHeader )
+ {
+ m_usHeaderSize = buf.GetShort();
+ Assert( m_usHeaderSize > 0 && m_usHeaderSize <= 32767 );
+ if ( m_usHeaderSize > 0 )
+ {
+ byte *data = new byte[ m_usHeaderSize ];
+ buf.Get( data, m_usHeaderSize );
+ SetHeaderData( data );
+ }
+ }
+}
+
+int CAudioSourceCachedInfo::s_CurrentType = CAudioSource::AUDIO_SOURCE_MAXTYPE;
+CSfxTable *CAudioSourceCachedInfo::s_pSfx = NULL;
+bool CAudioSourceCachedInfo::s_bIsPrecacheSound = false;
+
+void CAudioSourceCachedInfo::Rebuild( char const *filename )
+{
+ // Wipe any old data
+ Clear();
+
+ Assert( s_pSfx );
+ Assert( s_CurrentType != CAudioSource::AUDIO_SOURCE_MAXTYPE );
+
+#if 0
+ // Never cachify something which is not in the client precache list
+ if ( s_bIsPrecacheSound != s_pSfx->IsPrecachedSound() )
+ {
+ Msg( "Logic bug, precaching entry for '%s' which is not in precache list\n",
+ filename );
+ }
+#endif
+
+ SetType( s_CurrentType );
+
+ CAudioSource *as = NULL;
+
+ // Note though these instantiate a specific AudioSource subclass, it doesn't matter, we just need one for .wav and one for .mp3
+ switch ( s_CurrentType )
+ {
+ default:
+ case CAudioSource::AUDIO_SOURCE_VOICE:
+ break;
+ case CAudioSource::AUDIO_SOURCE_WAV:
+ as = new CAudioSourceMemWave( s_pSfx );
+ break;
+ case CAudioSource::AUDIO_SOURCE_MP3:
+#if defined( MP3_SUPPORT )
+ as = new CAudioSourceMP3Cache( s_pSfx );
+#endif
+ break;
+ }
+
+ if ( as )
+ {
+ as->GetCacheData( this );
+ delete as;
+ }
+}
+
+// Versions
+// 3: The before time
+// 4: Changed MP3 caching to ensure we store proper sample rate, removed hack to not cache vo/
+// 5: Fixed bug that could result in incorrect mp3 datasizes in the sound cache
+#define AUDIOSOURCE_CACHE_VERSION 5
+class CAudioSourceCache : public IAudioSourceCache
+{
+public:
+
+ struct SearchPathCache : CUtlCachedFileData< CAudioSourceCachedInfo >
+ {
+ SearchPathCache( const char *pszRepositoryFilename, const char *pszSearchPath, UtlCachedFileDataType_t eOutOfDateMethod )
+ : CUtlCachedFileData( pszRepositoryFilename, AUDIOSOURCE_CACHE_VERSION, AsyncLookaheadMetaChecksum, eOutOfDateMethod )
+ {
+ V_strcpy_safe( m_szSearchPath, pszSearchPath );
+
+ // Delete any existing cache if it's out of date
+ IsUpToDate();
+
+ // Load up existing cache file
+ Init();
+ }
+
+ char m_szSearchPath[ MAX_PATH ];
+
+ virtual ~SearchPathCache()
+ {
+ Shutdown();
+ }
+ };
+
+
+ CAudioSourceCache()
+ {
+ m_nServerCount = -1;
+ m_bSndCacheDebug = false;
+ }
+
+ bool Init( unsigned int memSize );
+ void Shutdown();
+
+ void CheckSaveDirtyCaches();
+ void CheckCacheBuild();
+ void BuildCache( char const *pszSearchPath );
+
+ static unsigned int AsyncLookaheadMetaChecksum( void );
+
+ void LevelInit( char const *mapname );
+ void LevelShutdown();
+
+ virtual CAudioSourceCachedInfo *GetInfo( int audiosourcetype, bool soundisprecached, CSfxTable *sfx );
+ virtual void RebuildCacheEntry( int audiosourcetype, bool soundisprecached, CSfxTable *sfx );
+ virtual void ForceRecheckDiskInfo();
+
+private:
+ SearchPathCache *LookUpCacheEntry( const char *szCleanedFilename, int audiosourcetype, bool soundisprecached, CSfxTable *sfx );
+
+ SearchPathCache *FindCacheForSearchPath( const char *pszSearchPath );
+ SearchPathCache *CreateCacheForSearchPath( const char *pszSearchPath );
+
+ static void GetSoundFilename( char *szResult, int nResultSize, const char *pszInputFilename );
+
+ void RemoveCache( char const *cachename );
+
+ // List of all loaded caches
+ CUtlVector<SearchPathCache*> m_vecCaches;
+
+ int m_nServerCount;
+ bool m_bSndCacheDebug;
+};
+
+static CAudioSourceCache g_ASCache;
+IAudioSourceCache *audiosourcecache = &g_ASCache;
+
+unsigned int CAudioSourceCachedInfoHandle_t::s_nCurrentFlushCount = 1;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceCachedInfoHandle_t::InvalidateCache()
+{
+ ++s_nCurrentFlushCount;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CAudioSourceCache::Init( unsigned int memSize )
+{
+#if defined( _DEBUG )
+ Msg( "CAudioSourceCache: Init\n" );
+#endif
+
+ m_bSndCacheDebug = CommandLine()->FindParm( "-sndcachedebug" ) ? true : false;
+
+ if ( !wavedatacache->Init( memSize ) )
+ {
+ Error( "Unable to init wavedatacache system\n" );
+ return false;
+ }
+
+ if ( IsX360() )
+ {
+ // 360 doesn't use audio source caches
+ return true;
+ }
+
+ // Gather up list of search paths
+ CUtlVector< CUtlString > vecSearchPaths;
+ GetSearchPath( vecSearchPaths, "game" );
+
+ // Create corresponding caches
+ FOR_EACH_VEC( vecSearchPaths, idxSearchPath )
+ {
+
+ // Standardize the name
+ char szSearchPath[ MAX_PATH ];
+ V_strcpy_safe( szSearchPath, vecSearchPaths[idxSearchPath] );
+ V_FixSlashes( szSearchPath );
+ V_AppendSlash( szSearchPath, sizeof(szSearchPath ) );
+
+ // See if we already have a cache for this search path.
+ bool bFound = false;
+ FOR_EACH_VEC( m_vecCaches, idxCache )
+ {
+ if ( V_stricmp( szSearchPath, m_vecCaches[idxCache]->m_szSearchPath ) == 0 )
+ {
+ Assert( V_strcmp( szSearchPath, m_vecCaches[idxCache]->m_szSearchPath ) == 0 ); // case *should* match exactly
+ bFound = true;
+ break;
+ }
+ }
+ if ( bFound )
+ continue;
+
+ // Add a ceche
+ SearchPathCache *pCache = CreateCacheForSearchPath( szSearchPath );
+ m_vecCaches.AddToTail( pCache );
+ }
+
+ return true;
+}
+
+CAudioSourceCache::SearchPathCache *CAudioSourceCache::FindCacheForSearchPath( const char *pszSearchPath )
+{
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ SearchPathCache *pCache = m_vecCaches[idx];
+ if ( V_stricmp( pCache->m_szSearchPath, pszSearchPath ) == 0 )
+ {
+ return pCache;
+ }
+ }
+
+ return NULL;
+}
+
+CAudioSourceCache::SearchPathCache *CAudioSourceCache::CreateCacheForSearchPath( const char *pszSearchPath )
+{
+
+ // Make sure search path ends in a slash
+ char szSearchPath[ MAX_PATH ];
+ V_strcpy_safe( szSearchPath, pszSearchPath );
+ V_AppendSlash( szSearchPath, sizeof(szSearchPath) );
+
+ // Set the filename for the cache.
+ UtlCachedFileDataType_t eOutOfDateMethod = UTL_CACHED_FILE_USE_FILESIZE;
+ char szCacheName[ MAX_PATH + 32 ];
+ V_strcpy_safe( szCacheName, szSearchPath );
+ char *dotVpkSlash = V_stristr( szCacheName, ".vpk" CORRECT_PATH_SEPARATOR_S );
+ if ( dotVpkSlash )
+ {
+ Assert( dotVpkSlash[5] == '\0' );
+ char *d = dotVpkSlash+4; // backup to where the slash is
+ Assert( *d == CORRECT_PATH_SEPARATOR );
+ V_strcpy( d, ".sound.cache" );
+ }
+ else
+ {
+ V_strcat_safe( szCacheName, "sound" CORRECT_PATH_SEPARATOR_S "sound.cache" );
+ eOutOfDateMethod = UTL_CACHED_FILE_USE_TIMESTAMP;
+ }
+
+ return new SearchPathCache( szCacheName, szSearchPath, eOutOfDateMethod );
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::Shutdown()
+{
+#if defined( _DEBUG )
+ Msg( "CAudioSourceCache: Shutdown\n" );
+#endif
+
+ CheckSaveDirtyCaches();
+ m_vecCaches.PurgeAndDeleteElements();
+
+ wavedatacache->Shutdown();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called by Host_Init on engine startup to rebuild everything if needed
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::CheckCacheBuild()
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ // !FIXME! We'll just do everything lazily for now!
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::CheckSaveDirtyCaches()
+{
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ SearchPathCache *pCache = m_vecCaches[idx];
+ if ( pCache->IsDirty() && pCache->GetNumElements() > 0 )
+ {
+ Msg( "Saving %s\n", pCache->GetRepositoryFileName() );
+ pCache->Save();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Static method
+// Output : unsigned int
+//-----------------------------------------------------------------------------
+unsigned int CAudioSourceCache::AsyncLookaheadMetaChecksum( void )
+{
+ if ( IsX360() )
+ {
+ return 0;
+ }
+
+ CRC32_t crc;
+ CRC32_Init( &crc );
+
+ float f = SND_ASYNC_LOOKAHEAD_SECONDS;
+ CRC32_ProcessBuffer( &crc, &f, sizeof( f ) );
+ // Finish
+ CRC32_Final( &crc );
+
+ return (unsigned int)crc;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *mapname -
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::LevelInit( char const *mapname )
+{
+ CheckSaveDirtyCaches();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::LevelShutdown()
+{
+ CheckSaveDirtyCaches();
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::GetSoundFilename( char *szResult, int nResultSize, const char *pszInputFilename )
+{
+ V_snprintf( szResult, nResultSize, "sound/%s", pszInputFilename );
+ V_FixSlashes( szResult );
+ V_RemoveDotSlashes( szResult );
+ V_strlower( szResult );
+}
+
+//-----------------------------------------------------------------------------
+CAudioSourceCache::SearchPathCache *CAudioSourceCache::LookUpCacheEntry( const char *fn, int audiosourcetype, bool soundisprecached, CSfxTable *sfx )
+{
+ if ( IsX360() )
+ {
+ return NULL;
+ }
+
+ // Hack to remember the type of audiosource to create if we need to recreate it
+ CAudioSourceCachedInfo::s_CurrentType = audiosourcetype;
+ CAudioSourceCachedInfo::s_pSfx = sfx;
+ CAudioSourceCachedInfo::s_bIsPrecacheSound = soundisprecached;
+
+ // Get cleaned up filename
+ char szRelFilename[ 256 ];
+ GetSoundFilename( szRelFilename, sizeof( szRelFilename ), sfx->GetFileName() );
+
+ // Get absolute filename. This thing had better exist in the filesystem somewhere
+ char szAbsFilename[ 1024 ];
+ if ( !g_pFullFileSystem->RelativePathToFullPath( szRelFilename, "game", szAbsFilename, sizeof(szAbsFilename) ) )
+ {
+ return NULL;
+ }
+
+ // now try to figure out which search path this corresponds to
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ SearchPathCache *pCache = m_vecCaches[idx];
+ if ( V_strncmp( pCache->m_szSearchPath, szAbsFilename, V_strlen( pCache->m_szSearchPath ) ) == 0 )
+ {
+ return pCache;
+ }
+ }
+ Warning( "Cannot figure out which search path %s came from. Absolute path is %s\n", szRelFilename, szAbsFilename );
+
+ SearchPathCache *pCache = NULL;
+
+ return pCache;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CAudioSourceCachedInfo *CAudioSourceCache::GetInfo( int audiosourcetype, bool soundisprecached, CSfxTable *sfx )
+{
+ VPROF("CAudioSourceCache::GetInfo");
+
+ if ( IsX360() )
+ {
+ // 360 not using
+ return NULL;
+ }
+
+ Assert( sfx );
+
+ char fn[ 512 ];
+ GetSoundFilename( fn, sizeof( fn ), sfx->GetFileName() );
+
+ CAudioSourceCachedInfo *info = NULL;
+ SearchPathCache *pCache = LookUpCacheEntry( fn, audiosourcetype, soundisprecached, sfx );
+ if ( !pCache )
+ return NULL;
+
+ info = pCache->Get( fn );
+
+// Is this applicable anymore now that we have a cache per search path?
+// if ( info && info->Format() == 0 )
+// {
+// if ( g_pFullFileSystem->FileExists( fn, "BSP" ) )
+// {
+// DevMsg( 1, "Forced rebuild of bsp cache sound '%s'\n", fn );
+// info = pCache->RebuildItem( fn );
+// Assert( info->Format() != 0 );
+// }
+// }
+
+ return info;
+}
+
+
+void CAudioSourceCache::RebuildCacheEntry( int audiosourcetype, bool soundisprecached, CSfxTable *sfx )
+{
+ VPROF("CAudioSourceCache::RebuildCacheEntry");
+
+ if ( IsX360() )
+ {
+ // 360 not using
+ return;
+ }
+
+ Assert( sfx );
+
+ char fn[ 512 ];
+ GetSoundFilename( fn, sizeof( fn ), sfx->GetFileName() );
+ SearchPathCache *pCache = LookUpCacheEntry( fn, audiosourcetype, soundisprecached, sfx );
+ if ( !pCache )
+ return;
+
+ pCache->RebuildItem( fn );
+}
+
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::ForceRecheckDiskInfo()
+{
+ FOR_EACH_VEC( m_vecCaches, idx )
+ {
+ m_vecCaches[ idx ]->ForceRecheckDiskInfo();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::RemoveCache( char const *cachename )
+{
+ if ( IsX360() )
+ {
+ return;
+ }
+
+ if ( g_pFullFileSystem->FileExists( cachename, "MOD" ) )
+ {
+ if ( !g_pFullFileSystem->IsFileWritable( cachename, "MOD" ) )
+ {
+ g_pFullFileSystem->SetFileWritable( cachename, true, "MOD" );
+ }
+ g_pFullFileSystem->RemoveFile( cachename, "MOD" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CAudioSourceCache::BuildCache( char const *pszSearchPath )
+{
+
+ // Get absolute path
+ char szAbsPath[ MAX_PATH ];
+ V_MakeAbsolutePath( szAbsPath, sizeof(szAbsPath), pszSearchPath );
+ V_FixSlashes( szAbsPath );
+
+ // Add a search path to the filesystem. We'll add one search path as a kludge so we
+ // can use the existing file finder system easily
+ g_pFullFileSystem->AddSearchPath( szAbsPath, "soundcache_kludge", PATH_ADD_TO_HEAD );
+
+ Msg( "Finding .wav files...\n");
+ CUtlVector< CUtlString > vecFilenames;
+ AddFilesToList( vecFilenames, "sound", "soundcache_kludge", "wav" );
+
+ Msg( "Finding .mp3 files...\n");
+ AddFilesToList( vecFilenames, "sound", "soundcache_kludge", "mp3" );
+
+ Msg( "Found %d audio files.\n", vecFilenames.Count() );
+
+ g_pFullFileSystem->RemoveSearchPaths( "soundcache_kludge" );
+
+ if ( vecFilenames.Count() < 1 )
+ {
+ Warning(" No audio files found. Not building cache\n" );
+ return;
+ }
+
+ // FindCacheForSearchPath expects an absolute search path, but if we're working with a VPK we'll have the path to
+ // the file, wherein the proper path to the file is /foo/bar.vpk, but the *search path* should be /foo/bar.vpk/
+ char szAsSearchPath[MAX_PATH] = { 0 };
+ V_strncpy( szAsSearchPath, szAbsPath, sizeof( szAsSearchPath ) );
+ V_AppendSlash( szAsSearchPath, sizeof( szAsSearchPath ) );
+
+ SearchPathCache *pCache = FindCacheForSearchPath( szAsSearchPath );
+ if ( !pCache )
+ {
+ // This cache might not have existed on startup
+ pCache = CreateCacheForSearchPath( szAbsPath );
+ m_vecCaches.AddToTail( pCache );
+ }
+
+ g_pFullFileSystem->AddSearchPath( szAbsPath, "game", PATH_ADD_TO_HEAD );
+ int nLenAbsPath = V_strlen( szAbsPath );
+ int iLastShownPct = -1;
+ FOR_EACH_VEC( vecFilenames, idxFilename )
+ {
+ const char *pszFilename = vecFilenames[ idxFilename ];
+ if ( V_strnicmp( pszFilename, szAbsPath, nLenAbsPath ) != 0 )
+ {
+ Warning( "Sound %s doesn't begin with search path %s\n", pszFilename, szAbsPath );
+ Assert( false );
+ continue;
+ }
+ const char *pszRelName = pszFilename + nLenAbsPath;
+ if ( *pszRelName == '/' || *pszRelName == '\\' )
+ ++pszRelName;
+ if ( V_strnicmp( pszRelName, "sound" CORRECT_PATH_SEPARATOR_S, 6 ) != 0 )
+ {
+ Warning( "Relative name %s doesn't begin with leading 'sound' directory?\n", pszRelName );
+ Assert( false );
+ continue;
+ }
+ const char *pszName = pszRelName + 6;
+
+ // Show progress
+ int iPct = idxFilename * 100 / vecFilenames.Count();
+ if ( iPct != iLastShownPct )
+ {
+ Msg( " %3d%% %s\n", iPct, pszName );
+ iLastShownPct = iPct;
+ }
+
+ CAudioSourceCachedInfo::s_bIsPrecacheSound = true;
+ CAudioSourceCachedInfo::s_CurrentType = CAudioSource::AUDIO_SOURCE_WAV;
+ char szExt[ 10 ] = { 0 };
+ V_ExtractFileExtension( pszFilename, szExt, sizeof( szExt ) );
+ if ( V_stricmp( szExt, "mp3" ) == 0 )
+ {
+ CAudioSourceCachedInfo::s_CurrentType = CAudioSource::AUDIO_SOURCE_MP3;
+ }
+ CAudioSourceCachedInfo::s_pSfx = S_DummySfx( pszName );
+
+ const CAudioSourceCachedInfo *pInfo = pCache->Get( pszRelName );
+ if ( !pInfo )
+ {
+ Warning( "Failed to cache info for %s\n", pszFilename );
+ }
+ }
+ g_pFullFileSystem->RemoveSearchPath( szAbsPath, "game" );
+
+ if ( pCache->IsDirty() )
+ {
+ Msg( "Saving %s\n", pCache->GetRepositoryFileName() );
+ pCache->Save();
+ }
+ else
+ {
+ Msg( "No changes detected; not saving %s\n", pCache->GetRepositoryFileName() );
+ }
+}
+
+void CheckCacheBuild()
+{
+ g_ASCache.CheckCacheBuild();
+}
+
+CON_COMMAND( snd_buildcache, "<directory or VPK filename> Rebulds sound cache for a given search path.\n" )
+{
+ if ( args.ArgC() < 2 )
+ {
+ ConMsg( "Usage: snd_buildcache <directory or VPK filename>\n" );
+ return;
+ }
+
+ // Allow them to eitehr specify multiple args, or comma-seperated list.
+ // You cannot easily pas multiple args on the (OS) command line.
+ for ( int idxArg = 1 ; idxArg < args.ArgC() ; ++idxArg )
+ {
+ CUtlStringList vecPaths;
+ V_SplitString( args[idxArg], ",", vecPaths );
+ FOR_EACH_VEC( vecPaths, idxPath )
+ {
+ g_ASCache.BuildCache( vecPaths[idxPath] );
+ }
+ }
+
+ // And now quit the game, because we mucked with search paths and the game is almost certainly not
+ // going to work anymore
+ Msg( "Quitting the game because we probably screwed up the search paths...\n" );
+ extern void HostState_Shutdown();
+ HostState_Shutdown();
+}
+