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.

1186 lines
37 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include "sfmobjects/SFMPhonemeExtractor.h"
  7. #include "tier2/riff.h"
  8. #include "PhonemeConverter.h"
  9. #include "filesystem.h"
  10. #include "tier1/utlbuffer.h"
  11. #include "sentence.h"
  12. #include "movieobjects/dmesound.h"
  13. #include "movieobjects/dmeanimationset.h"
  14. #include "movieobjects/dmebookmark.h"
  15. #include "movieobjects/dmeclip.h"
  16. #include "movieobjects/dmechannel.h"
  17. #include "soundchars.h"
  18. #include "tier2/p4helpers.h"
  19. #include "tier2/soundutils.h"
  20. #include "tier1/utldict.h"
  21. #include <windows.h> // WAVEFORMATEX, WAVEFORMAT and ADPCM WAVEFORMAT!!!
  22. #include <mmreg.h>
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. static const char *s_pAttributeValueNames[LOG_PREVIEW_FLEX_CHANNEL_COUNT] =
  26. {
  27. "value",
  28. "balance",
  29. "multilevel"
  30. };
  31. static const char *s_pDefaultAttributeValueNames[LOG_PREVIEW_FLEX_CHANNEL_COUNT] =
  32. {
  33. "defaultValue",
  34. "defaultBalance",
  35. "defaultMultilevel"
  36. };
  37. struct Extractor
  38. {
  39. PE_APITYPE apitype;
  40. CSysModule *module;
  41. IPhonemeExtractor *extractor;
  42. };
  43. //-----------------------------------------------------------------------------
  44. // Implementations of the phoneme extractor
  45. //-----------------------------------------------------------------------------
  46. class CSFMPhonemeExtractor : public ISFMPhonemeExtractor
  47. {
  48. public:
  49. CSFMPhonemeExtractor();
  50. // Inherited from ISFMPhonemeExtractor
  51. virtual bool Init();
  52. virtual void Shutdown();
  53. virtual int GetAPICount();
  54. virtual void GetAPIInfo( int index, CUtlString* pPrintName, PE_APITYPE *pAPIType );
  55. virtual void Extract( const PE_APITYPE& apiType, ExtractDesc_t& info, bool bWritePhonemesToWavFiles );
  56. virtual void ReApply( ExtractDesc_t& info );
  57. virtual bool GetSentence( CDmeGameSound *gameSound, CSentence& sentence );
  58. private:
  59. int FindExtractor( PE_APITYPE type );
  60. bool GetWaveFormat( const char *filename, CUtlBuffer* pFormat, int *pDataSize, CSentence& sentence, bool &bGotSentence );
  61. void LogPhonemes( int nItemIndex, ExtractDesc_t& info );
  62. void ClearInterstitialSpaces( CDmeChannelsClip *pChannelsClip, CUtlDict< LogPreview_t *, int >& controlLookup, ExtractDesc_t& info );
  63. void StampControlValueLogs( CDmePreset *preset, DmeTime_t tHeadPosition, float flIntensity, CUtlDict< LogPreview_t *, int > &controlLookup );
  64. void WriteCurrentValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup );
  65. void WriteDefaultValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup );
  66. void BuildPhonemeLogList( CUtlVector< LogPreview_t > &list, CUtlVector< CDmeLog * > &logs );
  67. CDmeChannelsClip* FindFacialChannelsClip( const CUtlVector< LogPreview_t > &list );
  68. void BuildPhonemeToPresetMapping( const CUtlVector< CBasePhonemeTag * > &stream, CDmeAnimationSet *pSet, CDmePresetGroup * pPresetGroup, CUtlDict< CDmePreset *, unsigned short > &phonemeToPresetDict );
  69. CUtlVector< Extractor > m_Extractors;
  70. int m_nCurrentExtractor;
  71. };
  72. //-----------------------------------------------------------------------------
  73. // Singleton
  74. //-----------------------------------------------------------------------------
  75. static CSFMPhonemeExtractor g_ExtractorSingleton;
  76. ISFMPhonemeExtractor *sfm_phonemeextractor = &g_ExtractorSingleton;
  77. //-----------------------------------------------------------------------------
  78. // Constructor
  79. //-----------------------------------------------------------------------------
  80. CSFMPhonemeExtractor::CSFMPhonemeExtractor() : m_nCurrentExtractor( -1 )
  81. {
  82. }
  83. //-----------------------------------------------------------------------------
  84. // Init, shutdown
  85. //-----------------------------------------------------------------------------
  86. bool CSFMPhonemeExtractor::Init()
  87. {
  88. // Enumerate modules under bin folder of exe
  89. FileFindHandle_t findHandle;
  90. const char *pFilename = g_pFullFileSystem->FindFirstEx( "phonemeextractors/*.dll", "EXECUTABLE_PATH", &findHandle );
  91. while( pFilename )
  92. {
  93. char fullpath[ 512 ];
  94. Q_snprintf( fullpath, sizeof( fullpath ), "phonemeextractors/%s", pFilename );
  95. // Msg( "Loading extractor from %s\n", fullpath );
  96. Extractor e;
  97. e.module = g_pFullFileSystem->LoadModule( fullpath );
  98. if ( !e.module )
  99. {
  100. pFilename = g_pFullFileSystem->FindNext( findHandle );
  101. continue;
  102. }
  103. CreateInterfaceFn factory = Sys_GetFactory( e.module );
  104. if ( !factory )
  105. {
  106. pFilename = g_pFullFileSystem->FindNext( findHandle );
  107. continue;
  108. }
  109. e.extractor = ( IPhonemeExtractor * )factory( VPHONEME_EXTRACTOR_INTERFACE, NULL );
  110. if ( !e.extractor )
  111. {
  112. Warning( "Unable to get IPhonemeExtractor interface version %s from %s\n", VPHONEME_EXTRACTOR_INTERFACE, fullpath );
  113. pFilename = g_pFullFileSystem->FindNext( findHandle );
  114. continue;
  115. }
  116. e.apitype = e.extractor->GetAPIType();
  117. m_Extractors.AddToTail( e );
  118. pFilename = g_pFullFileSystem->FindNext( findHandle );
  119. }
  120. g_pFullFileSystem->FindClose( findHandle );
  121. return true;
  122. }
  123. void CSFMPhonemeExtractor::Shutdown()
  124. {
  125. int c = m_Extractors.Count();
  126. for ( int i = c - 1; i >= 0; i-- )
  127. {
  128. Extractor *e = &m_Extractors[ i ];
  129. g_pFullFileSystem->UnloadModule( e->module );
  130. }
  131. m_Extractors.RemoveAll();
  132. }
  133. //-----------------------------------------------------------------------------
  134. // Finds an extractor of a particular type
  135. //-----------------------------------------------------------------------------
  136. int CSFMPhonemeExtractor::FindExtractor( PE_APITYPE type )
  137. {
  138. for ( int i=0; i < m_Extractors.Count(); i++ )
  139. {
  140. if ( m_Extractors[i].apitype == type )
  141. return i;
  142. }
  143. return -1;
  144. }
  145. //-----------------------------------------------------------------------------
  146. // Iterates over extractors
  147. //-----------------------------------------------------------------------------
  148. int CSFMPhonemeExtractor::GetAPICount()
  149. {
  150. return m_Extractors.Count();
  151. }
  152. void CSFMPhonemeExtractor::GetAPIInfo( int index, CUtlString* pPrintName, PE_APITYPE *pAPIType )
  153. {
  154. Assert( pPrintName );
  155. Assert( pAPIType );
  156. pPrintName->Set( m_Extractors[ index ].extractor->GetName() );
  157. *pAPIType = m_Extractors[ index ].apitype;
  158. }
  159. static void ParseSentence( CSentence& sentence, IterateRIFF &walk )
  160. {
  161. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  162. buf.EnsureCapacity( walk.ChunkSize() );
  163. walk.ChunkRead( buf.Base() );
  164. buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
  165. sentence.InitFromDataChunk( buf.Base(), buf.TellPut() );
  166. }
  167. bool CSFMPhonemeExtractor::GetWaveFormat( const char *filename, CUtlBuffer *pBuf, int *pDataSize, CSentence& sentence, bool &bGotSentence )
  168. {
  169. InFileRIFF riff( filename, *g_pFSIOReadBinary );
  170. Assert( riff.RIFFName() == RIFF_WAVE );
  171. // set up the iterator for the whole file (root RIFF is a chunk)
  172. IterateRIFF walk( riff, riff.RIFFSize() );
  173. bool gotFmt = false;
  174. bool gotData = false;
  175. bGotSentence = false;
  176. // Walk input chunks and copy to output
  177. while ( walk.ChunkAvailable() )
  178. {
  179. switch ( walk.ChunkName() )
  180. {
  181. case WAVE_FMT:
  182. {
  183. pBuf->SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
  184. walk.ChunkRead( pBuf->Base() );
  185. gotFmt = true;
  186. }
  187. break;
  188. case WAVE_DATA:
  189. {
  190. *pDataSize = walk.ChunkSize();
  191. gotData = true;
  192. }
  193. break;
  194. case WAVE_VALVEDATA:
  195. {
  196. bGotSentence = true;
  197. ParseSentence( sentence, walk );
  198. }
  199. break;
  200. default:
  201. break;
  202. }
  203. // Done
  204. if ( gotFmt && gotData && bGotSentence )
  205. return true;
  206. walk.ChunkNext();
  207. }
  208. return ( gotFmt && gotData );
  209. }
  210. bool CSFMPhonemeExtractor::GetSentence( CDmeGameSound *gameSound, CSentence& sentence )
  211. {
  212. const char *filename = gameSound->m_SoundName.Get();
  213. Assert( filename && filename [ 0 ] );
  214. char soundname[ 512 ];
  215. // Note, calling PSkipSoundChars to remove any decorator characters used by the engine!!!
  216. Q_snprintf( soundname, sizeof( soundname ), "sound/%s", PSkipSoundChars( filename ) );
  217. Q_FixSlashes( soundname );
  218. char fullpath[ 512 ];
  219. g_pFullFileSystem->RelativePathToFullPath( soundname, "GAME", fullpath, sizeof( fullpath ) );
  220. // Get sound file metrics of interest
  221. CUtlBuffer buf;
  222. int nDataSize;
  223. bool bValidSentence = false;
  224. if ( !GetWaveFormat( soundname, &buf, &nDataSize, sentence, bValidSentence ) )
  225. return false;
  226. return bValidSentence;
  227. }
  228. static void BuildPhonemeStream( CSentence& in, CUtlVector< CBasePhonemeTag * >& list )
  229. {
  230. for ( int i = 0; i < in.m_Words.Count(); ++i )
  231. {
  232. CWordTag *w = in.m_Words[ i ];
  233. if ( !w )
  234. continue;
  235. for ( int j = 0; j < w->m_Phonemes.Count(); ++j )
  236. {
  237. CPhonemeTag *ph = w->m_Phonemes[ j ];
  238. if ( !ph )
  239. continue;
  240. CBasePhonemeTag *newTag = new CBasePhonemeTag( *ph );
  241. list.AddToTail( newTag );
  242. }
  243. }
  244. if ( !in.m_Words.Count() && in.m_RunTimePhonemes.Count() )
  245. {
  246. for ( int i = 0 ; i < in.m_RunTimePhonemes.Count(); ++i )
  247. {
  248. CBasePhonemeTag *newTag = new CBasePhonemeTag( *in.m_RunTimePhonemes[ i ] );
  249. list.AddToTail( newTag );
  250. }
  251. }
  252. }
  253. //-----------------------------------------------------------------------------
  254. // Purpose: Same the phoneme data into the sound files
  255. //-----------------------------------------------------------------------------
  256. static void StoreValveDataChunk( CSentence& sentence, IterateOutputRIFF& store )
  257. {
  258. // Buffer and dump data
  259. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  260. sentence.SaveToBuffer( buf );
  261. // Copy into store
  262. store.ChunkWriteData( buf.Base(), buf.TellPut() );
  263. }
  264. static bool SaveSentenceToWavFile( const char *pWavFile, CSentence& sentence )
  265. {
  266. char pTempFile[ 512 ];
  267. Q_StripExtension( pWavFile, pTempFile, sizeof( pTempFile ) );
  268. Q_DefaultExtension( pTempFile, ".tmp", sizeof( pTempFile ) );
  269. if ( g_pFullFileSystem->FileExists( pTempFile, "GAME" ) )
  270. {
  271. g_pFullFileSystem->RemoveFile( pTempFile, "GAME" );
  272. }
  273. CP4AutoEditAddFile p4Checkout( pWavFile );
  274. if ( !g_pFullFileSystem->IsFileWritable( pWavFile ) )
  275. {
  276. Warning( "%s is not writable, can't save sentence data to file\n", pWavFile );
  277. return false;
  278. }
  279. // Rename original pWavFile to temp
  280. g_pFullFileSystem->RenameFile( pWavFile, pTempFile, "GAME" );
  281. // NOTE: Put this in it's own scope so that the destructor for outfileRFF actually closes the file!!!!
  282. {
  283. // Read from Temp
  284. InFileRIFF riff( pTempFile, *g_pFSIOReadBinary );
  285. Assert( riff.RIFFName() == RIFF_WAVE );
  286. // set up the iterator for the whole file (root RIFF is a chunk)
  287. IterateRIFF walk( riff, riff.RIFFSize() );
  288. // And put data back into original pWavFile by name
  289. OutFileRIFF riffout( pWavFile, *g_pFSIOWriteBinary );
  290. IterateOutputRIFF store( riffout );
  291. bool bWordTrackWritten = false;
  292. // Walk input chunks and copy to output
  293. while ( walk.ChunkAvailable() )
  294. {
  295. store.ChunkStart( walk.ChunkName() );
  296. switch ( walk.ChunkName() )
  297. {
  298. case WAVE_VALVEDATA:
  299. {
  300. // Overwrite data
  301. StoreValveDataChunk( sentence, store );
  302. bWordTrackWritten = true;
  303. }
  304. break;
  305. default:
  306. store.CopyChunkData( walk );
  307. break;
  308. }
  309. store.ChunkFinish();
  310. walk.ChunkNext();
  311. }
  312. // If we didn't write it above, write it now
  313. if ( !bWordTrackWritten )
  314. {
  315. store.ChunkStart( WAVE_VALVEDATA );
  316. StoreValveDataChunk( sentence, store );
  317. store.ChunkFinish();
  318. }
  319. }
  320. // Remove temp file
  321. g_pFullFileSystem->RemoveFile( pTempFile, NULL );
  322. return true;
  323. }
  324. //-----------------------------------------------------------------------------
  325. // Main entry point for phoneme extraction
  326. //-----------------------------------------------------------------------------
  327. void CSFMPhonemeExtractor::Extract( const PE_APITYPE& apiType, ExtractDesc_t& info, bool bWritePhonemesToWavFiles )
  328. {
  329. if ( !info.m_pSet )
  330. return;
  331. int iExtractor = FindExtractor( apiType );
  332. if ( iExtractor == -1 )
  333. return;
  334. Extractor& extractor = m_Extractors[ iExtractor ];
  335. int nWorkItem;
  336. for ( nWorkItem = 0; nWorkItem < info.m_WorkList.Count(); ++nWorkItem )
  337. {
  338. CExtractInfo& workItem = info.m_WorkList[ nWorkItem ];
  339. workItem.m_flDuration = 0.0f;
  340. CSentence in;
  341. CSentence out;
  342. in.SetText( workItem.m_sHintText.String() );
  343. out.SetText( workItem.m_sHintText.String() );
  344. const char *pFileName = workItem.m_pSound->m_SoundName.Get();
  345. Assert( pFileName && pFileName [ 0 ] );
  346. char pSoundName[ 512 ];
  347. // Note, calling PSkipSoundChars to remove any decorator characters used by the engine!!!
  348. Q_snprintf( pSoundName, sizeof( pSoundName ), "sound/%s", PSkipSoundChars( pFileName ) );
  349. Q_FixSlashes( pSoundName );
  350. char pFullPath[ 512 ];
  351. g_pFullFileSystem->RelativePathToFullPath( pSoundName, "GAME", pFullPath, sizeof( pFullPath ) );
  352. // Get sound file metrics of interest
  353. CUtlBuffer buf;
  354. WAVEFORMATEX *format;
  355. int nDataSize;
  356. if ( !GetWaveFormat( pSoundName, &buf, &nDataSize, workItem.m_Sentence, workItem.m_bSentenceValid ) )
  357. continue;
  358. format = ( WAVEFORMATEX * )buf.Base();
  359. if ( !( format->wBitsPerSample > ( 1 << 3 ) ) )
  360. {
  361. // Have to warn and early-out here to avoid crashing with "integer divide by zero" below
  362. Warning( "Cannot extract phonemes from '%s', %u bits per sample.\n", pSoundName, format->wBitsPerSample );
  363. continue;
  364. }
  365. int nBitsPerSample = format->wBitsPerSample;
  366. float flSampleRate = (float)format->nSamplesPerSec;
  367. int nChannels = format->nChannels;
  368. int nSampleCount = nDataSize / ( nBitsPerSample >> 3 );
  369. float flTrueSampleSize = ( nBitsPerSample * nChannels ) >> 3;
  370. if ( format->wFormatTag == WAVE_FORMAT_ADPCM )
  371. {
  372. nBitsPerSample = 16;
  373. flTrueSampleSize = 0.5f;
  374. ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)buf.Base();
  375. int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2;
  376. blockSize += 7 * pFormat->wfx.nChannels;
  377. int blockCount = nDataSize / blockSize;
  378. int blockRem = nDataSize % blockSize;
  379. // total samples in complete blocks
  380. nSampleCount = blockCount * pFormat->wSamplesPerBlock;
  381. // add remaining in a short block
  382. if ( blockRem )
  383. {
  384. nSampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / nChannels);
  385. }
  386. }
  387. if ( flSampleRate > 0.0f )
  388. {
  389. workItem.m_flDuration = (float)nSampleCount / flSampleRate;
  390. }
  391. in.CreateEventWordDistribution( workItem.m_sHintText.String(), workItem.m_flDuration );
  392. if ( !workItem.m_bUseSentence || !workItem.m_bSentenceValid )
  393. {
  394. extractor.extractor->Extract( pFullPath,
  395. (int)( workItem.m_flDuration * flSampleRate * flTrueSampleSize ),
  396. Msg, in, out );
  397. // Tracker 57389:
  398. // Total hack to fix a bug where the Lipsinc extractor is messing up the # channels on 16 bit stereo waves
  399. if ( apiType == SPEECH_API_LIPSINC && nChannels == 2 && nBitsPerSample == 16 )
  400. {
  401. flTrueSampleSize *= 2.0f;
  402. }
  403. float bytespersecond = flSampleRate * flTrueSampleSize;
  404. int i;
  405. // Now convert byte offsets to times
  406. for ( i = 0; i < out.m_Words.Size(); i++ )
  407. {
  408. CWordTag *tag = out.m_Words[ i ];
  409. Assert( tag );
  410. if ( !tag )
  411. continue;
  412. tag->m_flStartTime = ( float )(tag->m_uiStartByte ) / bytespersecond;
  413. tag->m_flEndTime = ( float )(tag->m_uiEndByte ) / bytespersecond;
  414. for ( int j = 0; j < tag->m_Phonemes.Size(); j++ )
  415. {
  416. CPhonemeTag *ptag = tag->m_Phonemes[ j ];
  417. Assert( ptag );
  418. if ( !ptag )
  419. continue;
  420. ptag->SetStartTime( ( float )(ptag->m_uiStartByte ) / bytespersecond );
  421. ptag->SetEndTime( ( float )(ptag->m_uiEndByte ) / bytespersecond );
  422. }
  423. }
  424. if ( bWritePhonemesToWavFiles )
  425. {
  426. SaveSentenceToWavFile( pFullPath, out );
  427. }
  428. }
  429. else
  430. {
  431. Msg( "Using .wav file phonemes for (%s)\n", pSoundName );
  432. out = workItem.m_Sentence;
  433. }
  434. // Now create channel data
  435. workItem.ClearTags();
  436. BuildPhonemeStream( out, workItem.m_ApplyTags );
  437. }
  438. if ( info.m_bCreateBookmarks )
  439. {
  440. info.m_pSet->GetBookmarks().RemoveAll();
  441. }
  442. for ( nWorkItem = 0; nWorkItem < info.m_WorkList.Count(); ++nWorkItem )
  443. {
  444. LogPhonemes( nWorkItem, info );
  445. }
  446. }
  447. //-----------------------------------------------------------------------------
  448. //
  449. //-----------------------------------------------------------------------------
  450. static bool UniquePhonemeLessFunc( CBasePhonemeTag * const & lhs, CBasePhonemeTag * const & rhs )
  451. {
  452. return lhs->GetPhonemeCode() < rhs->GetPhonemeCode();
  453. }
  454. void CSFMPhonemeExtractor::BuildPhonemeToPresetMapping( const CUtlVector< CBasePhonemeTag * > &stream,
  455. CDmeAnimationSet *pSet, CDmePresetGroup *pPresetGroup, CUtlDict< CDmePreset *, unsigned short > &phonemeToPresetDict )
  456. {
  457. int i;
  458. CUtlRBTree< CBasePhonemeTag * > uniquePhonemes( 0, 0, UniquePhonemeLessFunc );
  459. for ( i = 0; i < stream.Count(); ++i )
  460. {
  461. CBasePhonemeTag *tag = stream[ i ];
  462. if ( uniquePhonemes.Find( tag ) == uniquePhonemes.InvalidIndex() )
  463. {
  464. uniquePhonemes.Insert( tag );
  465. }
  466. }
  467. for ( i = uniquePhonemes.FirstInorder(); i != uniquePhonemes.InvalidIndex(); i = uniquePhonemes.NextInorder( i ) )
  468. {
  469. CBasePhonemeTag *tag = uniquePhonemes[ i ];
  470. // Convert phoneme code to text
  471. char ph[ 32 ];
  472. Q_strncpy( ph, ConvertPhoneme( tag->GetPhonemeCode() ), sizeof( ph ) );
  473. char remappedph[ 32 ];
  474. // By default we search for a preset name p_xxx where xxx is the phoneme string
  475. Q_snprintf( remappedph, sizeof( remappedph ), "p_%s", ph );
  476. // Now find the preset in the animation set converter
  477. CDmePhonemeMapping *mapping = pSet->FindMapping( ph );
  478. if ( mapping )
  479. {
  480. Q_strncpy( remappedph, mapping->GetValueString( "preset" ), sizeof( remappedph ) );
  481. }
  482. // Now look up the preset, if it exists
  483. CDmePreset *preset = pPresetGroup->FindPreset( remappedph );
  484. if ( !preset )
  485. {
  486. Warning( "Animation set '%s' missing phoneme preset for '%s' -> '%s'\n",
  487. pSet->GetName(), ph, remappedph );
  488. continue;
  489. }
  490. // Add to dictionary if it's not already there
  491. if ( phonemeToPresetDict.Find( ph ) == phonemeToPresetDict.InvalidIndex() )
  492. {
  493. phonemeToPresetDict.Insert( ph, preset );
  494. }
  495. }
  496. }
  497. //-----------------------------------------------------------------------------
  498. // Finds the channels clip which refers to facial control values
  499. //-----------------------------------------------------------------------------
  500. CDmeChannelsClip* CSFMPhonemeExtractor::FindFacialChannelsClip( const CUtlVector< LogPreview_t > &list )
  501. {
  502. CDmeChannelsClip *pChannelsClip = NULL;
  503. int i;
  504. for ( i = list.Count() - 1; i >= 0; --i )
  505. {
  506. const LogPreview_t &lp = list[i];
  507. CDmeChannelsClip *check = FindAncestorReferencingElement< CDmeChannelsClip >( (CDmElement *)lp.m_hChannels[ 0 ].Get() );
  508. if ( !pChannelsClip && check )
  509. {
  510. pChannelsClip = check;
  511. }
  512. else
  513. {
  514. if ( pChannelsClip != check )
  515. {
  516. Warning( "Selected controls overlap multiple channels clips!!!\n" );
  517. }
  518. }
  519. }
  520. if ( !pChannelsClip )
  521. {
  522. Warning( "Unable to determine destination channels clip!!!\n" );
  523. }
  524. return pChannelsClip;
  525. }
  526. //-----------------------------------------------------------------------------
  527. // Builds the list of logs which target facial control values
  528. //-----------------------------------------------------------------------------
  529. void CSFMPhonemeExtractor::BuildPhonemeLogList( CUtlVector< LogPreview_t > &list, CUtlVector< CDmeLog * > &logs )
  530. {
  531. for ( int i = 0; i < list.Count(); ++i )
  532. {
  533. LogPreview_t& p = list[ i ];
  534. for ( int channel = 0; channel < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++channel )
  535. {
  536. CDmeChannel *ch = p.m_hChannels[ channel ];
  537. if ( !ch )
  538. continue;
  539. CDmeLog *log = p.m_hChannels[ channel ]->GetLog();
  540. if ( !log )
  541. continue;
  542. logs.AddToTail( log );
  543. }
  544. }
  545. }
  546. //-----------------------------------------------------------------------------
  547. // Writes default values into all log layers targetting facial control values
  548. //-----------------------------------------------------------------------------
  549. void CSFMPhonemeExtractor::WriteDefaultValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup )
  550. {
  551. // Write a zero into all relevant log layers
  552. for ( int j = controlLookup.First(); j != controlLookup.InvalidIndex(); j = controlLookup.Next( j ) )
  553. {
  554. LogPreview_t* lp = controlLookup[ j ];
  555. CDmElement *pControl = lp->m_hControl;
  556. for ( int chIndex = 0; chIndex < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++chIndex )
  557. {
  558. CDmeChannel *pChannel = lp->m_hChannels[ chIndex ];
  559. if ( !pChannel )
  560. continue;
  561. // Now get the log for the channel
  562. CDmeFloatLog *pFloatLog = CastElement< CDmeFloatLog >( pChannel->GetLog() );
  563. if ( !pFloatLog )
  564. continue;
  565. CDmeFloatLogLayer *pLayer = pFloatLog->GetLayer( pFloatLog->GetTopmostLayer() );
  566. if ( !pLayer )
  567. continue;
  568. float flDefaultValue = pControl->GetValue< float >( s_pDefaultAttributeValueNames[chIndex] );
  569. pLayer->InsertKey( tHeadPosition, flDefaultValue );
  570. }
  571. }
  572. }
  573. //-----------------------------------------------------------------------------
  574. // Creates a new log key based on the interpolated value at that time
  575. //-----------------------------------------------------------------------------
  576. void CSFMPhonemeExtractor::WriteCurrentValuesIntoLogLayers( DmeTime_t tHeadPosition, const CUtlDict< LogPreview_t *, int > &controlLookup )
  577. {
  578. // Write a zero into all relevant log layers
  579. for ( int j = controlLookup.First(); j != controlLookup.InvalidIndex(); j = controlLookup.Next( j ) )
  580. {
  581. LogPreview_t* lp = controlLookup[ j ];
  582. for ( int chIndex = 0; chIndex < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++chIndex )
  583. {
  584. CDmeChannel *pChannel = lp->m_hChannels[ chIndex ];
  585. if ( !pChannel )
  586. continue;
  587. // Now get the log for the channel
  588. CDmeFloatLog *pFloatLog = CastElement< CDmeFloatLog >( pChannel->GetLog() );
  589. if ( !pFloatLog )
  590. continue;
  591. CDmeFloatLogLayer *pLayer = pFloatLog->GetLayer( pFloatLog->GetTopmostLayer() );
  592. if ( !pLayer )
  593. continue;
  594. float flCurrentValue = pLayer->GetValue( tHeadPosition );
  595. pLayer->InsertKey( tHeadPosition, flCurrentValue );
  596. }
  597. }
  598. }
  599. //-----------------------------------------------------------------------------
  600. // Samples extracted phoneme data and stamps that values into control value logs
  601. //-----------------------------------------------------------------------------
  602. void CSFMPhonemeExtractor::StampControlValueLogs( CDmePreset *preset, DmeTime_t tHeadPosition, float flIntensity, CUtlDict< LogPreview_t *, int > &controlLookup )
  603. {
  604. // Now walk the logs required by the preset
  605. const CDmrElementArray< CDmElement > &controlValues = preset->GetControlValues( );
  606. for ( int j = 0; j < controlValues.Count(); ++j )
  607. {
  608. // This control contains the preset value
  609. CDmElement *presetControl = controlValues[ j ];
  610. if ( !presetControl )
  611. continue;
  612. int visIndex = controlLookup.Find( presetControl->GetName() );
  613. if ( visIndex == controlLookup.InvalidIndex() )
  614. continue;
  615. LogPreview_t* lp = controlLookup[ visIndex ];
  616. for ( int chIndex = 0; chIndex < LOG_PREVIEW_FLEX_CHANNEL_COUNT; ++chIndex )
  617. {
  618. CDmeChannel *ch = lp->m_hChannels[ chIndex ];
  619. if ( !ch )
  620. continue;
  621. // Whereas this control contains the "default" value for the slider (since the presetControl won't have that value)
  622. CDmElement *defaultValueControl = lp->m_hControl.Get();
  623. if ( !defaultValueControl )
  624. continue;
  625. // Now get the log for the channel
  626. CDmeLog *log = ch->GetLog();
  627. if ( !log )
  628. {
  629. Assert( 0 );
  630. continue;
  631. }
  632. CDmeFloatLog *floatLog = CastElement< CDmeFloatLog >( log );
  633. if ( !floatLog )
  634. continue;
  635. CDmeFloatLogLayer *pLayer = floatLog->GetLayer( floatLog->GetTopmostLayer() );
  636. if ( !pLayer )
  637. continue;
  638. float flDefault = defaultValueControl->GetValue< float >( s_pDefaultAttributeValueNames[chIndex] );
  639. float flControlValue = presetControl->GetValue< float >( s_pAttributeValueNames[ chIndex ] );
  640. float flNewValue = flIntensity * ( flControlValue - flDefault );
  641. float flCurrent = pLayer->GetValue( tHeadPosition ) - flDefault;
  642. // Accumulate new value into topmost layer
  643. pLayer->InsertKey( tHeadPosition, flCurrent + flNewValue + flDefault );
  644. }
  645. }
  646. }
  647. void CSFMPhonemeExtractor::ClearInterstitialSpaces( CDmeChannelsClip *pChannelsClip, CUtlDict< LogPreview_t *, int >& controlLookup, ExtractDesc_t& info )
  648. {
  649. Assert( info.m_pShot );
  650. Assert( pChannelsClip );
  651. if ( info.m_WorkList.Count() == 0 )
  652. return;
  653. // This is handled by the main layering code...
  654. if ( info.m_nExtractType == EXTRACT_WIPE_SOUNDS )
  655. return;
  656. // Now walk through all relevant logs
  657. CUtlVector< CDmeLog * > logs;
  658. BuildPhonemeLogList( info.m_ControlList, logs );
  659. DmeTime_t tMinTime( DMETIME_MAXTIME );
  660. DmeTime_t tMaxTime( DMETIME_MINTIME );
  661. int i;
  662. // Walk work items and figure out time bounds
  663. for ( i = 0; i < info.m_WorkList.Count(); ++i )
  664. {
  665. CExtractInfo &item = info.m_WorkList[ i ];
  666. CUtlVector< CDmeHandle< CDmeClip > > srcStack;
  667. CUtlVector< CDmeHandle< CDmeClip > > dstStack;
  668. // Convert original .wav start to animation set channels clip relative time
  669. item.m_pClip->BuildClipStack( &srcStack, info.m_pMovie, info.m_pShot );
  670. // NOTE: Time bounds measured in sound media time goes from 0 -> flWaveDuration
  671. DmeTime_t tSoundMediaStartTime = CDmeClip::FromChildMediaTime( srcStack, DMETIME_ZERO, false );
  672. DmeTime_t tSoundMediaEndTime = CDmeClip::FromChildMediaTime( srcStack, DmeTime_t( item.m_flDuration ), false );
  673. // NOTE: Start and end time are measured in sound media time
  674. DmeTime_t tStartTime = item.m_pClip->GetStartInChildMediaTime();
  675. DmeTime_t tEndTime = item.m_pClip->GetEndInChildMediaTime();
  676. // And convert back down into channels clip relative time
  677. pChannelsClip->BuildClipStack( &dstStack, info.m_pMovie, info.m_pShot );
  678. // Now convert back down to channels clip relative time
  679. DmeTime_t tChannelMediaStartTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaStartTime, false );
  680. DmeTime_t tChannelMediaEndTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaEndTime, false );
  681. // Find a scale + offset which transforms data in media space of the sound [namely, the phonemes]
  682. // into the media space of the channels [the logs that drive the facial animation]
  683. DmeTime_t tEndDuration = tChannelMediaEndTime - tChannelMediaStartTime;
  684. double flScale = ( item.m_flDuration != 0.0f ) ? tEndDuration.GetSeconds() / item.m_flDuration : 0.0f;
  685. DmeTime_t tOffset = tChannelMediaStartTime;
  686. DmeTime_t tChannelRelativeStartTime( tStartTime * flScale );
  687. tChannelRelativeStartTime += tOffset;
  688. DmeTime_t tChannelRelativeEndTime( tEndTime * flScale );
  689. tChannelRelativeEndTime += tOffset;
  690. if ( tChannelRelativeStartTime < tMinTime )
  691. {
  692. tMinTime = tChannelRelativeStartTime;
  693. }
  694. if ( tChannelRelativeEndTime > tMaxTime )
  695. {
  696. tMaxTime = tChannelRelativeEndTime;
  697. }
  698. }
  699. // Bloat by one quantum
  700. tMinTime -= DMETIME_MINDELTA;
  701. tMaxTime += DMETIME_MINDELTA;
  702. for ( i = 0; i < logs.Count(); ++i )
  703. {
  704. CDmeLog *log = logs[ i ];
  705. Assert( log->GetNumLayers() == 1 );
  706. CDmeLogLayer *layer = log->GetLayer( log->GetTopmostLayer() );
  707. if ( info.m_nExtractType == EXTRACT_WIPE_RANGE )
  708. {
  709. // Write default value keys into log
  710. // Write a default value at that time
  711. WriteDefaultValuesIntoLogLayers( tMinTime, controlLookup );
  712. // Write a default value at that time
  713. WriteDefaultValuesIntoLogLayers( tMaxTime, controlLookup );
  714. // Now discard all keys > tMinTime and < tMaxTime
  715. for ( int j = layer->GetKeyCount() - 1; j >= 0; --j )
  716. {
  717. DmeTime_t &t = layer->GetKeyTime( j );
  718. if ( t <= tMinTime )
  719. continue;
  720. if ( t >= tMaxTime )
  721. continue;
  722. layer->RemoveKey( j );
  723. }
  724. }
  725. else
  726. {
  727. Assert( info.m_nExtractType == EXTRACT_WIPE_CLIP );
  728. layer->ClearKeys();
  729. }
  730. }
  731. }
  732. void AddAnimSetBookmarkAtSoundMediaTime( const char *pName, DmeTime_t tStart, DmeTime_t tEnd, const CUtlVector< CDmeHandle< CDmeClip > > &srcStack, ExtractDesc_t& info )
  733. {
  734. tStart = CDmeClip::FromChildMediaTime( srcStack, tStart, false );
  735. tEnd = CDmeClip::FromChildMediaTime( srcStack, tEnd, false );
  736. tStart = info.m_pShot->ToChildMediaTime( tStart, false );
  737. tEnd = info.m_pShot->ToChildMediaTime( tEnd, false );
  738. CDmeBookmark *pBookmark = CreateElement< CDmeBookmark >( pName );
  739. pBookmark->SetNote( pName );
  740. pBookmark->SetTime( tStart );
  741. pBookmark->SetDuration( tEnd - tStart );
  742. info.m_pSet->GetBookmarks().AddToTail( pBookmark );
  743. }
  744. //-----------------------------------------------------------------------------
  745. // Main entry point for generating phoneme logs
  746. //-----------------------------------------------------------------------------
  747. void CSFMPhonemeExtractor::LogPhonemes( int nItemIndex, ExtractDesc_t& info )
  748. {
  749. CExtractInfo &item = info.m_WorkList[ nItemIndex ];
  750. // Validate input parameters
  751. Assert( info.m_pSet && item.m_pClip && item.m_pSound );
  752. if ( !info.m_pSet || !item.m_pClip || !item.m_pSound )
  753. return;
  754. CDmePresetGroup *pPresetGroup = info.m_pSet->FindPresetGroup( "phoneme" );
  755. if ( !pPresetGroup )
  756. {
  757. Warning( "Animation set '%s' missing preset group 'phoneme'\n", info.m_pSet->GetName() );
  758. return;
  759. }
  760. if ( !info.m_pSet->GetPhonemeMap().Count() )
  761. {
  762. info.m_pSet->RestoreDefaultPhonemeMap();
  763. }
  764. // Walk through phoneme stack and build list of unique presets
  765. CUtlDict< CDmePreset *, unsigned short > phonemeToPresetDict;
  766. BuildPhonemeToPresetMapping( item.m_ApplyTags, info.m_pSet, pPresetGroup, phonemeToPresetDict );
  767. CDmeChannelsClip *pChannelsClip = FindFacialChannelsClip( info.m_ControlList );
  768. if ( !pChannelsClip )
  769. return;
  770. // Build a fast lookup of the visible sliders
  771. int i;
  772. CUtlDict< LogPreview_t *, int > controlLookup;
  773. for ( i = 0; i < info.m_ControlList.Count(); ++i )
  774. {
  775. controlLookup.Insert( info.m_ControlList[ i ].m_hControl->GetName(), &info.m_ControlList[ i ] );
  776. }
  777. // Only need to do this on the first item and we have multiple .wavs selected
  778. if ( nItemIndex == 0 && info.m_WorkList.Count() > 1 )
  779. {
  780. ClearInterstitialSpaces( pChannelsClip, controlLookup, info );
  781. }
  782. // Set up time selection, put channels into record and stamp out keyframes
  783. // Convert original .wav start to animation set channels clip relative time
  784. CUtlVector< CDmeHandle< CDmeClip > > srcStack;
  785. item.m_pClip->BuildClipStack( &srcStack, info.m_pMovie, info.m_pShot );
  786. if ( srcStack.Count() == 0 )
  787. {
  788. item.m_pClip->BuildClipStack( &srcStack, info.m_pMovie, NULL );
  789. if ( srcStack.Count() == 0 )
  790. {
  791. Msg( "Couldn't build stack sound clip to current shot\n" );
  792. return;
  793. }
  794. }
  795. // NOTE: Time bounds measured in sound media time goes from 0 -> flWaveDuration
  796. DmeTime_t tSoundMediaStartTime = CDmeClip::FromChildMediaTime( srcStack, DMETIME_ZERO, false );
  797. DmeTime_t tSoundMediaEndTime = CDmeClip::FromChildMediaTime( srcStack, DmeTime_t( item.m_flDuration ), false );
  798. // NOTE: Start and end time are measured in sound media time
  799. DmeTime_t tStartTime = item.m_pClip->GetStartInChildMediaTime();
  800. DmeTime_t tEndTime = item.m_pClip->GetEndInChildMediaTime();
  801. // And convert back down into channels clip relative time
  802. CUtlVector< CDmeHandle< CDmeClip > > dstStack;
  803. pChannelsClip->BuildClipStack( &dstStack, info.m_pMovie, info.m_pShot );
  804. // Now convert back down to channels clip relative time
  805. DmeTime_t tChannelMediaStartTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaStartTime, false );
  806. DmeTime_t tChannelMediaEndTime = CDmeClip::ToChildMediaTime( dstStack, tSoundMediaEndTime, false );
  807. // Find a scale + offset which transforms data in media space of the sound [namely, the phonemes]
  808. // into the media space of the channels [the logs that drive the facial animation]
  809. DmeTime_t tEndDuration = tChannelMediaEndTime - tChannelMediaStartTime;
  810. double flScale = ( item.m_flDuration != 0.0f ) ? tEndDuration.GetSeconds() / item.m_flDuration : 0.0f;
  811. DmeTime_t tOffset = tChannelMediaStartTime;
  812. CUtlVector< CDmeLog * > logs;
  813. BuildPhonemeLogList( info.m_ControlList, logs );
  814. // Add new write layer to each recording log
  815. for ( i = 0; i < logs.Count(); ++i )
  816. {
  817. logs[ i ]->AddNewLayer();
  818. }
  819. // Iterate over the entire range of the sound
  820. double flStartSoundTime = max( 0, tStartTime.GetSeconds() );
  821. double flEndSoundTime = min( item.m_flDuration, tEndTime.GetSeconds() );
  822. // Stamp keys right before and after the sound so as to
  823. // not generate new values outside the import time range
  824. DmeTime_t tPrePhonemeTime( flStartSoundTime * flScale );
  825. tPrePhonemeTime += tOffset - DMETIME_MINDELTA;
  826. WriteCurrentValuesIntoLogLayers( tPrePhonemeTime, controlLookup );
  827. DmeTime_t tPostPhonemeTime( flEndSoundTime * flScale );
  828. tPostPhonemeTime += tOffset + DMETIME_MINDELTA;
  829. WriteCurrentValuesIntoLogLayers( tPostPhonemeTime, controlLookup );
  830. // add bookmarks
  831. if ( info.m_bCreateBookmarks )
  832. {
  833. AddAnimSetBookmarkAtSoundMediaTime( "start", tPrePhonemeTime, tPrePhonemeTime, srcStack, info );
  834. for ( i = 0; i < item.m_ApplyTags.Count() ; ++i )
  835. {
  836. CBasePhonemeTag *p = item.m_ApplyTags[ i ];
  837. const char *pPhonemeName = ConvertPhoneme( p->GetPhonemeCode() );
  838. DmeTime_t tStart = DmeTime_t( p->GetStartTime() );
  839. DmeTime_t tEnd = DmeTime_t( p->GetEndTime() );
  840. AddAnimSetBookmarkAtSoundMediaTime( pPhonemeName, tStart, tEnd, srcStack, info );
  841. }
  842. AddAnimSetBookmarkAtSoundMediaTime( "end", tPostPhonemeTime, tPostPhonemeTime, srcStack, info );
  843. }
  844. if ( info.m_nFilterType == EXTRACT_FILTER_HOLD || info.m_nFilterType == EXTRACT_FILTER_LINEAR )
  845. {
  846. CDmePreset *pLastPreset = NULL;
  847. for ( i = 0; i < item.m_ApplyTags.Count() ; ++i )
  848. {
  849. CBasePhonemeTag *p = item.m_ApplyTags[ i ];
  850. DmeTime_t tStart = DmeTime_t( p->GetStartTime() );
  851. DmeTime_t tEnd = DmeTime_t( p->GetEndTime() );
  852. int idx = phonemeToPresetDict.Find( ConvertPhoneme( p->GetPhonemeCode() ) );
  853. if ( idx == phonemeToPresetDict.InvalidIndex() )
  854. continue;
  855. CDmePreset *preset = phonemeToPresetDict[ idx ];
  856. if ( !preset )
  857. continue;
  858. DmeTime_t tKeyTime = tStart * flScale + tOffset;
  859. if ( info.m_nFilterType == EXTRACT_FILTER_HOLD )
  860. {
  861. // stamp value at end of phoneme (or default prior to first phoneme)
  862. // NOTE - this ignores phoneme length, but since all phonemes directly abut one another, this doesn't matter
  863. DmeTime_t tLastEnd = tKeyTime - DMETIME_MINDELTA;
  864. if ( tLastEnd > tPrePhonemeTime )
  865. {
  866. WriteDefaultValuesIntoLogLayers( tKeyTime - DMETIME_MINDELTA, controlLookup );
  867. if ( pLastPreset )
  868. {
  869. StampControlValueLogs( pLastPreset, tKeyTime - DMETIME_MINDELTA, 1.0f, controlLookup );
  870. }
  871. }
  872. pLastPreset = preset;
  873. }
  874. WriteDefaultValuesIntoLogLayers( tKeyTime, controlLookup );
  875. StampControlValueLogs( preset, tKeyTime, 1.0f, controlLookup );
  876. if ( info.m_nFilterType == EXTRACT_FILTER_HOLD && i == item.m_ApplyTags.Count() - 1 )
  877. {
  878. // stamp value at end of last phoneme
  879. tKeyTime = tEnd * flScale + tOffset;
  880. tKeyTime = min( tKeyTime, tPostPhonemeTime );
  881. WriteDefaultValuesIntoLogLayers( tKeyTime - DMETIME_MINDELTA, controlLookup );
  882. StampControlValueLogs( preset, tKeyTime - DMETIME_MINDELTA, 1.0f, controlLookup );
  883. // stamp default just after end of last phoneme to hold silence until tPostPhonemeTime
  884. WriteDefaultValuesIntoLogLayers( tKeyTime, controlLookup );
  885. }
  886. }
  887. }
  888. else
  889. {
  890. Assert( info.m_nFilterType == EXTRACT_FILTER_FIXED_WIDTH );
  891. double tStep = 1.0 / (double)clamp( info.m_flSampleRateHz, 1.0f, 1000.0f );
  892. float flFilter = max( info.m_flSampleFilterSize, 0.001f );
  893. float flOOFilter = 1.0f / flFilter;
  894. for ( double t = flStartSoundTime; t < flEndSoundTime; t += tStep )
  895. {
  896. DmeTime_t tPhonemeTime( t );
  897. // Determine the location of the sample in the channels clip
  898. DmeTime_t tKeyTime( t * flScale );
  899. tKeyTime += tOffset;
  900. // Write a default value at that time
  901. WriteDefaultValuesIntoLogLayers( tKeyTime, controlLookup );
  902. // Walk phonemes...
  903. for ( i = 0; i < item.m_ApplyTags.Count() ; ++i )
  904. {
  905. CBasePhonemeTag *p = item.m_ApplyTags[ i ];
  906. DmeTime_t tStart = DmeTime_t( p->GetStartTime() );
  907. DmeTime_t tEnd = DmeTime_t( p->GetEndTime() );
  908. bool bContinue = false;
  909. float flI = 0.0f;
  910. {
  911. DmeTime_t tFilter( flFilter );
  912. if ( tStart >= tPhonemeTime + tFilter || tEnd <= tPhonemeTime )
  913. bContinue = true;
  914. tStart = max( tStart, tPhonemeTime );
  915. tEnd = min( tEnd, tPhonemeTime + tFilter );
  916. flI = ( tEnd - tStart ).GetSeconds() * flOOFilter;
  917. }
  918. DmeTime_t dStart = tStart - tPhonemeTime;
  919. DmeTime_t dEnd = tEnd - tPhonemeTime;
  920. float t1 = dStart.GetSeconds() * flOOFilter;
  921. float t2 = dEnd.GetSeconds() * flOOFilter;
  922. Assert( bContinue == !( t1 < 1.0f && t2 > 0.0f ) );
  923. if ( !( t1 < 1.0f && t2 > 0.0f ) )
  924. continue;
  925. if ( t2 > 1 )
  926. {
  927. t2 = 1;
  928. }
  929. if ( t1 < 0 )
  930. {
  931. t1 = 0;
  932. }
  933. float flIntensity = ( t2 - t1 );
  934. Assert( fabs( flI - flIntensity ) < 0.000001f );
  935. int idx = phonemeToPresetDict.Find( ConvertPhoneme( p->GetPhonemeCode() ) );
  936. if ( idx == phonemeToPresetDict.InvalidIndex() )
  937. continue;
  938. CDmePreset *preset = phonemeToPresetDict[ idx ];
  939. if ( !preset )
  940. continue;
  941. StampControlValueLogs( preset, tKeyTime, flIntensity, controlLookup );
  942. }
  943. }
  944. }
  945. // Flatten write layers
  946. for ( i = 0; i < logs.Count(); ++i )
  947. {
  948. logs[ i ]->FlattenLayers( DMELOG_DEFAULT_THRESHHOLD, CDmeLog::FLATTEN_NODISCONTINUITY_FIXUP );
  949. }
  950. }
  951. void CSFMPhonemeExtractor::ReApply( ExtractDesc_t& info )
  952. {
  953. if ( info.m_bCreateBookmarks )
  954. {
  955. info.m_pSet->GetBookmarks().RemoveAll();
  956. }
  957. for ( int nWorkItem = 0; nWorkItem < info.m_WorkList.Count(); ++nWorkItem )
  958. {
  959. LogPhonemes( nWorkItem, info );
  960. }
  961. }