source-engine/utils/xbox/MakeGameData/MakeSounds.cpp

969 lines
28 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= 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;
}