summaryrefslogtreecommitdiff
path: root/utils/xbox/MakeGameData/MakeSounds.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/xbox/MakeGameData/MakeSounds.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'utils/xbox/MakeGameData/MakeSounds.cpp')
-rw-r--r--utils/xbox/MakeGameData/MakeSounds.cpp968
1 files changed, 968 insertions, 0 deletions
diff --git a/utils/xbox/MakeGameData/MakeSounds.cpp b/utils/xbox/MakeGameData/MakeSounds.cpp
new file mode 100644
index 0000000..f0913ce
--- /dev/null
+++ b/utils/xbox/MakeGameData/MakeSounds.cpp
@@ -0,0 +1,968 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: .360.WAV Creation
+//
+//=====================================================================================//
+
+#include "MakeGameData.h"
+#ifndef NO_X360_XDK
+#include <XMAEncoder.h>
+#endif
+#include "datamap.h"
+#include "sentence.h"
+#include "tier2/riff.h"
+#include "resample.h"
+#include "xwvfile.h"
+
+// all files are built for streaming compliance
+// allows for fastest runtime loading path
+// actual streaming or static state is determined by engine
+#define XBOX_DVD_SECTORSIZE 2048
+#define XMA_BLOCK_SIZE 2048 // must be aligned to 1024
+#define MAX_CHUNKS 256
+
+// [0,100]
+#define XMA_HIGH_QUALITY 90
+#define XMA_DEFAULT_QUALITY 75
+#define XMA_MEDIUM_QUALITY 50
+#define XMA_LOW_QUALITY 25
+
+typedef struct
+{
+ unsigned int id;
+ int size;
+ byte *pData;
+} chunk_t;
+
+struct conversion_t
+{
+ const char *pSubDir;
+ int quality;
+ bool bForceTo22K;
+};
+
+// default conversion rules
+conversion_t g_defaultConversionRules[] =
+{
+ // subdir quality 22Khz
+ { "", XMA_DEFAULT_QUALITY, false }, // default settings
+ { "weapons", XMA_DEFAULT_QUALITY, false },
+ { "music", XMA_DEFAULT_QUALITY, false },
+ { "vo", XMA_MEDIUM_QUALITY, false },
+ { "npc", XMA_MEDIUM_QUALITY, false },
+ { "ambient", XMA_DEFAULT_QUALITY, false },
+ { "commentary", XMA_LOW_QUALITY, true },
+ { NULL },
+};
+
+// portal conversion rules
+conversion_t g_portalConversionRules[] =
+{
+ // subdir quality 22Khz
+ { "", XMA_DEFAULT_QUALITY, false }, // default settings
+ { "commentary", XMA_LOW_QUALITY, true },
+ { NULL },
+};
+
+chunk_t g_chunks[MAX_CHUNKS];
+int g_numChunks;
+
+extern IFileReadBinary *g_pSndIO;
+
+//-----------------------------------------------------------------------------
+// Purpose: chunk printer
+//-----------------------------------------------------------------------------
+void PrintChunk( unsigned int chunkName, int size )
+{
+ char c[4];
+
+ for ( int i=0; i<4; i++ )
+ {
+ c[i] = ( chunkName >> i*8 ) & 0xFF;
+ if ( !c[i] )
+ c[i] = ' ';
+ }
+
+ Msg( "%c%c%c%c: %d bytes\n", c[0], c[1], c[2], c[3], size );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: which chunks are supported, false to ignore
+//-----------------------------------------------------------------------------
+bool IsValidChunk( unsigned int chunkName )
+{
+ switch ( chunkName )
+ {
+ case WAVE_DATA:
+ case WAVE_CUE:
+ case WAVE_SAMPLER:
+ case WAVE_VALVEDATA:
+ case WAVE_FMT:
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: align buffer
+//-----------------------------------------------------------------------------
+int AlignToBoundary( CUtlBuffer &buf, int alignment )
+{
+ int curPosition;
+ int newPosition;
+ byte padByte = 0;
+
+ buf.SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
+ curPosition = buf.TellPut();
+
+ if ( alignment <= 1 )
+ return curPosition;
+
+ // advance to aligned position
+ newPosition = AlignValue( curPosition, alignment );
+ buf.EnsureCapacity( newPosition );
+
+ // write empty
+ for ( int i=0; i<newPosition-curPosition; i++ )
+ {
+ buf.Put( &padByte, 1 );
+ }
+
+ return newPosition;
+}
+
+//--------------------------------------------------------------------------------------
+// SampleToXMABlockOffset
+//
+// Description: converts from a sample index to a block index + the number of samples
+// to offset from the beginning of the block.
+//
+// Parameters:
+// dwSampleIndex: sample index to convert
+// pdwSeekTable: pointer to the file's XMA2 seek table
+// nEntries: number of DWORD entries in the seek table
+// out_pBlockIndex: index of block where the desired sample lives
+// out_pOffset: number of samples in the block before the desired sample
+//--------------------------------------------------------------------------------------
+bool SampleToXMABlockOffset( DWORD dwSampleIndex, const DWORD *pdwSeekTable, DWORD nEntries, DWORD *out_pBlockIndex, DWORD *out_pOffset )
+{
+ // 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).
+ bool bFound = false;
+ for ( DWORD i = 0; !bFound && i < nEntries; ++i )
+ {
+ if ( dwSampleIndex < BigLong( pdwSeekTable[i] ) )
+ {
+ *out_pBlockIndex = i;
+ bFound = true;
+ }
+ }
+
+ // Calculate the sample offset by figuring out what the sample index of the first sample
+ // in the block is, then subtracting that from dwSampleIndex.
+ if ( bFound )
+ {
+ DWORD dwStartOfBlock = (*out_pBlockIndex == 0) ? 0 : BigLong( pdwSeekTable[*out_pBlockIndex - 1] );
+ *out_pOffset = dwSampleIndex - dwStartOfBlock;
+ }
+
+ return bFound;
+}
+
+//-----------------------------------------------------------------------------
+// Compile and compress vdat
+//-----------------------------------------------------------------------------
+bool CompressVDAT( chunk_t *pChunk )
+{
+ CSentence *pSentence;
+
+ CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
+
+ buf.EnsureCapacity( pChunk->size );
+ memcpy( buf.Base(), pChunk->pData, pChunk->size );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, pChunk->size );
+
+ pSentence = new CSentence();
+
+ // Make binary version of VDAT
+ // Throws all phonemes into one word, discards sentence memory, etc.
+ pSentence->InitFromDataChunk( buf.Base(), buf.TellPut() );
+ pSentence->MakeRuntimeOnly();
+ CUtlBuffer binaryBuffer( 0, 0, 0 );
+ binaryBuffer.SetBigEndian( true );
+ pSentence->CacheSaveToBuffer( binaryBuffer, CACHED_SENTENCE_VERSION_ALIGNED );
+ delete pSentence;
+
+ unsigned int compressedSize = 0;
+ unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)binaryBuffer.Base(),
+ binaryBuffer.TellPut(), &compressedSize );
+ if ( pCompressedOutput )
+ {
+ if ( !g_bQuiet )
+ {
+ Msg( "CompressVDAT: Compressed %d to %d\n", binaryBuffer.TellPut(), compressedSize );
+ }
+
+ free( pChunk->pData );
+ pChunk->size = compressedSize;
+ pChunk->pData = pCompressedOutput;
+ }
+ else
+ {
+ // save binary VDAT as-is
+ free( pChunk->pData );
+ pChunk->size = binaryBuffer.TellPut();
+ pChunk->pData = (byte *)malloc( pChunk->size );
+ memcpy( pChunk->pData, binaryBuffer.Base(), pChunk->size );
+ }
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: read chunks into provided array
+//-----------------------------------------------------------------------------
+bool ReadChunks( const char *pFileName, int &numChunks, chunk_t chunks[MAX_CHUNKS] )
+{
+ numChunks = 0;
+
+ InFileRIFF riff( pFileName, *g_pSndIO );
+ if ( riff.RIFFName() != RIFF_WAVE )
+ {
+ return false;
+ }
+
+ IterateRIFF walk( riff, riff.RIFFSize() );
+
+ while ( walk.ChunkAvailable() )
+ {
+ chunks[numChunks].id = walk.ChunkName();
+ chunks[numChunks].size = walk.ChunkSize();
+
+ int size = chunks[numChunks].size;
+ if ( walk.ChunkName() == WAVE_FMT && size < sizeof( WAVEFORMATEXTENSIBLE ) )
+ {
+ // format chunks are variable and cast to different structures
+ // ensure the data footprint is at least the structure we want to manipulate
+ size = sizeof( WAVEFORMATEXTENSIBLE );
+ }
+
+ chunks[numChunks].pData = (byte *)malloc( size );
+ memset( chunks[numChunks].pData, 0, size );
+
+ walk.ChunkRead( chunks[numChunks].pData );
+
+ numChunks++;
+ if ( numChunks >= MAX_CHUNKS )
+ return false;
+
+ walk.ChunkNext();
+ }
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: promote pcm 8 bit to 16 bit pcm
+//-----------------------------------------------------------------------------
+void ConvertPCMDataChunk8To16( chunk_t *pFormatChunk, chunk_t *pDataChunk )
+{
+ WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData;
+
+ int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3;
+ int sampleCount = pDataChunk->size / sampleSize;
+ int outputSize = sizeof( short ) * ( sampleCount * pFormat->nChannels );
+ short *pOut = (short *)malloc( outputSize );
+
+ // in-place convert data from 8-bits to 16-bits
+ Convert8To16( pDataChunk->pData, pOut, sampleCount, pFormat->nChannels );
+
+ free( pDataChunk->pData );
+ pDataChunk->pData = (byte *)pOut;
+ pDataChunk->size = outputSize;
+
+ pFormat->wFormatTag = WAVE_FORMAT_PCM;
+ pFormat->nBlockAlign = 2 * pFormat->nChannels;
+ pFormat->wBitsPerSample = 16;
+ pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: convert adpcm to 16 bit pcm
+//-----------------------------------------------------------------------------
+void ConvertADPCMDataChunkTo16( chunk_t *pFormatChunk, chunk_t *pDataChunk )
+{
+ WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData;
+
+ int sampleCount = ADPCMSampleCount( (byte *)pFormat, pDataChunk->pData, pDataChunk->size );
+ int outputSize = sizeof( short ) * sampleCount * pFormat->nChannels;
+ short *pOut = (short *)malloc( outputSize );
+
+ // convert to PCM 16bit format
+ DecompressADPCMSamples( (byte*)pFormat, (byte*)pDataChunk->pData, pDataChunk->size, pOut );
+
+ free( pDataChunk->pData );
+ pDataChunk->pData = (byte *)pOut;
+ pDataChunk->size = outputSize;
+
+ pFormat->wFormatTag = WAVE_FORMAT_PCM;
+ pFormat->nBlockAlign = 2 * pFormat->nChannels;
+ pFormat->wBitsPerSample = 16;
+ pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels;
+
+ pFormatChunk->size = 16;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Decimate to 22K
+//-----------------------------------------------------------------------------
+void ConvertPCMDataChunk16To22K( chunk_t *pFormatChunk, chunk_t *pDataChunk )
+{
+ WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData;
+
+ if ( pFormat->nSamplesPerSec != 44100 || pFormat->wBitsPerSample != 16 || pFormat->wFormatTag != WAVE_FORMAT_PCM )
+ {
+ // not in expected format
+ return;
+ }
+
+ int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3;
+ int sampleCount = pDataChunk->size / sampleSize;
+ short *pOut = (short *)malloc( sizeof( short ) * ( sampleCount * pFormat->nChannels ) );
+
+ DecimateSampleRateBy2_16( (short *)pDataChunk->pData, pOut, sampleCount, pFormat->nChannels );
+
+ free( pDataChunk->pData );
+ pDataChunk->pData = (byte *)pOut;
+ pDataChunk->size = sizeof( short ) * ( sampleCount/2 * pFormat->nChannels );
+
+ pFormat->nSamplesPerSec = 22050;
+ pFormat->nBlockAlign = 2 * pFormat->nChannels;
+ pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: determine loop start
+//-----------------------------------------------------------------------------
+int FindLoopStart( int samplerChunk, int cueChunk )
+{
+ int loopStartFromCue = -1;
+ int loopStartFromSampler = -1;
+
+ if ( cueChunk != -1 )
+ {
+ struct cuechunk_t
+ {
+ unsigned int dwName;
+ unsigned int dwPosition;
+ unsigned int fccChunk;
+ unsigned int dwChunkStart;
+ unsigned int dwBlockStart;
+ unsigned int dwSampleOffset;
+ };
+ struct cueRIFF_t
+ {
+ int cueCount;
+ cuechunk_t cues[1];
+ };
+
+ cueRIFF_t *pCue = (cueRIFF_t *)g_chunks[cueChunk].pData;
+ if ( pCue->cueCount > 0 )
+ {
+ loopStartFromCue = pCue->cues[0].dwSampleOffset;
+ }
+ }
+
+ if ( samplerChunk != -1 )
+ {
+ struct SampleLoop
+ {
+ unsigned int dwIdentifier;
+ unsigned int dwType;
+ unsigned int dwStart;
+ unsigned int dwEnd;
+ unsigned int dwFraction;
+ unsigned int dwPlayCount;
+ };
+
+ struct samplerchunk_t
+ {
+ 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];
+ };
+
+ // assume that the loop end is the sample end
+ // assume that only the first loop is relevant
+ samplerchunk_t *pSampler = (samplerchunk_t *)g_chunks[samplerChunk].pData;
+ if ( pSampler->cSampleLoops > 0 )
+ {
+ // only support normal forward loops
+ if ( pSampler->Loops[0].dwType == 0 )
+ {
+ loopStartFromSampler = pSampler->Loops[0].dwStart;
+ }
+ }
+ }
+
+ return ( max( loopStartFromCue, loopStartFromSampler ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: returns chunk, -1 if not found
+//-----------------------------------------------------------------------------
+int FindChunk( unsigned int id )
+{
+ int i;
+ for ( i=0; i<g_numChunks; i++ )
+ {
+ if ( g_chunks[i].id == id )
+ {
+ return i;
+ }
+ }
+
+ // not found
+ return - 1;
+}
+
+bool EncodeAsXMA( const char *pDebugName, CUtlBuffer &targetBuff, int quality, bool bIsVoiceOver )
+{
+#ifdef NO_X360_XDK
+ return false;
+#else
+ int formatChunk = FindChunk( WAVE_FMT );
+ int dataChunk = FindChunk( WAVE_DATA );
+ if ( formatChunk == -1 || dataChunk == -1 )
+ {
+ // huh? these should have been pre-validated
+ return false;
+ }
+
+ int vdatSize = 0;
+ int vdatChunk = FindChunk( WAVE_VALVEDATA );
+ if ( vdatChunk != -1 )
+ {
+ vdatSize = g_chunks[vdatChunk].size;
+ }
+
+ int loopStart = FindLoopStart( FindChunk( WAVE_SAMPLER ), FindChunk( WAVE_CUE ) );
+
+ // format structure must be expected 16 bit PCM, otherwise encoder crashes
+ WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData;
+ pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nChannels * 2;
+ pFormat->nBlockAlign = 2 * pFormat->nChannels;
+ pFormat->cbSize = 0;
+
+ XMAENCODERSTREAM inputStream = { 0 };
+
+ WAVEFORMATEXTENSIBLE wfx;
+ Assert( g_chunks[formatChunk].size <= sizeof( WAVEFORMATEXTENSIBLE ) );
+ memcpy( &wfx, g_chunks[formatChunk].pData, g_chunks[formatChunk].size );
+ if ( g_chunks[formatChunk].size < sizeof( WAVEFORMATEXTENSIBLE ) )
+ {
+ memset( (unsigned char*)&wfx + g_chunks[formatChunk].size, 0, sizeof( WAVEFORMATEXTENSIBLE ) - g_chunks[formatChunk].size );
+ }
+
+ memcpy( &inputStream.Format, &wfx, sizeof( WAVEFORMATEX ) );
+ inputStream.pBuffer = g_chunks[dataChunk].pData;
+ inputStream.BufferSize = g_chunks[dataChunk].size;
+ if ( loopStart != -1 )
+ {
+ // can only support a single loop point until end of file
+ inputStream.LoopStart = loopStart;
+ inputStream.LoopLength = inputStream.BufferSize / ( pFormat->nChannels * sizeof( short ) ) - loopStart;
+ }
+
+ void *pXMAData = NULL;
+ DWORD XMADataSize = 0;
+ XMA2WAVEFORMAT *pXMA2Format = NULL;
+ DWORD XMA2FormatSize = 0;
+ DWORD *pXMASeekTable = NULL;
+ DWORD XMASeekTableSize = 0;
+ HRESULT hr = S_OK;
+
+ DWORD xmaFlags = XMAENCODER_NOFILTER;
+ if ( loopStart != -1 )
+ {
+ xmaFlags |= XMAENCODER_LOOP;
+ }
+
+ int numAttempts = 1;
+ while ( numAttempts < 10 )
+ {
+ hr = XMA2InMemoryEncoder( 1, &inputStream, quality, xmaFlags, XMA_BLOCK_SIZE/1024, &pXMAData, &XMADataSize, &pXMA2Format, &XMA2FormatSize, &pXMASeekTable, &XMASeekTableSize );
+ if ( !FAILED( hr ) )
+ break;
+
+ // make small jumps
+ quality += 5;
+ if ( quality > 100 )
+ quality = 100;
+ if ( !g_bQuiet )
+ {
+ Msg( "XMA Encoding Error on '%s', Attempting increasing quality to %d\n", pDebugName, quality );
+ }
+
+ numAttempts++;
+
+ pXMAData = NULL;
+ XMADataSize = 0;
+ pXMA2Format = NULL;
+ XMA2FormatSize = 0;
+ pXMASeekTable = NULL;
+ XMASeekTableSize = 0;
+ }
+
+ if ( FAILED( hr ) )
+ {
+ // unrecoverable
+ return false;
+ }
+ else if ( numAttempts > 1 )
+ {
+ if ( !g_bQuiet )
+ {
+ Msg( "XMA Encoding Success on '%s' at quality %d\n", pDebugName, quality );
+ }
+ }
+
+ DWORD loopBlock = 0;
+ DWORD numLeadingSamples = 0;
+ DWORD numTrailingSamples = 0;
+
+ if ( loopStart != -1 )
+ {
+ // calculate start block/offset
+ DWORD loopBlockStartIndex = 0;
+ DWORD loopBlockStartOffset = 0;
+
+ if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopBegin ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockStartIndex, &loopBlockStartOffset ) )
+ {
+ // could not determine loop point, out of range of encoded samples
+ Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart );
+ return false;
+ }
+
+ loopBlock = loopBlockStartIndex;
+ numLeadingSamples = loopBlockStartOffset;
+
+ if ( BigLong( pXMA2Format->LoopEnd ) < BigLong( pXMA2Format->SamplesEncoded ) )
+ {
+ // calculate end block/offset
+ DWORD loopBlockEndIndex = 0;
+ DWORD loopBlockEndOffset = 0;
+
+ if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopEnd ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockEndIndex, &loopBlockEndOffset ) )
+ {
+ // could not determine loop point, out of range of encoded samples
+ Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart );
+ return false;
+ }
+
+ if ( loopBlockEndIndex != BigLong( pXMA2Format->BlockCount ) - 1 )
+ {
+ // end block MUST be last block
+ Msg( "XMA Loop Encoding Error on '%s', block end is %d/%d\n", pDebugName, loopBlockEndOffset, BigLong( pXMA2Format->BlockCount ) );
+ return false;
+ }
+
+ numTrailingSamples = BigLong( pXMA2Format->SamplesEncoded ) - BigLong( pXMA2Format->LoopEnd );
+ }
+
+ // check for proper encoding range
+ if ( loopBlock > 32767 )
+ {
+ Msg( "XMA Loop Encoding Error on '%s', loop block exceeds 16 bits %d\n", pDebugName, loopBlock );
+ return false;
+ }
+ if ( numLeadingSamples > 32767 )
+ {
+ Msg( "XMA Loop Encoding Error on '%s', leading samples exceeds 16 bits %d\n", pDebugName, numLeadingSamples );
+ return false;
+ }
+ if ( numTrailingSamples > 32767 )
+ {
+ Msg( "XMA Loop Encoding Error on '%s', trailing samples exceeds 16 bits %d\n", pDebugName, numTrailingSamples );
+ return false;
+ }
+ }
+
+ xwvHeader_t header;
+ memset( &header, 0, sizeof( xwvHeader_t ) );
+
+ int seekTableSize = 0;
+ if ( vdatSize || bIsVoiceOver )
+ {
+ // save the optional seek table only for vdat or vo
+ // the seek table size is expected to be derived by this calculation
+ seekTableSize = ( XMADataSize / XMA_BYTES_PER_PACKET ) * sizeof( int );
+ if ( seekTableSize != XMASeekTableSize )
+ {
+ Msg( "XMA Error: Unexpected seek table calculation in '%s'!", pDebugName );
+ return false;
+ }
+ }
+
+ if ( loopStart != -1 && ( vdatSize || bIsVoiceOver ) )
+ {
+ Msg( "XMA Warning: Unexpected loop in vo data '%s'!", pDebugName );
+
+ // do not write the seek table for looping sounds
+ seekTableSize = 0;
+ }
+
+ header.id = BigLong( XWV_ID );
+ header.version = BigLong( XWV_VERSION );
+ header.headerSize = BigLong( sizeof( xwvHeader_t ) );
+ header.staticDataSize = BigLong( seekTableSize + vdatSize );
+ header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + seekTableSize + vdatSize, XBOX_DVD_SECTORSIZE ) );
+ header.dataSize = BigLong( XMADataSize );
+
+ // track the XMA number of samples that will get decoded
+ // which is NOT the same as what the source actually encoded
+ header.numDecodedSamples = pXMA2Format->SamplesEncoded;
+
+ if ( loopStart != -1 )
+ {
+ // the loop start is in source space (now meaningless), need the loop in XMA decoding sample space
+ header.loopStart = pXMA2Format->LoopBegin;
+ }
+ else
+ {
+ header.loopStart = BigLong( -1 );
+ }
+ header.loopBlock = BigShort( (unsigned short)loopBlock );
+ header.numLeadingSamples = BigShort( (unsigned short)numLeadingSamples );
+ header.numTrailingSamples = BigShort( (unsigned short)numTrailingSamples );
+
+ header.vdatSize = BigShort( (short)vdatSize );
+ header.format = XWV_FORMAT_XMA;
+ header.bitsPerSample = 16;
+ header.SetSampleRate( pFormat->nSamplesPerSec );
+ header.SetChannels( pFormat->nChannels );
+ header.quality = quality;
+ header.bHasSeekTable = ( seekTableSize != 0 );
+
+ // output header
+ targetBuff.Put( &header, sizeof( xwvHeader_t ) );
+
+ // output optional seek table
+ if ( seekTableSize )
+ {
+ // seek table is already in big-endian format
+ targetBuff.Put( pXMASeekTable, seekTableSize );
+ }
+
+ // output vdat
+ if ( vdatSize )
+ {
+ targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size );
+ }
+
+ AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
+
+ // write data
+ targetBuff.Put( pXMAData, XMADataSize );
+
+ // pad to EOF
+ AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
+
+ free( pXMAData );
+ free( pXMA2Format );
+ free( pXMASeekTable );
+
+ // xma encoder leaves its temporary files, we'll delete
+ scriptlib->DeleteTemporaryFiles( "LoopStrm*" );
+ scriptlib->DeleteTemporaryFiles( "EncStrm*" );
+
+ return true;
+#endif
+}
+
+bool EncodeAsPCM( const char *pTargetName, CUtlBuffer &targetBuff )
+{
+ int formatChunk = FindChunk( WAVE_FMT );
+ int dataChunk = FindChunk( WAVE_DATA );
+ if ( formatChunk == -1 || dataChunk == -1 )
+ {
+ // huh? these should have been pre-validated
+ return false;
+ }
+
+ WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData;
+ if ( pFormat->wBitsPerSample != 16 )
+ {
+ // huh? the input is expeted to be 16 bit PCM
+ return false;
+ }
+
+ int vdatSize = 0;
+ int vdatChunk = FindChunk( WAVE_VALVEDATA );
+ if ( vdatChunk != -1 )
+ {
+ vdatSize = g_chunks[vdatChunk].size;
+ }
+
+ chunk_t *pDataChunk = &g_chunks[dataChunk];
+
+ xwvHeader_t header;
+ memset( &header, 0, sizeof( xwvHeader_t ) );
+
+ int sampleSize = pFormat->nChannels * sizeof( short );
+ int sampleCount = pDataChunk->size / sampleSize;
+
+ header.id = BigLong( XWV_ID );
+ header.version = BigLong( XWV_VERSION );
+ header.headerSize = BigLong( sizeof( xwvHeader_t ) );
+ header.staticDataSize = BigLong( vdatSize );
+ header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + vdatSize, XBOX_DVD_SECTORSIZE ) );
+ header.dataSize = BigLong( pDataChunk->size );
+ header.numDecodedSamples = BigLong( sampleCount );
+ header.loopStart = BigLong( -1 );
+ header.loopBlock = 0;
+ header.numLeadingSamples = 0;
+ header.numTrailingSamples = 0;
+ header.vdatSize = BigShort( (short)vdatSize );
+ header.format = XWV_FORMAT_PCM;
+ header.bitsPerSample = 16;
+ header.SetSampleRate( pFormat->nSamplesPerSec );
+ header.SetChannels( pFormat->nChannels );
+ header.quality = 100;
+
+ // output header
+ targetBuff.Put( &header, sizeof( xwvHeader_t ) );
+
+ // output vdat
+ if ( vdatSize )
+ {
+ targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size );
+ }
+
+ AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
+
+ for ( int i = 0; i < sampleCount * pFormat->nChannels; i++ )
+ {
+ ((short *)pDataChunk->pData)[i] = BigShort( ((short *)pDataChunk->pData)[i] );
+ }
+
+ // write data
+ targetBuff.Put( pDataChunk->pData, pDataChunk->size );
+
+ // pad to EOF
+ AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE );
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: read source, do work, and write to target
+//-----------------------------------------------------------------------------
+bool CreateTargetFile_WAV( const char *pSourceName, const char *pTargetName, bool bWriteToZip )
+{
+ g_numChunks = 0;
+
+ // resolve relative source to absolute path
+ char fullSourcePath[MAX_PATH];
+ if ( _fullpath( fullSourcePath, pSourceName, sizeof( fullSourcePath ) ) )
+ {
+ pSourceName = fullSourcePath;
+ }
+
+ if ( !ReadChunks( pSourceName, g_numChunks, g_chunks ) )
+ {
+ Msg( "No RIFF Chunks on '%s'\n", pSourceName );
+ return false;
+ }
+
+ int formatChunk = FindChunk( WAVE_FMT );
+ if ( formatChunk == -1 )
+ {
+ Msg( "RIFF Format Chunk not found on '%s'\n", pSourceName );
+ return false;
+ }
+
+ int dataChunk = FindChunk( WAVE_DATA );
+ if ( dataChunk == -1 )
+ {
+ Msg( "RIFF Data Chunk not found on '%s'\n", pSourceName );
+ return false;
+ }
+
+ // get the conversion rules
+ conversion_t *pConversion = g_defaultConversionRules;
+ if ( V_stristr( g_szModPath, "\\portal" ) )
+ {
+ pConversion = g_portalConversionRules;
+ }
+
+ // conversion rules are based on matching subdir
+ for ( int i=1; ;i++ )
+ {
+ char subString[MAX_PATH];
+ if ( !pConversion[i].pSubDir )
+ {
+ // end of list
+ break;
+ }
+
+ sprintf( subString, "\\%s\\", pConversion[i].pSubDir );
+ if ( V_stristr( pSourceName, subString ) )
+ {
+ // use matched conversion rules
+ pConversion = &pConversion[i];
+ break;
+ }
+ }
+
+ bool bForceTo22K = pConversion->bForceTo22K;
+ int quality = pConversion->quality;
+
+ // cannot trust the localization depots to have matched their sources
+ // cannot allow 44K
+ if ( IsLocalizedFile( pSourceName ) )
+ {
+ bForceTo22K = true;
+ }
+
+ // classify strict vo from /sound/vo only
+ bool bIsVoiceOver = V_stristr( pSourceName, "\\sound\\vo\\" ) != NULL;
+
+ // can override default settings
+ quality = CommandLine()->ParmValue( "-xmaquality", quality );
+ if ( quality < 0 )
+ quality = 0;
+ else if ( quality > 100 )
+ quality = 100;
+ if ( !g_bQuiet )
+ {
+ Msg( "Encoding quality: %d on '%s'\n", quality, pSourceName );
+ }
+
+ int vdatSize = 0;
+ int vdatChunk = FindChunk( WAVE_VALVEDATA );
+ if ( vdatChunk != -1 )
+ {
+ // compile to optimal block
+ if ( !CompressVDAT( &g_chunks[vdatChunk] ) )
+ {
+ Msg( "Compress VDAT Error on '%s'\n", pSourceName );
+ return false;
+ }
+ vdatSize = g_chunks[vdatChunk].size;
+ }
+
+ // for safety (not trusting their decoding) and simplicity convert all data to 16 bit PCM before encoding
+ WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData;
+ if ( ( pFormat->wFormatTag == WAVE_FORMAT_PCM ) )
+ {
+ if ( pFormat->wBitsPerSample == 8 )
+ {
+ ConvertPCMDataChunk8To16( &g_chunks[formatChunk], &g_chunks[dataChunk] );
+ }
+ }
+ else if ( pFormat->wFormatTag == WAVE_FORMAT_ADPCM )
+ {
+ ConvertADPCMDataChunkTo16( &g_chunks[formatChunk], &g_chunks[dataChunk] );
+ }
+ else
+ {
+ Msg( "Unknown RIFF Format on '%s'\n", pSourceName );
+ return false;
+ }
+
+ // optionally decimate to 22K
+ if ( pFormat->nSamplesPerSec == 44100 && bForceTo22K )
+ {
+ if ( !g_bQuiet )
+ {
+ Msg( "Converting to 22K '%s'\n", pSourceName );
+ }
+ ConvertPCMDataChunk16To22K( &g_chunks[formatChunk], &g_chunks[dataChunk] );
+ }
+
+ CUtlBuffer targetBuff;
+ bool bSuccess;
+
+ bSuccess = EncodeAsXMA( pSourceName, targetBuff, quality, bIsVoiceOver );
+ if ( bSuccess )
+ {
+ WriteBufferToFile( pTargetName, targetBuff, bWriteToZip, g_WriteModeForConversions );
+ }
+
+ // release data
+ for ( int i = 0; i < g_numChunks; i++ )
+ {
+ free( g_chunks[i].pData );
+ }
+
+ return bSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: MP3's are already pre-converted into .360.wav
+//-----------------------------------------------------------------------------
+bool CreateTargetFile_MP3( const char *pSourceName, const char *pTargetName, bool bWriteToZip )
+{
+ CUtlBuffer targetBuffer;
+
+ // ignore the .mp3 source, the .360.wav target should have been pre-converted, checked in, and exist
+ // use the expected target as the source
+ if ( !scriptlib->ReadFileToBuffer( pTargetName, targetBuffer ) )
+ {
+ // the .360.wav target does not exist
+ // try again using a .wav version and convert from that
+ char wavFilename[MAX_PATH];
+ V_StripExtension( pSourceName, wavFilename, sizeof( wavFilename ) );
+ V_SetExtension( wavFilename, ".wav", sizeof( wavFilename ) );
+ if ( scriptlib->DoesFileExist( wavFilename ) )
+ {
+ if ( CreateTargetFile_WAV( wavFilename, pTargetName, bWriteToZip ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // no conversion to write, but possibly zipped
+ bool bSuccess = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, WRITE_TO_DISK_NEVER );
+ return bSuccess;
+}
+
+//-----------------------------------------------------------------------------
+// Get the preload data for a wav file
+//-----------------------------------------------------------------------------
+bool GetPreloadData_WAV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut )
+{
+ xwvHeader_t *pHeader = ( xwvHeader_t * )fileBufferIn.Base();
+ if ( pHeader->id != ( unsigned int )BigLong( XWV_ID ) ||
+ pHeader->version != ( unsigned int )BigLong( XWV_VERSION ) ||
+ pHeader->headerSize != BigLong( sizeof( xwvHeader_t ) ) )
+ {
+ // bad version
+ Msg( "Can't preload: '%s', has bad version\n", pFilename );
+ return false;
+ }
+
+ // ensure caller's buffer is clean
+ // caller determines preload size, via TellMaxPut()
+ preloadBufferOut.Purge();
+ unsigned int preloadSize = BigLong( pHeader->headerSize ) + BigLong( pHeader->staticDataSize );
+ preloadBufferOut.Put( fileBufferIn.Base(), preloadSize );
+
+ return true;
+}