Team Fortress 2 Source Code as on 22/4/2020
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

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "cbase.h"
  7. #include "isoundcombiner.h"
  8. #include "sentence.h"
  9. #include "filesystem.h"
  10. #include "tier2/riff.h"
  11. #include "tier1/utlbuffer.h"
  12. #include "snd_audio_source.h"
  13. #include "snd_wave_source.h"
  14. #include "AudioWaveOutput.h"
  15. #include "ifaceposersound.h"
  16. #include "vstdlib/random.h"
  17. #include "checksum_crc.h"
  18. #define WAVEOUTPUT_BITSPERCHANNEL 16
  19. #define WAVEOUTPUT_FREQUENCY 44100
  20. class CSoundCombiner : public ISoundCombiner
  21. {
  22. public:
  23. CSoundCombiner() :
  24. m_pWaveOutput( NULL ),
  25. m_pOutRIFF( NULL ),
  26. m_pOutIterator( NULL )
  27. {
  28. m_szOutFile[ 0 ] = 0;
  29. }
  30. virtual bool CombineSoundFiles( IFileSystem *filesystem, char const *outfile, CUtlVector< CombinerEntry >& info );
  31. virtual bool IsCombinedFileChecksumValid( IFileSystem *filesystem, char const *outfile, CUtlVector< CombinerEntry >& info );
  32. private:
  33. struct CombinerWork
  34. {
  35. CombinerWork() :
  36. sentence(),
  37. duration( 0.0 ),
  38. wave( 0 ),
  39. mixer( 0 ),
  40. entry( 0 )
  41. {
  42. }
  43. CSentence sentence;
  44. float duration;
  45. CAudioSource *wave;
  46. CAudioMixer *mixer;
  47. CombinerEntry *entry;
  48. };
  49. bool InternalCombineSoundFiles( IFileSystem *filesystem, char const *outfile, CUtlVector< CombinerEntry >& info );
  50. bool VerifyFilesExist( IFileSystem *filesystem, CUtlVector< CombinerEntry >& info );
  51. bool CreateWorkList( IFileSystem *filesystem, CUtlVector< CombinerEntry >& info );
  52. bool PerformSplicingOnWorkItems( IFileSystem *filesystem );
  53. void CleanupWork();
  54. // .wav file utils
  55. int ComputeBestNumChannels();
  56. void ParseSentence( CSentence& sentence, IterateRIFF &walk );
  57. bool LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io );
  58. bool LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence );
  59. void StoreValveDataChunk( CSentence& sentence );
  60. // bool SaveSentenceToWavFile( char const *wavfile, CSentence& sentence );
  61. bool InitSplicer( IFileSystem *filesystem, int samplerate, int numchannels, int bitspersample );
  62. bool LoadSpliceAudioSources();
  63. bool AppendSilence( int &currentsample, float duration );
  64. bool AppendStereo16Data( short samples[ 2 ] );
  65. bool AppendWaveData( int& currentsample, CAudioSource *wave, CAudioMixer *mixer );
  66. void AddSentenceToCombined( float offset, CSentence& sentence );
  67. unsigned int CheckSumWork( IFileSystem *filesystem, CUtlVector< CombinerEntry >& info );
  68. unsigned int ComputeChecksum();
  69. CUtlVector< CombinerWork * > m_Work;
  70. CSentence m_Combined;
  71. CAudioWaveOutput *m_pWaveOutput;
  72. OutFileRIFF *m_pOutRIFF;
  73. IterateOutputRIFF *m_pOutIterator;
  74. int m_nSampleRate;
  75. int m_nNumChannels;
  76. int m_nBitsPerSample;
  77. int m_nBytesPerSample;
  78. char m_szOutFile[ MAX_PATH ];
  79. };
  80. static CSoundCombiner g_SoundCombiner;
  81. ISoundCombiner *soundcombiner = &g_SoundCombiner;
  82. bool CSoundCombiner::CreateWorkList( IFileSystem *pFilesystem, CUtlVector< CombinerEntry >& info )
  83. {
  84. m_Work.RemoveAll();
  85. int c = info.Count();
  86. for ( int i = 0; i < c; ++i )
  87. {
  88. CombinerWork *workitem = new CombinerWork();
  89. char fullpath[ MAX_PATH ];
  90. Q_strncpy( fullpath, info[ i ].wavefile, sizeof( fullpath ) );
  91. pFilesystem->GetLocalPath( info[ i ].wavefile, fullpath, sizeof( fullpath ) );
  92. if ( !LoadSentenceFromWavFile( fullpath, workitem->sentence ) )
  93. {
  94. Warning( "CSoundCombiner::CreateWorkList couldn't load %s for work item (%d)\n",
  95. fullpath, i );
  96. return false;
  97. }
  98. workitem->entry = &info[ i ];
  99. m_Work.AddToTail( workitem );
  100. }
  101. return true;
  102. }
  103. void CSoundCombiner::CleanupWork()
  104. {
  105. int c = m_Work.Count();
  106. for ( int i = 0; i < c; ++i )
  107. {
  108. CombinerWork *workitem = m_Work[ i ];
  109. delete workitem->mixer;
  110. delete workitem->wave;
  111. delete m_Work[ i ];
  112. }
  113. m_Work.RemoveAll();
  114. delete m_pOutIterator;
  115. m_pOutIterator = NULL;
  116. delete m_pOutRIFF;
  117. m_pOutRIFF = NULL;
  118. }
  119. bool CSoundCombiner::InternalCombineSoundFiles( IFileSystem *pFilesystem, char const *outfile, CUtlVector< CombinerEntry >& info )
  120. {
  121. Q_strncpy( m_szOutFile, outfile, sizeof( m_szOutFile ) );
  122. if ( info.Count() <= 0 )
  123. {
  124. Warning( "CSoundCombiner::InternalCombineSoundFiles: work item count is zero\n" );
  125. return false;
  126. }
  127. if ( !VerifyFilesExist( pFilesystem, info ) )
  128. {
  129. return false;
  130. }
  131. if ( !CreateWorkList( pFilesystem, info ) )
  132. {
  133. return false;
  134. }
  135. PerformSplicingOnWorkItems( pFilesystem );
  136. return true;
  137. }
  138. bool CSoundCombiner::CombineSoundFiles( IFileSystem *pFilesystem, char const *outfile, CUtlVector< CombinerEntry >& info )
  139. {
  140. bool bret = InternalCombineSoundFiles( pFilesystem, outfile, info );
  141. CleanupWork();
  142. return bret;
  143. }
  144. unsigned int CSoundCombiner::ComputeChecksum()
  145. {
  146. CRC32_t crc;
  147. CRC32_Init( &crc );
  148. int c = m_Work.Count();
  149. for ( int i = 0; i < c; ++i )
  150. {
  151. CombinerWork *curitem = m_Work[ i ];
  152. unsigned int chk = curitem->sentence.ComputeDataCheckSum();
  153. // Msg( " %i -> sentence %u, startoffset %f fn %s\n",
  154. // i, chk, curitem->entry->startoffset, curitem->entry->wavefile );
  155. CRC32_ProcessBuffer( &crc, &chk, sizeof( unsigned long ) );
  156. CRC32_ProcessBuffer( &crc, &curitem->entry->startoffset, sizeof( float ) );
  157. CRC32_ProcessBuffer( &crc, curitem->entry->wavefile, Q_strlen( curitem->entry->wavefile ) );
  158. }
  159. CRC32_Final( &crc );
  160. return ( unsigned int )crc;
  161. }
  162. unsigned int CSoundCombiner::CheckSumWork( IFileSystem *pFilesystem, CUtlVector< CombinerEntry >& info )
  163. {
  164. if ( info.Count() <= 0 )
  165. {
  166. Warning( "CSoundCombiner::CheckSumWork: work item count is zero\n" );
  167. return 0;
  168. }
  169. if ( !VerifyFilesExist( pFilesystem, info ) )
  170. {
  171. return 0;
  172. }
  173. if ( !CreateWorkList( pFilesystem, info ) )
  174. {
  175. return 0;
  176. }
  177. // Checkum work items
  178. unsigned int checksum = ComputeChecksum();
  179. return checksum;
  180. }
  181. bool CSoundCombiner::IsCombinedFileChecksumValid( IFileSystem *pFilesystem, char const *outfile, CUtlVector< CombinerEntry >& info )
  182. {
  183. unsigned int computedChecksum = CheckSumWork( pFilesystem, info );
  184. char fullpath[ MAX_PATH ];
  185. Q_strncpy( fullpath, outfile, sizeof( fullpath ) );
  186. pFilesystem->GetLocalPath( outfile, fullpath, sizeof( fullpath ) );
  187. CSentence sentence;
  188. bool valid = false;
  189. if ( LoadSentenceFromWavFile( fullpath, sentence ) )
  190. {
  191. unsigned int diskFileEmbeddedChecksum = sentence.GetDataCheckSum();
  192. valid = computedChecksum == diskFileEmbeddedChecksum;
  193. if ( !valid )
  194. {
  195. Warning( " checksum computed %u, disk %u\n",
  196. computedChecksum, diskFileEmbeddedChecksum );
  197. }
  198. }
  199. else
  200. {
  201. Warning( "CSoundCombiner::IsCombinedFileChecksumValid: Unabled to load %s\n", fullpath );
  202. }
  203. CleanupWork();
  204. return valid;
  205. }
  206. bool CSoundCombiner::VerifyFilesExist( IFileSystem *pFilesystem, CUtlVector< CombinerEntry >& info )
  207. {
  208. int c = info.Count();
  209. for ( int i = 0 ; i < c; ++i )
  210. {
  211. CombinerEntry& entry = info[ i ];
  212. if ( !pFilesystem->FileExists( entry.wavefile ) )
  213. {
  214. Warning( "CSoundCombiner::VerifyFilesExist: missing file %s\n", entry.wavefile );
  215. return false;
  216. }
  217. }
  218. return true;
  219. }
  220. //-----------------------------------------------------------------------------
  221. // Purpose: Implements the RIFF i/o interface on stdio
  222. //-----------------------------------------------------------------------------
  223. class StdIOReadBinary : public IFileReadBinary
  224. {
  225. public:
  226. int open( const char *pFileName )
  227. {
  228. return (int)filesystem->Open( pFileName, "rb" );
  229. }
  230. int read( void *pOutput, int size, int file )
  231. {
  232. if ( !file )
  233. return 0;
  234. return filesystem->Read( pOutput, size, (FileHandle_t)file );
  235. }
  236. void seek( int file, int pos )
  237. {
  238. if ( !file )
  239. return;
  240. filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
  241. }
  242. unsigned int tell( int file )
  243. {
  244. if ( !file )
  245. return 0;
  246. return filesystem->Tell( (FileHandle_t)file );
  247. }
  248. unsigned int size( int file )
  249. {
  250. if ( !file )
  251. return 0;
  252. return filesystem->Size( (FileHandle_t)file );
  253. }
  254. void close( int file )
  255. {
  256. if ( !file )
  257. return;
  258. filesystem->Close( (FileHandle_t)file );
  259. }
  260. };
  261. class StdIOWriteBinary : public IFileWriteBinary
  262. {
  263. public:
  264. int create( const char *pFileName )
  265. {
  266. return (int)filesystem->Open( pFileName, "wb" );
  267. }
  268. int write( void *pData, int size, int file )
  269. {
  270. return filesystem->Write( pData, size, (FileHandle_t)file );
  271. }
  272. void close( int file )
  273. {
  274. filesystem->Close( (FileHandle_t)file );
  275. }
  276. void seek( int file, int pos )
  277. {
  278. filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
  279. }
  280. unsigned int tell( int file )
  281. {
  282. return filesystem->Tell( (FileHandle_t)file );
  283. }
  284. };
  285. static StdIOReadBinary io_in;
  286. static StdIOWriteBinary io_out;
  287. #define RIFF_WAVE MAKEID('W','A','V','E')
  288. #define WAVE_FMT MAKEID('f','m','t',' ')
  289. #define WAVE_DATA MAKEID('d','a','t','a')
  290. #define WAVE_FACT MAKEID('f','a','c','t')
  291. #define WAVE_CUE MAKEID('c','u','e',' ')
  292. //-----------------------------------------------------------------------------
  293. // Purpose:
  294. // Input : &walk -
  295. //-----------------------------------------------------------------------------
  296. void CSoundCombiner::ParseSentence( CSentence& sentence, IterateRIFF &walk )
  297. {
  298. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  299. buf.EnsureCapacity( walk.ChunkSize() );
  300. walk.ChunkRead( buf.Base() );
  301. buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
  302. sentence.InitFromDataChunk( buf.Base(), buf.TellPut() );
  303. }
  304. bool CSoundCombiner::LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io )
  305. {
  306. sentence.Reset();
  307. InFileRIFF riff( wavfile, io );
  308. // UNDONE: Don't use printf to handle errors
  309. if ( riff.RIFFName() != RIFF_WAVE )
  310. {
  311. return false;
  312. }
  313. // set up the iterator for the whole file (root RIFF is a chunk)
  314. IterateRIFF walk( riff, riff.RIFFSize() );
  315. // This chunk must be first as it contains the wave's format
  316. // break out when we've parsed it
  317. bool found = false;
  318. while ( walk.ChunkAvailable() && !found )
  319. {
  320. switch( walk.ChunkName() )
  321. {
  322. case WAVE_VALVEDATA:
  323. {
  324. found = true;
  325. CSoundCombiner::ParseSentence( sentence, walk );
  326. }
  327. break;
  328. }
  329. walk.ChunkNext();
  330. }
  331. return true;
  332. }
  333. bool CSoundCombiner::LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence )
  334. {
  335. return CSoundCombiner::LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in );
  336. }
  337. //-----------------------------------------------------------------------------
  338. // Purpose:
  339. // Input : store -
  340. //-----------------------------------------------------------------------------
  341. void CSoundCombiner::StoreValveDataChunk( CSentence& sentence )
  342. {
  343. // Buffer and dump data
  344. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  345. sentence.SaveToBuffer( buf );
  346. // Copy into store
  347. m_pOutIterator->ChunkWriteData( buf.Base(), buf.TellPut() );
  348. }
  349. /*
  350. bool CSoundCombiner::SaveSentenceToWavFile( char const *wavfile, CSentence& sentence )
  351. {
  352. char tempfile[ 512 ];
  353. Q_StripExtension( wavfile, tempfile, sizeof( tempfile ) );
  354. Q_DefaultExtension( tempfile, ".tmp", sizeof( tempfile ) );
  355. if ( filesystem->FileExists( tempfile, NULL ) )
  356. {
  357. filesystem->RemoveFile( tempfile, NULL );
  358. }
  359. if ( !filesystem->IsFileWritable( wavfile ) )
  360. {
  361. Msg( "%s is not writable, can't save sentence data to file\n", wavfile );
  362. return false;
  363. }
  364. // Rename original wavfile to temp
  365. filesystem->RenameFile( wavfile, tempfile, NULL );
  366. // NOTE: Put this in it's own scope so that the destructor for outfileRFF actually closes the file!!!!
  367. {
  368. // Read from Temp
  369. InFileRIFF riff( tempfile, io_in );
  370. Assert( riff.RIFFName() == RIFF_WAVE );
  371. // set up the iterator for the whole file (root RIFF is a chunk)
  372. IterateRIFF walk( riff, riff.RIFFSize() );
  373. // And put data back into original wavfile by name
  374. OutFileRIFF riffout( wavfile, io_out );
  375. IterateOutputRIFF store( riffout );
  376. bool wordtrackwritten = false;
  377. // Walk input chunks and copy to output
  378. while ( walk.ChunkAvailable() )
  379. {
  380. m_pOutIterator->ChunkStart( walk.ChunkName() );
  381. switch ( walk.ChunkName() )
  382. {
  383. case WAVE_VALVEDATA:
  384. {
  385. // Overwrite data
  386. CSoundCombiner::StoreValveDataChunk( sentence );
  387. wordtrackwritten = true;
  388. }
  389. break;
  390. default:
  391. m_pOutIterator->CopyChunkData( walk );
  392. break;
  393. }
  394. m_pOutIterator->ChunkFinish();
  395. walk.ChunkNext();
  396. }
  397. // If we didn't write it above, write it now
  398. if ( !wordtrackwritten )
  399. {
  400. m_pOutIterator->ChunkStart( WAVE_VALVEDATA );
  401. CSoundCombiner::StoreValveDataChunk( sentence );
  402. m_pOutIterator->ChunkFinish();
  403. }
  404. }
  405. // Remove temp file
  406. filesystem->RemoveFile( tempfile, NULL );
  407. return true;
  408. }
  409. */
  410. typedef struct channel_s
  411. {
  412. int leftvol;
  413. int rightvol;
  414. int rleftvol;
  415. int rrightvol;
  416. float pitch;
  417. } channel_t;
  418. bool CSoundCombiner::InitSplicer( IFileSystem *pFilesystem, int samplerate, int numchannels, int bitspersample )
  419. {
  420. m_nSampleRate = samplerate;
  421. m_nNumChannels = numchannels;
  422. m_nBitsPerSample = bitspersample;
  423. m_nBytesPerSample = bitspersample >> 3;
  424. m_pWaveOutput = ( CAudioWaveOutput * )sound->GetAudioOutput();
  425. if ( !m_pWaveOutput )
  426. {
  427. Warning( "CSoundCombiner::InitSplicer m_pWaveOutput == NULL\n" );
  428. return false;
  429. }
  430. // Make sure the directory exists
  431. char basepath[ 512 ];
  432. Q_ExtractFilePath( m_szOutFile, basepath, sizeof( basepath ) );
  433. pFilesystem->CreateDirHierarchy( basepath, "GAME" );
  434. // Create out put file
  435. m_pOutRIFF = new OutFileRIFF( m_szOutFile, io_out );
  436. if ( !m_pOutRIFF )
  437. {
  438. Warning( "CSoundCombiner::InitSplicer m_pOutRIFF == NULL\n" );
  439. return false;
  440. }
  441. // Create output iterator
  442. m_pOutIterator = new IterateOutputRIFF( *m_pOutRIFF );
  443. if ( !m_pOutIterator )
  444. {
  445. Warning( "CSoundCombiner::InitSplicer m_pOutIterator == NULL\n" );
  446. return false;
  447. }
  448. WAVEFORMATEX format;
  449. format.cbSize = sizeof( format );
  450. format.wFormatTag = WAVE_FORMAT_PCM;
  451. format.nAvgBytesPerSec = m_nSampleRate * m_nNumChannels * m_nBytesPerSample;
  452. format.nChannels = m_nNumChannels;
  453. format.wBitsPerSample = m_nBitsPerSample;
  454. format.nSamplesPerSec = m_nSampleRate;
  455. format.nBlockAlign = 1;
  456. // Always store the format chunk first
  457. m_pOutIterator->ChunkWrite( WAVE_FMT, &format, sizeof( format ) );
  458. return true;
  459. }
  460. bool CSoundCombiner::LoadSpliceAudioSources()
  461. {
  462. int c = m_Work.Count();
  463. for ( int i = 0; i < c; ++i )
  464. {
  465. CombinerWork *item = m_Work[ i ];
  466. CAudioSource *wave = sound->LoadSound( item->entry->wavefile );
  467. if ( !wave )
  468. {
  469. Warning( "CSoundCombiner::LoadSpliceAudioSources LoadSound failed '%s'\n", item->entry->wavefile );
  470. return false;
  471. }
  472. CAudioMixer *pMixer = wave->CreateMixer();
  473. if ( !pMixer )
  474. {
  475. Warning( "CSoundCombiner::LoadSpliceAudioSources CreateMixer failed '%s'\n", item->entry->wavefile );
  476. return false;
  477. }
  478. item->wave = wave;
  479. item->mixer = pMixer;
  480. item->duration = wave->GetRunningLength();
  481. }
  482. return true;
  483. }
  484. bool CSoundCombiner::AppendSilence( int &currentsample, float duration )
  485. {
  486. int numSamples = duration * m_nSampleRate;
  487. #define MOTION_RANGE 150
  488. #define MOTION_MAXSTEP 20
  489. int currentValue = 32767;
  490. int maxValue = currentValue + ( MOTION_RANGE / 2 );
  491. int minValue = currentValue - ( MOTION_RANGE / 2 );
  492. short samples[ 2 ];
  493. while ( --numSamples >= 0 )
  494. {
  495. currentValue += random->RandomInt( -MOTION_MAXSTEP, MOTION_MAXSTEP );
  496. currentValue = min( maxValue, currentValue );
  497. currentValue = max( minValue, currentValue );
  498. // Downsample to 0 65556 range
  499. short s = (float)currentValue / 32768.0f;
  500. samples[ 0 ] = s;
  501. samples[ 1 ] = s;
  502. AppendStereo16Data( samples );
  503. }
  504. return true;
  505. }
  506. bool CSoundCombiner::AppendStereo16Data( short samples[ 2 ] )
  507. {
  508. // Convert from 16 bit, 2 channels to output size
  509. if ( m_nNumChannels == 1 )
  510. {
  511. if ( m_nBytesPerSample == 1 )
  512. {
  513. // Convert to 8 bit mono
  514. // left + right (2 channels ) * 16 bits
  515. float s1 = (float)( samples[ 0 ] >> 8 );
  516. float s2 = (float)( samples[ 1 ] >> 8 );
  517. float avg = ( s1 + s2 ) * 0.5f;
  518. avg = clamp( avg, -127.0f, 127.0f );
  519. byte chopped = (byte)( avg+ 127 );
  520. m_pOutIterator->ChunkWriteData( &chopped, sizeof( byte ) );
  521. }
  522. else if ( m_nBytesPerSample == 2 )
  523. {
  524. // Conver to 16 bit mono
  525. float s1 = (float)( samples[ 0 ] );
  526. float s2 = (float)( samples[ 1 ] );
  527. float avg = ( s1 + s2 ) * 0.5f;
  528. unsigned short chopped = (unsigned short)( avg );
  529. m_pOutIterator->ChunkWriteData( &chopped, sizeof( unsigned short ) );
  530. }
  531. else
  532. {
  533. Assert( 0 );
  534. return false;
  535. }
  536. }
  537. else if ( m_nNumChannels == 2 )
  538. {
  539. if ( m_nBytesPerSample == 1 )
  540. {
  541. // Convert to 8 bit stereo
  542. // left + right (2 channels ) * 16 bits
  543. float s1 = (float)( samples[ 0 ] >> 8 );
  544. float s2 = (float)( samples[ 1 ] >> 8 );
  545. s1 = clamp( s1, -127.0f, 127.0f );
  546. s2 = clamp( s2, -127.0f, 127.0f );
  547. byte chopped1 = (byte)( s1 + 127.0f );
  548. byte chopped2 = (byte)( s2 + 127.0f );
  549. m_pOutIterator->ChunkWriteData( &chopped1, sizeof( byte ) );
  550. m_pOutIterator->ChunkWriteData( &chopped2, sizeof( byte ) );
  551. }
  552. else if ( m_nBytesPerSample == 2 )
  553. {
  554. // Leave as 16 bit stereo
  555. // Directly store values
  556. m_pOutIterator->ChunkWriteData( &samples[ 0 ], sizeof( unsigned short ) );
  557. m_pOutIterator->ChunkWriteData( &samples[ 1 ], sizeof( unsigned short ) );
  558. }
  559. else
  560. {
  561. Assert( 0 );
  562. return false;
  563. }
  564. }
  565. else
  566. {
  567. Assert( 0 );
  568. return false;
  569. }
  570. return true;
  571. }
  572. bool CSoundCombiner::AppendWaveData( int& currentsample, CAudioSource *wave, CAudioMixer *mixer )
  573. {
  574. // need a bit of space
  575. short samples[ 2 ];
  576. channel_t channel;
  577. memset( &channel, 0, sizeof( channel ) );
  578. channel.leftvol = 255;
  579. channel.rightvol = 255;
  580. channel.pitch = 1.0;
  581. while ( 1 )
  582. {
  583. m_pWaveOutput->m_audioDevice.MixBegin();
  584. if ( !mixer->MixDataToDevice( &m_pWaveOutput->m_audioDevice, &channel, currentsample, 1, wave->SampleRate(), true ) )
  585. break;
  586. m_pWaveOutput->m_audioDevice.TransferBufferStereo16( samples, 1 );
  587. currentsample = mixer->GetSamplePosition();
  588. AppendStereo16Data( samples );
  589. }
  590. return true;
  591. }
  592. int CSoundCombiner::ComputeBestNumChannels()
  593. {
  594. // We prefer mono output unless one of the source wav files is stereo, then we'll do stereo output
  595. int c = m_Work.Count();
  596. for ( int i = 0; i < c; ++i )
  597. {
  598. CombinerWork *curitem = m_Work[ i ];
  599. if ( curitem->wave->GetNumChannels() == 2 )
  600. {
  601. return 2;
  602. }
  603. }
  604. return 1;
  605. }
  606. bool CSoundCombiner::PerformSplicingOnWorkItems( IFileSystem *pFilesystem )
  607. {
  608. if ( !LoadSpliceAudioSources() )
  609. {
  610. return false;
  611. }
  612. int bestNumChannels = ComputeBestNumChannels();
  613. int bitsPerChannel = WAVEOUTPUT_BITSPERCHANNEL;
  614. // Pull in data and write it out
  615. if ( !InitSplicer( pFilesystem, WAVEOUTPUT_FREQUENCY, bestNumChannels, bitsPerChannel ) )
  616. {
  617. return false;
  618. }
  619. m_pOutIterator->ChunkStart( WAVE_DATA );
  620. float timeoffset = 0.0f;
  621. m_Combined.Reset();
  622. m_Combined.SetText( "" );
  623. int c = m_Work.Count();
  624. for ( int i = 0; i < c; ++i )
  625. {
  626. int currentsample = 0;
  627. CombinerWork *curitem = m_Work[ i ];
  628. CombinerWork *nextitem = NULL;
  629. if ( i != c - 1 )
  630. {
  631. nextitem = m_Work[ i + 1 ];
  632. }
  633. float duration = curitem->duration;
  634. AppendWaveData( currentsample, curitem->wave, curitem->mixer );
  635. AddSentenceToCombined( timeoffset, curitem->sentence );
  636. timeoffset += duration;
  637. if ( nextitem != NULL )
  638. {
  639. float nextstart = nextitem->entry->startoffset;
  640. float silence_time = nextstart - timeoffset;
  641. AppendSilence( currentsample, silence_time );
  642. timeoffset += silence_time;
  643. }
  644. }
  645. m_pOutIterator->ChunkFinish();
  646. // Checksum the work items
  647. unsigned int checksum = ComputeChecksum();
  648. // Make sure the checksum is embedded in the data file
  649. m_Combined.SetDataCheckSum( checksum );
  650. // Msg( " checksum computed %u\n", checksum );
  651. m_pOutIterator->ChunkStart( WAVE_VALVEDATA );
  652. StoreValveDataChunk( m_Combined );
  653. m_pOutIterator->ChunkFinish();
  654. return true;
  655. }
  656. void CSoundCombiner::AddSentenceToCombined( float offset, CSentence& sentence )
  657. {
  658. m_Combined.Append( offset, sentence );
  659. }