You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
823 lines
20 KiB
823 lines
20 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
|
|
#include "cbase.h" |
|
#include "isoundcombiner.h" |
|
#include "sentence.h" |
|
#include "filesystem.h" |
|
#include "tier2/riff.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "snd_audio_source.h" |
|
#include "snd_wave_source.h" |
|
#include "AudioWaveOutput.h" |
|
#include "ifaceposersound.h" |
|
#include "vstdlib/random.h" |
|
#include "checksum_crc.h" |
|
|
|
#define WAVEOUTPUT_BITSPERCHANNEL 16 |
|
#define WAVEOUTPUT_FREQUENCY 44100 |
|
|
|
class CSoundCombiner : public ISoundCombiner |
|
{ |
|
public: |
|
CSoundCombiner() : |
|
m_pWaveOutput( NULL ), |
|
m_pOutRIFF( NULL ), |
|
m_pOutIterator( NULL ) |
|
{ |
|
m_szOutFile[ 0 ] = 0; |
|
} |
|
|
|
virtual bool CombineSoundFiles( IFileSystem *filesystem, char const *outfile, CUtlVector< CombinerEntry >& info ); |
|
virtual bool IsCombinedFileChecksumValid( IFileSystem *filesystem, char const *outfile, CUtlVector< CombinerEntry >& info ); |
|
|
|
private: |
|
|
|
struct CombinerWork |
|
{ |
|
CombinerWork() : |
|
sentence(), |
|
duration( 0.0 ), |
|
wave( 0 ), |
|
mixer( 0 ), |
|
entry( 0 ) |
|
{ |
|
} |
|
CSentence sentence; |
|
float duration; |
|
CAudioSource *wave; |
|
CAudioMixer *mixer; |
|
CombinerEntry *entry; |
|
}; |
|
|
|
bool InternalCombineSoundFiles( IFileSystem *filesystem, char const *outfile, CUtlVector< CombinerEntry >& info ); |
|
bool VerifyFilesExist( IFileSystem *filesystem, CUtlVector< CombinerEntry >& info ); |
|
bool CreateWorkList( IFileSystem *filesystem, CUtlVector< CombinerEntry >& info ); |
|
|
|
bool PerformSplicingOnWorkItems( IFileSystem *filesystem ); |
|
void CleanupWork(); |
|
|
|
// .wav file utils |
|
int ComputeBestNumChannels(); |
|
void ParseSentence( CSentence& sentence, IterateRIFF &walk ); |
|
bool LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io ); |
|
bool LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence ); |
|
void StoreValveDataChunk( CSentence& sentence ); |
|
// bool SaveSentenceToWavFile( char const *wavfile, CSentence& sentence ); |
|
|
|
bool InitSplicer( IFileSystem *filesystem, int samplerate, int numchannels, int bitspersample ); |
|
bool LoadSpliceAudioSources(); |
|
bool AppendSilence( int ¤tsample, float duration ); |
|
bool AppendStereo16Data( short samples[ 2 ] ); |
|
bool AppendWaveData( int& currentsample, CAudioSource *wave, CAudioMixer *mixer ); |
|
void AddSentenceToCombined( float offset, CSentence& sentence ); |
|
|
|
unsigned int CheckSumWork( IFileSystem *filesystem, CUtlVector< CombinerEntry >& info ); |
|
unsigned int ComputeChecksum(); |
|
|
|
CUtlVector< CombinerWork * > m_Work; |
|
CSentence m_Combined; |
|
|
|
CAudioWaveOutput *m_pWaveOutput; |
|
|
|
OutFileRIFF *m_pOutRIFF; |
|
IterateOutputRIFF *m_pOutIterator; |
|
|
|
int m_nSampleRate; |
|
int m_nNumChannels; |
|
int m_nBitsPerSample; |
|
int m_nBytesPerSample; |
|
char m_szOutFile[ MAX_PATH ]; |
|
}; |
|
|
|
static CSoundCombiner g_SoundCombiner; |
|
ISoundCombiner *soundcombiner = &g_SoundCombiner; |
|
|
|
bool CSoundCombiner::CreateWorkList( IFileSystem *pFilesystem, CUtlVector< CombinerEntry >& info ) |
|
{ |
|
m_Work.RemoveAll(); |
|
|
|
int c = info.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CombinerWork *workitem = new CombinerWork(); |
|
|
|
char fullpath[ MAX_PATH ]; |
|
Q_strncpy( fullpath, info[ i ].wavefile, sizeof( fullpath ) ); |
|
pFilesystem->GetLocalPath( info[ i ].wavefile, fullpath, sizeof( fullpath ) ); |
|
|
|
if ( !LoadSentenceFromWavFile( fullpath, workitem->sentence ) ) |
|
{ |
|
Warning( "CSoundCombiner::CreateWorkList couldn't load %s for work item (%d)\n", |
|
fullpath, i ); |
|
return false; |
|
} |
|
|
|
workitem->entry = &info[ i ]; |
|
|
|
m_Work.AddToTail( workitem ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CSoundCombiner::CleanupWork() |
|
{ |
|
int c = m_Work.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CombinerWork *workitem = m_Work[ i ]; |
|
delete workitem->mixer; |
|
delete workitem->wave; |
|
|
|
delete m_Work[ i ]; |
|
} |
|
m_Work.RemoveAll(); |
|
|
|
delete m_pOutIterator; |
|
m_pOutIterator = NULL; |
|
|
|
delete m_pOutRIFF; |
|
m_pOutRIFF = NULL; |
|
} |
|
|
|
bool CSoundCombiner::InternalCombineSoundFiles( IFileSystem *pFilesystem, char const *outfile, CUtlVector< CombinerEntry >& info ) |
|
{ |
|
Q_strncpy( m_szOutFile, outfile, sizeof( m_szOutFile ) ); |
|
if ( info.Count() <= 0 ) |
|
{ |
|
Warning( "CSoundCombiner::InternalCombineSoundFiles: work item count is zero\n" ); |
|
return false; |
|
} |
|
|
|
if ( !VerifyFilesExist( pFilesystem, info ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( !CreateWorkList( pFilesystem, info ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
PerformSplicingOnWorkItems( pFilesystem ); |
|
|
|
return true; |
|
} |
|
|
|
bool CSoundCombiner::CombineSoundFiles( IFileSystem *pFilesystem, char const *outfile, CUtlVector< CombinerEntry >& info ) |
|
{ |
|
bool bret = InternalCombineSoundFiles( pFilesystem, outfile, info ); |
|
CleanupWork(); |
|
return bret; |
|
} |
|
|
|
unsigned int CSoundCombiner::ComputeChecksum() |
|
{ |
|
CRC32_t crc; |
|
CRC32_Init( &crc ); |
|
|
|
int c = m_Work.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CombinerWork *curitem = m_Work[ i ]; |
|
unsigned int chk = curitem->sentence.ComputeDataCheckSum(); |
|
|
|
// Msg( " %i -> sentence %u, startoffset %f fn %s\n", |
|
// i, chk, curitem->entry->startoffset, curitem->entry->wavefile ); |
|
|
|
CRC32_ProcessBuffer( &crc, &chk, sizeof( unsigned long ) ); |
|
CRC32_ProcessBuffer( &crc, &curitem->entry->startoffset, sizeof( float ) ); |
|
CRC32_ProcessBuffer( &crc, curitem->entry->wavefile, Q_strlen( curitem->entry->wavefile ) ); |
|
} |
|
|
|
CRC32_Final( &crc ); |
|
return ( unsigned int )crc; |
|
} |
|
|
|
unsigned int CSoundCombiner::CheckSumWork( IFileSystem *pFilesystem, CUtlVector< CombinerEntry >& info ) |
|
{ |
|
if ( info.Count() <= 0 ) |
|
{ |
|
Warning( "CSoundCombiner::CheckSumWork: work item count is zero\n" ); |
|
return 0; |
|
} |
|
|
|
if ( !VerifyFilesExist( pFilesystem, info ) ) |
|
{ |
|
return 0; |
|
} |
|
|
|
if ( !CreateWorkList( pFilesystem, info ) ) |
|
{ |
|
return 0; |
|
} |
|
|
|
// Checkum work items |
|
unsigned int checksum = ComputeChecksum(); |
|
|
|
return checksum; |
|
} |
|
|
|
bool CSoundCombiner::IsCombinedFileChecksumValid( IFileSystem *pFilesystem, char const *outfile, CUtlVector< CombinerEntry >& info ) |
|
{ |
|
unsigned int computedChecksum = CheckSumWork( pFilesystem, info ); |
|
|
|
char fullpath[ MAX_PATH ]; |
|
Q_strncpy( fullpath, outfile, sizeof( fullpath ) ); |
|
pFilesystem->GetLocalPath( outfile, fullpath, sizeof( fullpath ) ); |
|
|
|
CSentence sentence; |
|
|
|
bool valid = false; |
|
|
|
if ( LoadSentenceFromWavFile( fullpath, sentence ) ) |
|
{ |
|
unsigned int diskFileEmbeddedChecksum = sentence.GetDataCheckSum(); |
|
|
|
valid = computedChecksum == diskFileEmbeddedChecksum; |
|
|
|
if ( !valid ) |
|
{ |
|
Warning( " checksum computed %u, disk %u\n", |
|
computedChecksum, diskFileEmbeddedChecksum ); |
|
} |
|
} |
|
else |
|
{ |
|
Warning( "CSoundCombiner::IsCombinedFileChecksumValid: Unabled to load %s\n", fullpath ); |
|
} |
|
|
|
CleanupWork(); |
|
return valid; |
|
} |
|
|
|
bool CSoundCombiner::VerifyFilesExist( IFileSystem *pFilesystem, CUtlVector< CombinerEntry >& info ) |
|
{ |
|
int c = info.Count(); |
|
for ( int i = 0 ; i < c; ++i ) |
|
{ |
|
CombinerEntry& entry = info[ i ]; |
|
if ( !pFilesystem->FileExists( entry.wavefile ) ) |
|
{ |
|
Warning( "CSoundCombiner::VerifyFilesExist: missing file %s\n", entry.wavefile ); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Implements the RIFF i/o interface on stdio |
|
//----------------------------------------------------------------------------- |
|
class StdIOReadBinary : public IFileReadBinary |
|
{ |
|
public: |
|
int open( const char *pFileName ) |
|
{ |
|
return (int)filesystem->Open( pFileName, "rb" ); |
|
} |
|
|
|
int read( void *pOutput, int size, int file ) |
|
{ |
|
if ( !file ) |
|
return 0; |
|
|
|
return filesystem->Read( pOutput, size, (FileHandle_t)file ); |
|
} |
|
|
|
void seek( int file, int pos ) |
|
{ |
|
if ( !file ) |
|
return; |
|
|
|
filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
unsigned int tell( int file ) |
|
{ |
|
if ( !file ) |
|
return 0; |
|
|
|
return filesystem->Tell( (FileHandle_t)file ); |
|
} |
|
|
|
unsigned int size( int file ) |
|
{ |
|
if ( !file ) |
|
return 0; |
|
|
|
return filesystem->Size( (FileHandle_t)file ); |
|
} |
|
|
|
void close( int file ) |
|
{ |
|
if ( !file ) |
|
return; |
|
|
|
filesystem->Close( (FileHandle_t)file ); |
|
} |
|
}; |
|
|
|
class StdIOWriteBinary : public IFileWriteBinary |
|
{ |
|
public: |
|
int create( const char *pFileName ) |
|
{ |
|
return (int)filesystem->Open( pFileName, "wb" ); |
|
} |
|
|
|
int write( void *pData, int size, int file ) |
|
{ |
|
return filesystem->Write( pData, size, (FileHandle_t)file ); |
|
} |
|
|
|
void close( int file ) |
|
{ |
|
filesystem->Close( (FileHandle_t)file ); |
|
} |
|
|
|
void seek( int file, int pos ) |
|
{ |
|
filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
unsigned int tell( int file ) |
|
{ |
|
return filesystem->Tell( (FileHandle_t)file ); |
|
} |
|
}; |
|
|
|
static StdIOReadBinary io_in; |
|
static StdIOWriteBinary io_out; |
|
|
|
#define RIFF_WAVE MAKEID('W','A','V','E') |
|
#define WAVE_FMT MAKEID('f','m','t',' ') |
|
#define WAVE_DATA MAKEID('d','a','t','a') |
|
#define WAVE_FACT MAKEID('f','a','c','t') |
|
#define WAVE_CUE MAKEID('c','u','e',' ') |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &walk - |
|
//----------------------------------------------------------------------------- |
|
void CSoundCombiner::ParseSentence( CSentence& sentence, IterateRIFF &walk ) |
|
{ |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
buf.EnsureCapacity( walk.ChunkSize() ); |
|
walk.ChunkRead( buf.Base() ); |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() ); |
|
|
|
sentence.InitFromDataChunk( buf.Base(), buf.TellPut() ); |
|
} |
|
|
|
bool CSoundCombiner::LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io ) |
|
{ |
|
sentence.Reset(); |
|
|
|
InFileRIFF riff( wavfile, io ); |
|
|
|
// UNDONE: Don't use printf to handle errors |
|
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() ); |
|
|
|
// This chunk must be first as it contains the wave's format |
|
// break out when we've parsed it |
|
bool found = false; |
|
while ( walk.ChunkAvailable() && !found ) |
|
{ |
|
switch( walk.ChunkName() ) |
|
{ |
|
case WAVE_VALVEDATA: |
|
{ |
|
found = true; |
|
CSoundCombiner::ParseSentence( sentence, walk ); |
|
} |
|
break; |
|
} |
|
walk.ChunkNext(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CSoundCombiner::LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence ) |
|
{ |
|
return CSoundCombiner::LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : store - |
|
//----------------------------------------------------------------------------- |
|
void CSoundCombiner::StoreValveDataChunk( CSentence& sentence ) |
|
{ |
|
// Buffer and dump data |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
|
|
sentence.SaveToBuffer( buf ); |
|
|
|
// Copy into store |
|
m_pOutIterator->ChunkWriteData( buf.Base(), buf.TellPut() ); |
|
} |
|
|
|
/* |
|
bool CSoundCombiner::SaveSentenceToWavFile( char const *wavfile, CSentence& sentence ) |
|
{ |
|
char tempfile[ 512 ]; |
|
|
|
Q_StripExtension( wavfile, tempfile, sizeof( tempfile ) ); |
|
Q_DefaultExtension( tempfile, ".tmp", sizeof( tempfile ) ); |
|
|
|
if ( filesystem->FileExists( tempfile, NULL ) ) |
|
{ |
|
filesystem->RemoveFile( tempfile, NULL ); |
|
} |
|
|
|
if ( !filesystem->IsFileWritable( wavfile ) ) |
|
{ |
|
Msg( "%s is not writable, can't save sentence data to file\n", wavfile ); |
|
return false; |
|
} |
|
|
|
// Rename original wavfile to temp |
|
filesystem->RenameFile( wavfile, tempfile, NULL ); |
|
|
|
// NOTE: Put this in it's own scope so that the destructor for outfileRFF actually closes the file!!!! |
|
{ |
|
// Read from Temp |
|
InFileRIFF riff( tempfile, io_in ); |
|
Assert( riff.RIFFName() == RIFF_WAVE ); |
|
|
|
// set up the iterator for the whole file (root RIFF is a chunk) |
|
IterateRIFF walk( riff, riff.RIFFSize() ); |
|
|
|
// And put data back into original wavfile by name |
|
OutFileRIFF riffout( wavfile, io_out ); |
|
|
|
IterateOutputRIFF store( riffout ); |
|
|
|
bool wordtrackwritten = false; |
|
|
|
// Walk input chunks and copy to output |
|
while ( walk.ChunkAvailable() ) |
|
{ |
|
m_pOutIterator->ChunkStart( walk.ChunkName() ); |
|
|
|
switch ( walk.ChunkName() ) |
|
{ |
|
case WAVE_VALVEDATA: |
|
{ |
|
// Overwrite data |
|
CSoundCombiner::StoreValveDataChunk( sentence ); |
|
wordtrackwritten = true; |
|
} |
|
break; |
|
default: |
|
m_pOutIterator->CopyChunkData( walk ); |
|
break; |
|
} |
|
|
|
m_pOutIterator->ChunkFinish(); |
|
|
|
walk.ChunkNext(); |
|
} |
|
|
|
// If we didn't write it above, write it now |
|
if ( !wordtrackwritten ) |
|
{ |
|
m_pOutIterator->ChunkStart( WAVE_VALVEDATA ); |
|
CSoundCombiner::StoreValveDataChunk( sentence ); |
|
m_pOutIterator->ChunkFinish(); |
|
} |
|
} |
|
|
|
// Remove temp file |
|
filesystem->RemoveFile( tempfile, NULL ); |
|
|
|
return true; |
|
} |
|
*/ |
|
|
|
typedef struct channel_s |
|
{ |
|
int leftvol; |
|
int rightvol; |
|
int rleftvol; |
|
int rrightvol; |
|
float pitch; |
|
} channel_t; |
|
|
|
bool CSoundCombiner::InitSplicer( IFileSystem *pFilesystem, int samplerate, int numchannels, int bitspersample ) |
|
{ |
|
m_nSampleRate = samplerate; |
|
m_nNumChannels = numchannels; |
|
m_nBitsPerSample = bitspersample; |
|
m_nBytesPerSample = bitspersample >> 3; |
|
|
|
m_pWaveOutput = ( CAudioWaveOutput * )sound->GetAudioOutput(); |
|
if ( !m_pWaveOutput ) |
|
{ |
|
Warning( "CSoundCombiner::InitSplicer m_pWaveOutput == NULL\n" ); |
|
return false; |
|
} |
|
|
|
// Make sure the directory exists |
|
char basepath[ 512 ]; |
|
Q_ExtractFilePath( m_szOutFile, basepath, sizeof( basepath ) ); |
|
pFilesystem->CreateDirHierarchy( basepath, "GAME" ); |
|
|
|
// Create out put file |
|
m_pOutRIFF = new OutFileRIFF( m_szOutFile, io_out ); |
|
if ( !m_pOutRIFF ) |
|
{ |
|
Warning( "CSoundCombiner::InitSplicer m_pOutRIFF == NULL\n" ); |
|
return false; |
|
} |
|
|
|
// Create output iterator |
|
m_pOutIterator = new IterateOutputRIFF( *m_pOutRIFF ); |
|
if ( !m_pOutIterator ) |
|
{ |
|
Warning( "CSoundCombiner::InitSplicer m_pOutIterator == NULL\n" ); |
|
return false; |
|
} |
|
|
|
WAVEFORMATEX format; |
|
format.cbSize = sizeof( format ); |
|
|
|
format.wFormatTag = WAVE_FORMAT_PCM; |
|
format.nAvgBytesPerSec = m_nSampleRate * m_nNumChannels * m_nBytesPerSample; |
|
format.nChannels = m_nNumChannels; |
|
format.wBitsPerSample = m_nBitsPerSample; |
|
format.nSamplesPerSec = m_nSampleRate; |
|
format.nBlockAlign = 1; |
|
|
|
// Always store the format chunk first |
|
m_pOutIterator->ChunkWrite( WAVE_FMT, &format, sizeof( format ) ); |
|
|
|
return true; |
|
} |
|
|
|
bool CSoundCombiner::LoadSpliceAudioSources() |
|
{ |
|
int c = m_Work.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CombinerWork *item = m_Work[ i ]; |
|
|
|
CAudioSource *wave = sound->LoadSound( item->entry->wavefile ); |
|
if ( !wave ) |
|
{ |
|
Warning( "CSoundCombiner::LoadSpliceAudioSources LoadSound failed '%s'\n", item->entry->wavefile ); |
|
return false; |
|
} |
|
|
|
CAudioMixer *pMixer = wave->CreateMixer(); |
|
if ( !pMixer ) |
|
{ |
|
Warning( "CSoundCombiner::LoadSpliceAudioSources CreateMixer failed '%s'\n", item->entry->wavefile ); |
|
return false; |
|
} |
|
|
|
item->wave = wave; |
|
item->mixer = pMixer; |
|
item->duration = wave->GetRunningLength(); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CSoundCombiner::AppendSilence( int ¤tsample, float duration ) |
|
{ |
|
int numSamples = duration * m_nSampleRate; |
|
|
|
#define MOTION_RANGE 150 |
|
#define MOTION_MAXSTEP 20 |
|
int currentValue = 32767; |
|
int maxValue = currentValue + ( MOTION_RANGE / 2 ); |
|
int minValue = currentValue - ( MOTION_RANGE / 2 ); |
|
|
|
short samples[ 2 ]; |
|
|
|
while ( --numSamples >= 0 ) |
|
{ |
|
currentValue += random->RandomInt( -MOTION_MAXSTEP, MOTION_MAXSTEP ); |
|
currentValue = min( maxValue, currentValue ); |
|
currentValue = max( minValue, currentValue ); |
|
|
|
// Downsample to 0 65556 range |
|
short s = (float)currentValue / 32768.0f; |
|
|
|
samples[ 0 ] = s; |
|
samples[ 1 ] = s; |
|
|
|
AppendStereo16Data( samples ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CSoundCombiner::AppendStereo16Data( short samples[ 2 ] ) |
|
{ |
|
// Convert from 16 bit, 2 channels to output size |
|
if ( m_nNumChannels == 1 ) |
|
{ |
|
if ( m_nBytesPerSample == 1 ) |
|
{ |
|
// Convert to 8 bit mono |
|
// left + right (2 channels ) * 16 bits |
|
float s1 = (float)( samples[ 0 ] >> 8 ); |
|
float s2 = (float)( samples[ 1 ] >> 8 ); |
|
|
|
float avg = ( s1 + s2 ) * 0.5f; |
|
avg = clamp( avg, -127.0f, 127.0f ); |
|
byte chopped = (byte)( avg+ 127 ); |
|
|
|
m_pOutIterator->ChunkWriteData( &chopped, sizeof( byte ) ); |
|
} |
|
else if ( m_nBytesPerSample == 2 ) |
|
{ |
|
// Conver to 16 bit mono |
|
float s1 = (float)( samples[ 0 ] ); |
|
float s2 = (float)( samples[ 1 ] ); |
|
|
|
float avg = ( s1 + s2 ) * 0.5f; |
|
unsigned short chopped = (unsigned short)( avg ); |
|
|
|
m_pOutIterator->ChunkWriteData( &chopped, sizeof( unsigned short ) ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
} |
|
else if ( m_nNumChannels == 2 ) |
|
{ |
|
if ( m_nBytesPerSample == 1 ) |
|
{ |
|
// Convert to 8 bit stereo |
|
// left + right (2 channels ) * 16 bits |
|
float s1 = (float)( samples[ 0 ] >> 8 ); |
|
float s2 = (float)( samples[ 1 ] >> 8 ); |
|
|
|
s1 = clamp( s1, -127.0f, 127.0f ); |
|
s2 = clamp( s2, -127.0f, 127.0f ); |
|
|
|
byte chopped1 = (byte)( s1 + 127.0f ); |
|
byte chopped2 = (byte)( s2 + 127.0f ); |
|
|
|
m_pOutIterator->ChunkWriteData( &chopped1, sizeof( byte ) ); |
|
m_pOutIterator->ChunkWriteData( &chopped2, sizeof( byte ) ); |
|
} |
|
else if ( m_nBytesPerSample == 2 ) |
|
{ |
|
// Leave as 16 bit stereo |
|
// Directly store values |
|
m_pOutIterator->ChunkWriteData( &samples[ 0 ], sizeof( unsigned short ) ); |
|
m_pOutIterator->ChunkWriteData( &samples[ 1 ], sizeof( unsigned short ) ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CSoundCombiner::AppendWaveData( int& currentsample, CAudioSource *wave, CAudioMixer *mixer ) |
|
{ |
|
// need a bit of space |
|
short samples[ 2 ]; |
|
channel_t channel; |
|
memset( &channel, 0, sizeof( channel ) ); |
|
channel.leftvol = 255; |
|
channel.rightvol = 255; |
|
channel.pitch = 1.0; |
|
|
|
while ( 1 ) |
|
{ |
|
m_pWaveOutput->m_audioDevice.MixBegin(); |
|
|
|
if ( !mixer->MixDataToDevice( &m_pWaveOutput->m_audioDevice, &channel, currentsample, 1, wave->SampleRate(), true ) ) |
|
break; |
|
|
|
m_pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, 1 ); |
|
|
|
currentsample = mixer->GetSamplePosition(); |
|
|
|
AppendStereo16Data( samples ); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
int CSoundCombiner::ComputeBestNumChannels() |
|
{ |
|
// We prefer mono output unless one of the source wav files is stereo, then we'll do stereo output |
|
int c = m_Work.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
CombinerWork *curitem = m_Work[ i ]; |
|
|
|
if ( curitem->wave->GetNumChannels() == 2 ) |
|
{ |
|
return 2; |
|
} |
|
} |
|
return 1; |
|
} |
|
|
|
bool CSoundCombiner::PerformSplicingOnWorkItems( IFileSystem *pFilesystem ) |
|
{ |
|
if ( !LoadSpliceAudioSources() ) |
|
{ |
|
return false; |
|
} |
|
|
|
int bestNumChannels = ComputeBestNumChannels(); |
|
int bitsPerChannel = WAVEOUTPUT_BITSPERCHANNEL; |
|
|
|
// Pull in data and write it out |
|
if ( !InitSplicer( pFilesystem, WAVEOUTPUT_FREQUENCY, bestNumChannels, bitsPerChannel ) ) |
|
{ |
|
return false; |
|
} |
|
|
|
m_pOutIterator->ChunkStart( WAVE_DATA ); |
|
|
|
float timeoffset = 0.0f; |
|
|
|
m_Combined.Reset(); |
|
m_Combined.SetText( "" ); |
|
|
|
int c = m_Work.Count(); |
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
int currentsample = 0; |
|
|
|
CombinerWork *curitem = m_Work[ i ]; |
|
CombinerWork *nextitem = NULL; |
|
if ( i != c - 1 ) |
|
{ |
|
nextitem = m_Work[ i + 1 ]; |
|
} |
|
|
|
float duration = curitem->duration; |
|
|
|
AppendWaveData( currentsample, curitem->wave, curitem->mixer ); |
|
|
|
AddSentenceToCombined( timeoffset, curitem->sentence ); |
|
|
|
timeoffset += duration; |
|
|
|
if ( nextitem != NULL ) |
|
{ |
|
float nextstart = nextitem->entry->startoffset; |
|
float silence_time = nextstart - timeoffset; |
|
|
|
AppendSilence( currentsample, silence_time ); |
|
|
|
timeoffset += silence_time; |
|
} |
|
} |
|
|
|
m_pOutIterator->ChunkFinish(); |
|
|
|
// Checksum the work items |
|
unsigned int checksum = ComputeChecksum(); |
|
|
|
// Make sure the checksum is embedded in the data file |
|
m_Combined.SetDataCheckSum( checksum ); |
|
|
|
// Msg( " checksum computed %u\n", checksum ); |
|
|
|
m_pOutIterator->ChunkStart( WAVE_VALVEDATA ); |
|
StoreValveDataChunk( m_Combined ); |
|
m_pOutIterator->ChunkFinish(); |
|
|
|
|
|
return true; |
|
} |
|
|
|
void CSoundCombiner::AddSentenceToCombined( float offset, CSentence& sentence ) |
|
{ |
|
m_Combined.Append( offset, sentence ); |
|
}
|
|
|