824 lines
20 KiB
C++
824 lines
20 KiB
C++
|
//========= 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 );
|
||
|
}
|