//========= 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_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();
}