diff options
Diffstat (limited to 'engine/audio/private/snd_wave_source.cpp')
| -rw-r--r-- | engine/audio/private/snd_wave_source.cpp | 2816 |
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(); +} + |