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.

973 lines
29 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // The copyright to the contents herein is the property of Valve, L.L.C.
  4. // The contents may be used and/or copied only with the written permission of
  5. // Valve, L.L.C., or in accordance with the terms and conditions stipulated in
  6. // the agreement/contract under which the contents have been supplied.
  7. //
  8. // $Header: $
  9. // $NoKeywords: $
  10. //
  11. //=============================================================================
  12. // Valve includes
  13. #include "appframework/tier3app.h"
  14. #include "datamodel/idatamodel.h"
  15. #include "filesystem.h"
  16. #include "filesystem_init.h"
  17. #include "icommandline.h"
  18. #include "materialsystem/imaterialsystem.h"
  19. #include "istudiorender.h"
  20. #include "mathlib/mathlib.h"
  21. #include "vstdlib/vstdlib.h"
  22. #include "vstdlib/iprocessutils.h"
  23. #include "tier2/p4helpers.h"
  24. #include "p4lib/ip4.h"
  25. #include "tier1/utlbuffer.h"
  26. #include "tier1/utlstringmap.h"
  27. #include "sfmobjects/sfmsession.h"
  28. #include "datacache/idatacache.h"
  29. #include "datacache/imdlcache.h"
  30. #include "vphysics_interface.h"
  31. #include "movieobjects/dmeclip.h"
  32. #include "movieobjects/dmetrack.h"
  33. #include "movieobjects/dmetrackgroup.h"
  34. #include "movieobjects/dmegamemodel.h"
  35. #include "movieobjects/dmesound.h"
  36. #include "movieobjects/dmedag.h"
  37. #include "movieobjects/dmechannel.h"
  38. #include "movieobjects/dmeanimationset.h"
  39. #include "studio.h"
  40. #include "sfmobjects/sfmanimationsetutils.h"
  41. #include "sfmobjects/flexcontrolbuilder.h"
  42. #include "sfmobjects/sfmphonemeextractor.h"
  43. #include "sfmobjects/exportfacialanimation.h"
  44. #include "soundemittersystem/isoundemittersystembase.h"
  45. #include "phonemeconverter.h"
  46. #include "tier2/riff.h"
  47. #include "tier2/soundutils.h"
  48. #include "soundchars.h"
  49. #include <ctype.h>
  50. #ifdef _DEBUG
  51. #include <windows.h>
  52. #undef GetCurrentDirectory
  53. #endif
  54. class StdIOReadBinary : public IFileReadBinary
  55. {
  56. public:
  57. int open( const char *pFileName )
  58. {
  59. return (int)g_pFullFileSystem->Open( pFileName, "rb" );
  60. }
  61. int read( void *pOutput, int size, int file )
  62. {
  63. if ( !file )
  64. return 0;
  65. return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file );
  66. }
  67. void seek( int file, int pos )
  68. {
  69. if ( !file )
  70. return;
  71. g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
  72. }
  73. unsigned int tell( int file )
  74. {
  75. if ( !file )
  76. return 0;
  77. return g_pFullFileSystem->Tell( (FileHandle_t)file );
  78. }
  79. unsigned int size( int file )
  80. {
  81. if ( !file )
  82. return 0;
  83. return g_pFullFileSystem->Size( (FileHandle_t)file );
  84. }
  85. void close( int file )
  86. {
  87. if ( !file )
  88. return;
  89. g_pFullFileSystem->Close( (FileHandle_t)file );
  90. }
  91. };
  92. class StdIOWriteBinary : public IFileWriteBinary
  93. {
  94. public:
  95. int create( const char *pFileName )
  96. {
  97. return (int)g_pFullFileSystem->Open( pFileName, "wb" );
  98. }
  99. int write( void *pData, int size, int file )
  100. {
  101. return g_pFullFileSystem->Write( pData, size, (FileHandle_t)file );
  102. }
  103. void close( int file )
  104. {
  105. g_pFullFileSystem->Close( (FileHandle_t)file );
  106. }
  107. void seek( int file, int pos )
  108. {
  109. g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
  110. }
  111. unsigned int tell( int file )
  112. {
  113. return g_pFullFileSystem->Tell( (FileHandle_t)file );
  114. }
  115. };
  116. static StdIOReadBinary io_in;
  117. static StdIOWriteBinary io_out;
  118. #define RIFF_WAVE MAKEID('W','A','V','E')
  119. #define WAVE_FMT MAKEID('f','m','t',' ')
  120. #define WAVE_DATA MAKEID('d','a','t','a')
  121. #define WAVE_FACT MAKEID('f','a','c','t')
  122. #define WAVE_CUE MAKEID('c','u','e',' ')
  123. //-----------------------------------------------------------------------------
  124. // Purpose:
  125. // Input : &walk -
  126. //-----------------------------------------------------------------------------
  127. static void SceneManager_ParseSentence( CSentence& sentence, IterateRIFF &walk )
  128. {
  129. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  130. buf.EnsureCapacity( walk.ChunkSize() );
  131. walk.ChunkRead( buf.Base() );
  132. buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
  133. sentence.InitFromDataChunk( buf.Base(), buf.TellPut() );
  134. }
  135. bool SceneManager_LoadSentenceFromWavFileUsingIO( char const *wavfile, CSentence& sentence, IFileReadBinary& io )
  136. {
  137. sentence.Reset();
  138. InFileRIFF riff( wavfile, io );
  139. // UNDONE: Don't use printf to handle errors
  140. if ( riff.RIFFName() != RIFF_WAVE )
  141. {
  142. return false;
  143. }
  144. // set up the iterator for the whole file (root RIFF is a chunk)
  145. IterateRIFF walk( riff, riff.RIFFSize() );
  146. // This chunk must be first as it contains the wave's format
  147. // break out when we've parsed it
  148. bool found = false;
  149. while ( walk.ChunkAvailable() && !found )
  150. {
  151. switch ( walk.ChunkName() )
  152. {
  153. case WAVE_VALVEDATA:
  154. {
  155. found = true;
  156. SceneManager_ParseSentence( sentence, walk );
  157. }
  158. break;
  159. }
  160. walk.ChunkNext();
  161. }
  162. return found;
  163. }
  164. bool SceneManager_LoadSentenceFromWavFile( char const *wavfile, CSentence& sentence )
  165. {
  166. return SceneManager_LoadSentenceFromWavFileUsingIO( wavfile, sentence, io_in );
  167. }
  168. //-----------------------------------------------------------------------------
  169. // Standard spew functions
  170. //-----------------------------------------------------------------------------
  171. static SpewRetval_t SpewStdout( SpewType_t spewType, char const *pMsg )
  172. {
  173. if ( !pMsg )
  174. return SPEW_CONTINUE;
  175. #ifdef _DEBUG
  176. OutputDebugString( pMsg );
  177. #endif
  178. printf( pMsg );
  179. fflush( stdout );
  180. return ( spewType == SPEW_ASSERT ) ? SPEW_DEBUGGER : SPEW_CONTINUE;
  181. }
  182. //-----------------------------------------------------------------------------
  183. // The application object
  184. //-----------------------------------------------------------------------------
  185. class CSFMGenApp : public CTier3DmSteamApp
  186. {
  187. typedef CTier3DmSteamApp BaseClass;
  188. public:
  189. // Methods of IApplication
  190. virtual bool Create();
  191. virtual bool PreInit( );
  192. virtual int Main();
  193. virtual void Destroy() {}
  194. void PrintHelp( );
  195. private:
  196. struct SFMInfo_t
  197. {
  198. CUtlString m_GameSound;
  199. CUtlString m_Text;
  200. CUtlString m_DMXFileName;
  201. };
  202. struct SFMGenInfo_t
  203. {
  204. const char *m_pCSVFile;
  205. const char *m_pModelName;
  206. const char *m_pOutputDirectory;
  207. const char *m_pExportFacDirectory;
  208. bool m_bWritePhonemesInWavs;
  209. bool m_bUsePhonemesInWavs;
  210. float m_flSampleRateHz;
  211. float m_flSampleFilterSize;
  212. bool m_bGenerateSFMFiles;
  213. bool m_bExtractPhonemeFromWavsForMp3;
  214. };
  215. enum TokenRetVal_t
  216. {
  217. TOKEN_COMMA = 0,
  218. TOKEN_RETURN,
  219. TOKEN_COMMENT,
  220. TOKEN_EOF,
  221. };
  222. // Parses the excel .csv file
  223. TokenRetVal_t ParseToken( CUtlBuffer &buf, char *pToken, int nMaxTokenLen );
  224. // Parses the excel file
  225. void ParseCSVFile( CUtlBuffer &buf, CUtlVector< SFMInfo_t > &infoList, int nSkipLines );
  226. // The application object
  227. void GenerateSFMFiles( SFMGenInfo_t &info );
  228. // Makes the names unique
  229. void UniqueifyNames( CUtlVector< SFMInfo_t > &infoList );
  230. // Creates a single sfm file
  231. void GenerateSFMFile( const SFMGenInfo_t &sfmGenInfo, const SFMInfo_t &info, studiohdr_t *pStudioHdr, const char *pOutputDirectory, const char *pExportFacPath );
  232. // Saves an SFM file
  233. void SaveSFMFile( CDmElement *pRoot, const char *pRelativeScenePath, const char *pFileName );
  234. // Generates a sound clip for the game sound
  235. CDmeSoundClip *CreateSoundClip( CDmeFilmClip *pShot, const char *pAnimationSetName, const char *pGameSound, studiohdr_t *pStudioHdr, CDmeGameSound **ppGameSound );
  236. // Builds the list of all controls in the animation set contributing to facial animation
  237. void BuildFacialControlList( CDmeFilmClip *pShot, CDmeAnimationSet *pAnimationSet, CUtlVector< LogPreview_t > &list );
  238. };
  239. DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( CSFMGenApp );
  240. //-----------------------------------------------------------------------------
  241. // The application object
  242. //-----------------------------------------------------------------------------
  243. bool CSFMGenApp::Create()
  244. {
  245. SpewOutputFunc( SpewStdout );
  246. AppSystemInfo_t appSystems[] =
  247. {
  248. { "vstdlib.dll", PROCESS_UTILS_INTERFACE_VERSION },
  249. { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION },
  250. { "p4lib.dll", P4_INTERFACE_VERSION },
  251. { "datacache.dll", DATACACHE_INTERFACE_VERSION },
  252. { "datacache.dll", MDLCACHE_INTERFACE_VERSION },
  253. { "studiorender.dll", STUDIO_RENDER_INTERFACE_VERSION },
  254. { "vphysics.dll", VPHYSICS_INTERFACE_VERSION },
  255. { "soundemittersystem.dll", SOUNDEMITTERSYSTEM_INTERFACE_VERSION },
  256. { "", "" } // Required to terminate the list
  257. };
  258. AddSystems( appSystems );
  259. IMaterialSystem *pMaterialSystem = reinterpret_cast< IMaterialSystem * >( FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION ) );
  260. if ( !pMaterialSystem )
  261. {
  262. Error( "// ERROR: Unable to connect to material system interface!\n" );
  263. return false;
  264. }
  265. pMaterialSystem->SetShaderAPI( "shaderapiempty.dll" );
  266. SetupDefaultFlexController();
  267. return true;
  268. }
  269. //-----------------------------------------------------------------------------
  270. //
  271. //-----------------------------------------------------------------------------
  272. bool CSFMGenApp::PreInit( )
  273. {
  274. MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
  275. if ( !BaseClass::PreInit() )
  276. return false;
  277. if ( !g_pFullFileSystem || !g_pDataModel )
  278. {
  279. Error( "// ERROR: sfmgen is missing a required interface!\n" );
  280. return false;
  281. }
  282. // Add paths...
  283. if ( !SetupSearchPaths( NULL, false, true ) )
  284. return false;
  285. return true;
  286. }
  287. //-----------------------------------------------------------------------------
  288. // Print help
  289. //-----------------------------------------------------------------------------
  290. void CSFMGenApp::PrintHelp( )
  291. {
  292. Msg( "Usage: sfmgen -i <in .csv file> -m <.mdl relative path> -o <output dir>\n" );
  293. Msg( " [-f <fac output dir>] [-p] [-w] [-r <sample rate in hz>] [-s <sample filter size>]\n" );
  294. Msg( " [-nop4] [-vproject <path to gameinfo.txt>]\n" );
  295. Msg( "\t-i\t: Source .CSV file indicating game sound names, text for phoneme extraction, output sfm file names.\n" );
  296. Msg( "\t-m\t: Indicates the path of the .mdl file under game/mod/models/ to use in the sfm files.\n" );
  297. Msg( "\t-o\t: Indicates output directory to place generated files in. Required if Generating SFM files.\n" );
  298. Msg( "\t-f\t: [Optional] Indicates that facial files should be created in the specified fac output dir.\n" );
  299. Msg( "\t-p\t: [Optional] Indicates the extracted phonemes should be written into the wav file.\n" );
  300. Msg( "\t-w\t: [Optional] Indicates the phonemes should be read from the wav file. Cannot also have -p specified.\n" );
  301. Msg( "\t-r\t: [Optional] Specifies the phoneme extraction sample rate (default = 20)\n" );
  302. Msg( "\t-s\t: [Optional] Specifies the phoneme extraction sample filter size (default = 0.08)\n" );
  303. Msg( "\t-nop4\t: Disables auto perforce checkout/add.\n" );
  304. Msg( "\t-nosfm\t: Disables generating of SFM files (dmx).\n" );
  305. Msg( "\t-vproject\t: Specifies path to a gameinfo.txt file (which mod to build for).\n" );
  306. }
  307. //-----------------------------------------------------------------------------
  308. // Parses a token from the excel .csv file
  309. //-----------------------------------------------------------------------------
  310. CSFMGenApp::TokenRetVal_t CSFMGenApp::ParseToken( CUtlBuffer &buf, char *pToken, int nMaxTokenLen )
  311. {
  312. *pToken = 0;
  313. if ( !buf.IsValid() )
  314. return TOKEN_EOF;
  315. int nLen = 0;
  316. char c = buf.GetChar();
  317. bool bIsQuoted = false;
  318. bool bIsComment = false;
  319. while ( true )
  320. {
  321. if ( c == '#' )
  322. {
  323. bIsComment = true;
  324. }
  325. if ( c == '"' )
  326. {
  327. bIsQuoted = !bIsQuoted;
  328. }
  329. else if ( ( c == ',' || c == '\n' ) && !bIsQuoted )
  330. {
  331. pToken[nLen] = 0;
  332. if ( bIsComment )
  333. return TOKEN_COMMENT;
  334. return ( c == '\n' ) ? TOKEN_RETURN : TOKEN_COMMA;
  335. }
  336. if ( nLen < nMaxTokenLen - 1 )
  337. {
  338. if ( c != '"' )
  339. {
  340. pToken[nLen++] = c;
  341. }
  342. }
  343. if ( !buf.IsValid() )
  344. {
  345. pToken[nLen] = 0;
  346. return TOKEN_EOF;
  347. }
  348. c = buf.GetChar();
  349. }
  350. // Should never get here
  351. return TOKEN_EOF;
  352. }
  353. //-----------------------------------------------------------------------------
  354. // Parses the excel file
  355. //-----------------------------------------------------------------------------
  356. void CSFMGenApp::ParseCSVFile( CUtlBuffer &buf, CUtlVector< SFMInfo_t > &infoList, int nSkipLines )
  357. {
  358. char pToken[512];
  359. for( int nLine = 0; buf.IsValid(); ++nLine )
  360. {
  361. SFMInfo_t info;
  362. TokenRetVal_t nTokenRetVal = ParseToken( buf, pToken, sizeof(pToken) );
  363. if ( nTokenRetVal == TOKEN_EOF )
  364. return;
  365. if ( nTokenRetVal == TOKEN_COMMENT )
  366. {
  367. continue;
  368. }
  369. if ( nTokenRetVal != TOKEN_COMMA )
  370. {
  371. Warning( "sfmgen: Missing Column at line %d\n", nLine );
  372. continue;
  373. }
  374. info.m_DMXFileName = pToken;
  375. if ( ParseToken( buf, pToken, sizeof(pToken) ) != TOKEN_COMMA )
  376. {
  377. Warning( "sfmgen: Missing Column at line %d\n", nLine );
  378. continue;
  379. }
  380. if ( ParseToken( buf, pToken, sizeof(pToken) ) != TOKEN_COMMA )
  381. {
  382. Warning( "sfmgen: Missing Column at line %d\n", nLine );
  383. continue;
  384. }
  385. info.m_GameSound = pToken;
  386. nTokenRetVal = ParseToken( buf, pToken, sizeof(pToken) );
  387. info.m_Text = pToken;
  388. if ( nTokenRetVal == TOKEN_COMMENT )
  389. {
  390. Warning( "sfmgen: No VO Transcript on line %d - Skipping \n", nLine );
  391. continue;
  392. }
  393. // extract the VO Transcript
  394. while ( nTokenRetVal == TOKEN_COMMA )
  395. {
  396. nTokenRetVal = ParseToken( buf, pToken, sizeof(pToken) );
  397. }
  398. if ( nSkipLines > nLine )
  399. continue;
  400. infoList.AddToTail( info );
  401. }
  402. }
  403. //-----------------------------------------------------------------------------
  404. // Makes the names unique
  405. //-----------------------------------------------------------------------------
  406. void CSFMGenApp::UniqueifyNames( CUtlVector< SFMInfo_t > &infoList )
  407. {
  408. CUtlStringMap<int> foundNames;
  409. int nCount = infoList.Count();
  410. for ( int i = 0; i < nCount; ++i )
  411. {
  412. const char *pName = infoList[i].m_DMXFileName;
  413. int nFoundCount;
  414. if ( !foundNames.Defined( pName ) )
  415. {
  416. nFoundCount = foundNames[ pName ] = 1;
  417. }
  418. else
  419. {
  420. nFoundCount = ++foundNames[ pName ];
  421. }
  422. // Remove whitespace, change to lowercase, uniqueify.
  423. int nLen = Q_strlen(pName);
  424. int nDigits = (int)log10( (float)nFoundCount ) + 1;
  425. char *pTemp = (char*)_alloca( nLen + nDigits + 1 );
  426. for ( int j = 0; j < nLen; ++j )
  427. {
  428. if ( isspace( pName[j] ) )
  429. {
  430. pTemp[j] = '_';
  431. }
  432. else
  433. {
  434. pTemp[j] = tolower( pName[j] );
  435. }
  436. }
  437. if ( nFoundCount > 1 )
  438. {
  439. Q_snprintf( &pTemp[nLen], nDigits + 1, "%d", nFoundCount );
  440. nLen += nDigits;
  441. }
  442. pTemp[nLen] = 0;
  443. infoList[i].m_DMXFileName = pTemp;
  444. }
  445. }
  446. //-----------------------------------------------------------------------------
  447. // Saves an SFM file
  448. //-----------------------------------------------------------------------------
  449. void CSFMGenApp::SaveSFMFile( CDmElement *pRoot, const char *pOutputDir, const char *pFileName )
  450. {
  451. // Construct actual file name from the model name + dmx file name
  452. char pFullPathBuf[MAX_PATH];
  453. Q_snprintf( pFullPathBuf, sizeof(pFullPathBuf), "%s/%s.dmx", pOutputDir, pFileName );
  454. const char *pFullPath = pFullPathBuf;
  455. CP4AutoEditAddFile checkout( pFullPath );
  456. if ( !g_pDataModel->SaveToFile( pFullPath, NULL, g_pDataModel->GetDefaultEncoding( "sfm_session" ), "sfm_session", pRoot ) )
  457. {
  458. Warning( "sfmgen: Unable to write file %s\n", pFullPath );
  459. return;
  460. }
  461. Msg( "sfmgen: Wrote file %s\n", pFullPath );
  462. }
  463. //-----------------------------------------------------------------------------
  464. // Generates a sound clip for the game sound
  465. //-----------------------------------------------------------------------------
  466. CDmeSoundClip *CSFMGenApp::CreateSoundClip( CDmeFilmClip *pShot, const char *pAnimationSetName, const char *pGameSound, studiohdr_t *pStudioHdr, CDmeGameSound **ppGameSound )
  467. {
  468. *ppGameSound = NULL;
  469. CDmeTrackGroup *pTrackGroup = pShot->FindOrAddTrackGroup( "audio" );
  470. CDmeTrack *pTrack = pTrackGroup->FindOrAddTrack( pAnimationSetName, DMECLIP_SOUND );
  471. // Get the gender for the model
  472. gender_t actorGender = g_pSoundEmitterSystem->GetActorGender( pStudioHdr->pszName() );
  473. // Get the wav file for the gamesound.
  474. CSoundParameters params;
  475. if ( !g_pSoundEmitterSystem->GetParametersForSound( pGameSound, params, actorGender ) )
  476. {
  477. Warning( "Unable to determine .wav file for gamesound %s!\n", pGameSound );
  478. return NULL;
  479. }
  480. // Get the sound duration
  481. char pFullPath[MAX_PATH];
  482. char pRelativePath[MAX_PATH];
  483. const char *pWavFile = PSkipSoundChars( params.soundname );
  484. Q_ComposeFileName( "sound", pWavFile, pRelativePath, sizeof(pRelativePath) );
  485. g_pFullFileSystem->RelativePathToFullPath( pRelativePath, "GAME", pFullPath, sizeof(pFullPath) );
  486. DmeTime_t duration( GetWavSoundDuration( pFullPath ) );
  487. CDmeGameSound *pDmeSound = CreateElement< CDmeGameSound >( pGameSound, pTrack->GetFileId() );
  488. Assert( pDmeSound );
  489. pDmeSound->m_GameSoundName = pGameSound;
  490. pDmeSound->m_SoundName = params.soundname;
  491. pDmeSound->m_Volume = params.volume;
  492. pDmeSound->m_Level = params.soundlevel;
  493. pDmeSound->m_Pitch = params.pitch;
  494. pDmeSound->m_IsStatic = true;
  495. pDmeSound->m_Channel = CHAN_STATIC;
  496. pDmeSound->m_Flags = 0;
  497. pDmeSound->m_Origin = vec3_origin;
  498. pDmeSound->m_Direction = vec3_origin;
  499. CDmeSoundClip *pSubClip = CreateElement< CDmeSoundClip >( pGameSound, pTrack->GetFileId() );
  500. pSubClip->m_Sound = pDmeSound;
  501. pSubClip->SetStartTime( DMETIME_ZERO );
  502. pSubClip->SetTimeOffset( DmeTime_t( -params.delay_msec / 1000.0f ) );
  503. pSubClip->SetDuration( duration );
  504. pTrack->AddClip( pSubClip );
  505. *ppGameSound = pDmeSound;
  506. return pSubClip;
  507. }
  508. //-----------------------------------------------------------------------------
  509. // Builds the list of all controls in the animation set contributing to facial animation
  510. //-----------------------------------------------------------------------------
  511. void CSFMGenApp::BuildFacialControlList( CDmeFilmClip *pShot, CDmeAnimationSet *pAnimationSet, CUtlVector< LogPreview_t > &list )
  512. {
  513. const CDmaElementArray< CDmElement > &controls = pAnimationSet->GetControls();
  514. int nCount = controls.Count();
  515. for ( int i = 0; i < nCount; ++i )
  516. {
  517. CDmElement *pControl = controls[i];
  518. if ( !pControl || pControl->GetValue<bool>( "transform" ) )
  519. continue;
  520. LogPreview_t preview;
  521. preview.m_hControl = pControl;
  522. preview.m_hShot = pShot;
  523. preview.m_bActiveLog = preview.m_bSelected = false;
  524. if ( pControl->GetValue< bool >( "combo" ) )
  525. {
  526. preview.m_hChannels[ LOG_PREVIEW_VALUE ] = pControl->GetValueElement< CDmeChannel >( "valuechannel" );
  527. preview.m_hChannels[ LOG_PREVIEW_BALANCE ] = pControl->GetValueElement< CDmeChannel >( "balancechannel" );
  528. }
  529. else
  530. {
  531. preview.m_hChannels[ LOG_PREVIEW_VALUE ] = pControl->GetValueElement< CDmeChannel >( "channel" );
  532. preview.m_hChannels[ LOG_PREVIEW_BALANCE ] = NULL;
  533. }
  534. if ( pControl->GetValue< bool >( "multi" ) )
  535. {
  536. preview.m_hChannels[ LOG_PREVIEW_MULTILEVEL ] = pControl->GetValueElement< CDmeChannel >( "multilevelchannel" );
  537. }
  538. else
  539. {
  540. preview.m_hChannels[ LOG_PREVIEW_MULTILEVEL ] = NULL;
  541. }
  542. preview.m_hOwner = preview.m_hChannels[ LOG_PREVIEW_VALUE ]->FindOwnerClipForChannel( pShot );
  543. list.AddToTail( preview );
  544. }
  545. }
  546. //-----------------------------------------------------------------------------
  547. // Creates a single sfm file
  548. //-----------------------------------------------------------------------------
  549. void CSFMGenApp::GenerateSFMFile( const SFMGenInfo_t& sfmGenInfo, const SFMInfo_t &info,
  550. studiohdr_t *pStudioHdr, const char *pOutputDirectory, const char *pExportFacPath )
  551. {
  552. CSFMSession session;
  553. session.Init();
  554. char pAnimationSetName[256];
  555. Q_FileBase( pStudioHdr->pszName(), pAnimationSetName, sizeof(pAnimationSetName) );
  556. // Set the file id( necessary for phoneme extraction)
  557. DmFileId_t fileid = g_pDataModel->FindOrCreateFileId( pAnimationSetName );
  558. session.Root()->SetFileId( fileid, TD_DEEP );
  559. g_pModelPresetGroupMgr->AssociatePresetsWithFile( fileid );
  560. // Get the shot.
  561. CDmeFilmClip *pMovie = session.Root()->GetValueElement<CDmeFilmClip>( "activeClip" );
  562. CDmeFilmClip* pShot = CastElement< CDmeFilmClip >( pMovie->GetFilmTrack()->GetClip( 0 ) );
  563. // Create a camera for the shot
  564. DmeCameraParams_t cameraParams( pAnimationSetName, vec3_origin, QAngle( 0, 180, 0 ) );
  565. cameraParams.origin.x = pStudioHdr->hull_max.x + 20;
  566. cameraParams.origin.z = Lerp( 0.95f, pStudioHdr->hull_min.z, pStudioHdr->hull_max.z );
  567. cameraParams.fov = 75.0f;
  568. CDmeCamera* pCamera = session.CreateCamera( cameraParams );
  569. pShot->SetCamera( pCamera );
  570. // Create a game model for the studio hdr
  571. CDmeGameModel *pGameModel = session.CreateEditorGameModel( pStudioHdr, vec3_origin, Quaternion( 0, 0, 0, 1 ) );
  572. // Create a scene for the shot
  573. CDmeDag *pScene = session.FindOrCreateScene( pShot, pAnimationSetName );
  574. pScene->AddChild( pGameModel );
  575. // Create a sound clip
  576. CDmeGameSound *pGameSound;
  577. CDmeSoundClip *pSoundClip = CreateSoundClip( pMovie, pAnimationSetName, info.m_GameSound, pStudioHdr, &pGameSound );
  578. if ( pSoundClip )
  579. {
  580. pShot->SetDuration( pSoundClip->GetDuration() );
  581. pMovie->SetDuration( pSoundClip->GetDuration() );
  582. // Create an animation set
  583. CDmeAnimationSet *pAnimationSet = CreateAnimationSet( pMovie, pShot, pGameModel, pAnimationSetName, 0, false );
  584. // Extract phonemes
  585. CExtractInfo extractInfo;
  586. extractInfo.m_pClip = pSoundClip;
  587. extractInfo.m_pSound = pGameSound;
  588. extractInfo.m_sHintText = info.m_Text;
  589. extractInfo.m_bUseSentence = sfmGenInfo.m_bUsePhonemesInWavs;
  590. ExtractDesc_t extractDesc;
  591. extractDesc.m_nExtractType = EXTRACT_WIPE_CLIP;
  592. extractDesc.m_pMovie = pMovie;
  593. extractDesc.m_pShot = pShot;
  594. extractDesc.m_pSet = pAnimationSet;
  595. extractDesc.m_flSampleRateHz = sfmGenInfo.m_flSampleRateHz;
  596. extractDesc.m_flSampleFilterSize = sfmGenInfo.m_flSampleFilterSize;
  597. extractDesc.m_WorkList.AddToTail( extractInfo );
  598. BuildFacialControlList( pShot, pAnimationSet, extractDesc.m_ControlList );
  599. sfm_phonemeextractor->Extract( SPEECH_API_LIPSINC, extractDesc, sfmGenInfo.m_bWritePhonemesInWavs );
  600. CExtractInfo &results = extractDesc.m_WorkList[ 0 ];
  601. CDmElement *pExtractionSettings = pGameSound->FindOrAddPhonemeExtractionSettings();
  602. pExtractionSettings->SetValue< float >( "duration", results.m_flDuration );
  603. // Store off phonemes
  604. if ( !pExtractionSettings->HasAttribute( "results" ) )
  605. {
  606. pExtractionSettings->AddAttribute( "results", AT_ELEMENT_ARRAY );
  607. }
  608. CDmrElementArray< CDmElement > resultsArray( pExtractionSettings, "results" );
  609. resultsArray.RemoveAll();
  610. for ( int i = 0; i < results.m_ApplyTags.Count(); ++i )
  611. {
  612. CBasePhonemeTag *tag = results.m_ApplyTags[ i ];
  613. CDmElement *tagElement = CreateElement< CDmElement >( ConvertPhoneme( tag->GetPhonemeCode() ), pGameSound->GetFileId() );
  614. tagElement->SetValue< float >( "start", tag->GetStartTime() );
  615. tagElement->SetValue< float >( "end", tag->GetEndTime() );
  616. resultsArray.AddToTail( tagElement );
  617. }
  618. if ( sfmGenInfo.m_bGenerateSFMFiles && pExportFacPath )
  619. {
  620. char pFACFileName[MAX_PATH];
  621. Q_ComposeFileName( pExportFacPath, info.m_DMXFileName, pFACFileName, sizeof(pFACFileName) );
  622. Q_SetExtension( pFACFileName, ".dmx", sizeof(pFACFileName) );
  623. ExportFacialAnimation( pFACFileName, pMovie, pShot, pAnimationSet );
  624. }
  625. }
  626. if ( sfmGenInfo.m_bGenerateSFMFiles )
  627. {
  628. SaveSFMFile( session.Root(), pOutputDirectory, info.m_DMXFileName );
  629. }
  630. session.Shutdown();
  631. }
  632. //-----------------------------------------------------------------------------
  633. // Computes a full directory
  634. //-----------------------------------------------------------------------------
  635. static void ComputeFullPath( const char *pRelativeDir, char *pFullPath, int nBufLen )
  636. {
  637. if ( !Q_IsAbsolutePath( pRelativeDir ) )
  638. {
  639. char pDir[MAX_PATH];
  640. if ( g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof(pDir) ) )
  641. {
  642. Q_ComposeFileName( pDir, pRelativeDir, pFullPath, nBufLen );
  643. }
  644. }
  645. else
  646. {
  647. Q_strncpy( pFullPath, pRelativeDir, nBufLen );
  648. }
  649. Q_StripTrailingSlash( pFullPath );
  650. // Ensure the output directory exists
  651. g_pFullFileSystem->CreateDirHierarchy( pFullPath );
  652. }
  653. //-----------------------------------------------------------------------------
  654. // The application object
  655. //-----------------------------------------------------------------------------
  656. void CSFMGenApp::GenerateSFMFiles( SFMGenInfo_t& info )
  657. {
  658. char pRelativeModelPath[MAX_PATH];
  659. Q_ComposeFileName( "models", info.m_pModelName, pRelativeModelPath, sizeof(pRelativeModelPath) );
  660. Q_SetExtension( pRelativeModelPath, ".mdl", sizeof(pRelativeModelPath) );
  661. MDLHandle_t hMDL = g_pMDLCache->FindMDL( pRelativeModelPath );
  662. if ( hMDL == MDLHANDLE_INVALID )
  663. {
  664. Warning( "sfmgen: Model %s doesn't exist!\n", pRelativeModelPath );
  665. return;
  666. }
  667. studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( hMDL );
  668. if ( !pStudioHdr || g_pMDLCache->IsErrorModel( hMDL ) )
  669. {
  670. Warning( "sfmgen: Model %s doesn't exist!\n", pRelativeModelPath );
  671. return;
  672. }
  673. CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
  674. if ( !g_pFullFileSystem->ReadFile( info.m_pCSVFile, NULL, buf ) )
  675. {
  676. Warning( "sfmgen: Unable to load file %s\n", info.m_pCSVFile );
  677. return;
  678. }
  679. CUtlVector< SFMInfo_t > infoList;
  680. ParseCSVFile( buf, infoList, 1 );
  681. int nCount = infoList.Count();
  682. if ( nCount == 0 )
  683. {
  684. Warning( "sfmgen: no files to create!\n" );
  685. return;
  686. }
  687. UniqueifyNames( infoList );
  688. // Construct full path to the output directories
  689. char pFullPath[MAX_PATH];
  690. char pFullFacPathBuf[MAX_PATH];
  691. const char *pExportFacPath = NULL;
  692. if ( info.m_pExportFacDirectory )
  693. {
  694. ComputeFullPath( info.m_pExportFacDirectory, pFullFacPathBuf, sizeof( pFullFacPathBuf ) );
  695. pExportFacPath = pFullFacPathBuf;
  696. }
  697. if ( info.m_pOutputDirectory )
  698. {
  699. ComputeFullPath( info.m_pOutputDirectory, pFullPath, sizeof( pFullPath ) );
  700. }
  701. else
  702. {
  703. pFullPath[0] = '\0';
  704. }
  705. if (!info.m_bExtractPhonemeFromWavsForMp3)
  706. {
  707. for (int i = 0; i < nCount; ++i)
  708. {
  709. GenerateSFMFile(info, infoList[i], pStudioHdr, pFullPath, pExportFacPath);
  710. }
  711. }
  712. else
  713. {
  714. // Extra Phoneme Data from .wav files for .mp3
  715. CUtlBuffer bufOutput(0, 0, CUtlBuffer::TEXT_BUFFER); // Phoneme outout
  716. CUtlBuffer bufFilenames(0, 0, CUtlBuffer::TEXT_BUFFER); // Affected Files for reference (files with Phoneme data)
  717. for (int i = 0; i < nCount; ++i)
  718. {
  719. CSentence sSentence;
  720. if (SceneManager_LoadSentenceFromWavFile(infoList[i].m_GameSound, sSentence))
  721. {
  722. // Save Phoneme Data
  723. CUtlString strTemp(infoList[i].m_DMXFileName);
  724. // //bufOutput.Printf( "\n" );
  725. bufOutput.Printf(strTemp.Replace(".wav", ".mp3").Get());
  726. bufOutput.Printf("\n{\n");
  727. sSentence.SaveToBuffer(bufOutput);
  728. bufOutput.Printf("}\n\n");
  729. // Save file name
  730. bufFilenames.Printf(infoList[i].m_GameSound);
  731. bufFilenames.Printf("\n");
  732. Msg("%s\n", infoList[i].m_GameSound.Get());
  733. }
  734. }
  735. // output
  736. //if ( sfm_phonemeextractor->GetSentence( pGameSound, sSentence ) )
  737. {
  738. // write this to a text file
  739. FileHandle_t fh = g_pFullFileSystem->Open("tf_PhonemeData.txt", "wb");
  740. if (fh)
  741. {
  742. g_pFullFileSystem->Write(bufOutput.Base(), bufOutput.TellPut(), fh);
  743. g_pFullFileSystem->Close(fh);
  744. }
  745. }
  746. {
  747. // write this to a text file
  748. FileHandle_t fh = g_pFullFileSystem->Open("tf_PhonemeFiles.txt", "wb");
  749. if (fh)
  750. {
  751. g_pFullFileSystem->Write(bufFilenames.Base(), bufFilenames.TellPut(), fh);
  752. g_pFullFileSystem->Close(fh);
  753. }
  754. }
  755. }
  756. }
  757. //-----------------------------------------------------------------------------
  758. // The application object
  759. //-----------------------------------------------------------------------------
  760. int CSFMGenApp::Main()
  761. {
  762. g_pDataModel->SetUndoEnabled( false );
  763. // This bit of hackery allows us to access files on the harddrive
  764. g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD );
  765. if ( CommandLine()->CheckParm( "-h" ) || CommandLine()->CheckParm( "-help" ) )
  766. {
  767. PrintHelp();
  768. return 0;
  769. }
  770. // Do Perforce Stuff
  771. if ( CommandLine()->FindParm( "-nop4" ) )
  772. {
  773. g_p4factory->SetDummyMode( true );
  774. }
  775. g_p4factory->SetOpenFileChangeList( "Automatically Generated SFM files" );
  776. SFMGenInfo_t info;
  777. info.m_pCSVFile = CommandLine()->ParmValue( "-i" );
  778. info.m_pModelName = CommandLine()->ParmValue( "-m" );
  779. info.m_pOutputDirectory = CommandLine()->ParmValue( "-o" );
  780. info.m_pExportFacDirectory = CommandLine()->ParmValue( "-f" );
  781. info.m_bWritePhonemesInWavs = CommandLine()->FindParm( "-p" ) != 0;
  782. info.m_bUsePhonemesInWavs = CommandLine()->FindParm( "-w" ) != 0;
  783. info.m_flSampleRateHz = CommandLine()->ParmValue( "-r", 20.0f );
  784. info.m_flSampleFilterSize = CommandLine()->ParmValue( "-s", 0.08f );
  785. info.m_bGenerateSFMFiles = CommandLine()->FindParm( "-nosfm" ) == 0;
  786. info.m_bExtractPhonemeFromWavsForMp3 = CommandLine()->FindParm("-mp3");
  787. if ( !info.m_pCSVFile || !info.m_pModelName )
  788. {
  789. PrintHelp();
  790. return 0;
  791. }
  792. if ( !info.m_pOutputDirectory && info.m_bGenerateSFMFiles )
  793. {
  794. PrintHelp();
  795. return 0;
  796. }
  797. if ( info.m_bUsePhonemesInWavs && info.m_bWritePhonemesInWavs )
  798. {
  799. Warning( "Cannot simultaneously read the phones from wavs and also write them into the wavs!\n" );
  800. return 0;
  801. }
  802. if ( info.m_pExportFacDirectory && !Q_stricmp( info.m_pExportFacDirectory, info.m_pOutputDirectory ) )
  803. {
  804. Warning( "Must specify different directories for output + facial export!\n" );
  805. return 0;
  806. }
  807. g_pSoundEmitterSystem->ModInit();
  808. sfm_phonemeextractor->Init();
  809. GenerateSFMFiles( info );
  810. sfm_phonemeextractor->Shutdown();
  811. g_pSoundEmitterSystem->ModShutdown();
  812. return -1;
  813. }