You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2816 lines
72 KiB
2816 lines
72 KiB
//========= 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 ) |
|
{ |
|
intp 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_CreateMemoryMP3(pSfx); // TOSUCK: Dont work with streamed mp3, idk why |
|
} |
|
#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(); |
|
} |
|
|
|
|