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.

548 lines
16 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Sound management functions. Exposes a list of available sounds.
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "stdafx.h"
  8. #include "soundsystem.h"
  9. #include "mmsystem.h"
  10. #include "filesystem.h"
  11. #include "KeyValues.h"
  12. #include "hammer.h"
  13. #include "HammerScene.h"
  14. #include "ScenePreviewDlg.h"
  15. #include "soundchars.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include <tier0/memdbgon.h>
  18. // FIXME: Put gamesounds parsing into shared code somewhere
  19. #define MANIFEST_FILE "scripts/game_sounds_manifest.txt"
  20. #define SOUNDGENDER_MACRO "$gender"
  21. #define SOUNDGENDER_MACRO_LENGTH 7 // Length of above including $
  22. // Sounds we're playing are loaded into here for Windows to access while playing them.
  23. CUtlVector<char> g_SoundPlayData;
  24. //-----------------------------------------------------------------------------
  25. // Singleton sound system
  26. //-----------------------------------------------------------------------------
  27. CSoundSystem g_Sounds;
  28. //-----------------------------------------------------------------------------
  29. // Constructor, destructor
  30. //-----------------------------------------------------------------------------
  31. CSoundSystem::CSoundSystem()
  32. {
  33. }
  34. CSoundSystem::~CSoundSystem()
  35. {
  36. ShutDown();
  37. }
  38. //-----------------------------------------------------------------------------
  39. // Initialization, shutdown
  40. //-----------------------------------------------------------------------------
  41. bool CSoundSystem::Initialize( )
  42. {
  43. for ( int i = 0; i < SOUND_TYPE_COUNT; ++i )
  44. {
  45. m_SoundList[i].m_Sounds.EnsureCapacity( 1024 );
  46. m_SoundList[i].m_pStrings = NULL;
  47. if (!BuildSoundList( (SoundType_t)i ) )
  48. return false;
  49. }
  50. return true;
  51. }
  52. void CSoundSystem::ShutDown(void)
  53. {
  54. for ( int i = 0; i < SOUND_TYPE_COUNT; ++i )
  55. {
  56. CleanupSoundList( (SoundType_t)i );
  57. }
  58. }
  59. //-----------------------------------------------------------------------------
  60. // Build the list of sounds
  61. //-----------------------------------------------------------------------------
  62. bool CSoundSystem::BuildSoundList( SoundType_t type )
  63. {
  64. CleanupSoundList( type );
  65. switch( type )
  66. {
  67. case SOUND_TYPE_RAW:
  68. return RecurseIntoDirectories( "sound", &CSoundSystem::ProcessDirectory_RawFileList );
  69. case SOUND_TYPE_GAMESOUND:
  70. return BuildGameSoundList();
  71. case SOUND_TYPE_SCENE:
  72. return RecurseIntoDirectories( "scenes", &CSoundSystem::ProcessDirectory_SceneFileList );
  73. }
  74. return false;
  75. }
  76. //-----------------------------------------------------------------------------
  77. // Cleans up the sound list
  78. //-----------------------------------------------------------------------------
  79. void CSoundSystem::CleanupSoundList( SoundType_t type )
  80. {
  81. m_SoundList[type].m_Sounds.RemoveAll();
  82. DestroyStringCache( m_SoundList[type].m_pStrings );
  83. m_SoundList[type].m_pStrings = NULL;
  84. }
  85. //-----------------------------------------------------------------------------
  86. // Allocate, deallocate a string cache
  87. //-----------------------------------------------------------------------------
  88. CSoundSystem::StringCache_t *CSoundSystem::CreateStringCache( CSoundSystem::StringCache_t* pPrevious )
  89. {
  90. StringCache_t *pCache = new StringCache_t;
  91. pCache->m_nTailIndex = 0;
  92. pCache->m_pNext = pPrevious;
  93. return pCache;
  94. }
  95. void CSoundSystem::DestroyStringCache( CSoundSystem::StringCache_t *pCache )
  96. {
  97. if ( pCache )
  98. {
  99. DestroyStringCache( pCache->m_pNext );
  100. delete pCache;
  101. }
  102. }
  103. //-----------------------------------------------------------------------------
  104. // Adds a string to the string cache
  105. //-----------------------------------------------------------------------------
  106. char *CSoundSystem::AddStringToCache( SoundType_t type, const char *pString )
  107. {
  108. int copyLen = V_strlen( pString ) + 1;
  109. StringCache_t *pCache = m_SoundList[type].m_pStrings;
  110. if ( (!pCache) || ( copyLen + pCache->m_nTailIndex > StringCache_t::STRING_CACHE_SIZE ) )
  111. {
  112. m_SoundList[type].m_pStrings = CreateStringCache( pCache );
  113. pCache = m_SoundList[type].m_pStrings;
  114. }
  115. char fixedString[MAX_PATH];
  116. V_strncpy( fixedString, pString, sizeof( fixedString ) );
  117. V_FixSlashes( fixedString );
  118. copyLen = V_strlen( fixedString ) + 1;
  119. char *pDest = &pCache->m_pBuf[ pCache->m_nTailIndex ];
  120. memcpy( pDest, fixedString, copyLen );
  121. pCache->m_nTailIndex += copyLen;
  122. return pDest;
  123. }
  124. //-----------------------------------------------------------------------------
  125. // Adds a sound to a sound list
  126. //-----------------------------------------------------------------------------
  127. void CSoundSystem::AddSoundToList( SoundType_t type, const char *pSoundName, const char *pActualFile, const char *pSourceFile )
  128. {
  129. // FIXME: Optimize the allocation pattern?
  130. int i = m_SoundList[type].m_Sounds.AddToTail();
  131. SoundInfo_t &info = m_SoundList[type].m_Sounds[i];
  132. info.m_pSoundName = AddStringToCache( type, pSoundName );
  133. if ( type == SOUND_TYPE_RAW )
  134. {
  135. info.m_pSoundFile = info.m_pSoundName;
  136. info.m_pSourceFile = info.m_pSoundName;
  137. }
  138. else
  139. {
  140. info.m_pSoundFile = AddStringToCache( type, pActualFile );
  141. info.m_pSourceFile = pSourceFile;
  142. }
  143. }
  144. //-----------------------------------------------------------------------------
  145. // Add all sounds that lie within a single directory
  146. //-----------------------------------------------------------------------------
  147. void CSoundSystem::BuildFileListInDirectory( char const* pDirectoryName, const char *pExt, SoundType_t soundType )
  148. {
  149. Assert( Q_strlen( pExt ) <= 3 );
  150. int nDirectoryNameLen = V_strlen( pDirectoryName );
  151. char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 7 );
  152. Q_snprintf( pWildCard, nDirectoryNameLen + 7, "%s/*.%s", pDirectoryName, pExt );
  153. FileFindHandle_t findHandle;
  154. const char *pFileName = g_pFullFileSystem->FindFirst( pWildCard, &findHandle );
  155. for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( findHandle ) )
  156. {
  157. if( g_pFullFileSystem->FindIsDirectory( findHandle ) )
  158. continue;
  159. // Strip off the 'sound/' part of the sound name.
  160. int nAllocSize = nDirectoryNameLen + Q_strlen(pFileName) + 2;
  161. char *pFileNameWithPath = (char *)stackalloc( nAllocSize );
  162. const char *pStartPos = max( strchr( pDirectoryName, '/' ), strchr( pDirectoryName, '\\' ) );
  163. if ( pStartPos )
  164. Q_snprintf( pFileNameWithPath, nAllocSize, "%s%c%s", pStartPos+1, CORRECT_PATH_SEPARATOR, pFileName );
  165. else
  166. V_strncpy( pFileNameWithPath, pFileName, nAllocSize );
  167. Q_strnlwr( pFileNameWithPath, nAllocSize );
  168. AddSoundToList( soundType, pFileNameWithPath, pFileNameWithPath, NULL );
  169. }
  170. g_pFullFileSystem->FindClose( findHandle );
  171. }
  172. //-----------------------------------------------------------------------------
  173. // Populate the list of .WAV files
  174. //-----------------------------------------------------------------------------
  175. bool CSoundSystem::RecurseIntoDirectories( char const* pDirectoryName, pDirCallbackFn fn )
  176. {
  177. // Have the callback process the directory.
  178. if ( !(this->*fn)( pDirectoryName ) )
  179. return false;
  180. int nDirectoryNameLen = Q_strlen( pDirectoryName );
  181. char *pWildCard = ( char * )stackalloc( nDirectoryNameLen + 5 );
  182. strcpy(pWildCard, pDirectoryName);
  183. strcat(pWildCard, "/*.*");
  184. int nPathStrLen = nDirectoryNameLen + 1;
  185. FileFindHandle_t findHandle;
  186. const char *pFileName = g_pFullFileSystem->FindFirst( pWildCard, &findHandle );
  187. for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( findHandle ) )
  188. {
  189. if ((pFileName[0] != '.') || (pFileName[1] != '.' && pFileName[1] != 0))
  190. {
  191. if( !g_pFullFileSystem->FindIsDirectory( findHandle ) )
  192. continue;
  193. int fileNameStrLen = Q_strlen( pFileName );
  194. char *pFileNameWithPath = ( char * )stackalloc( nPathStrLen + fileNameStrLen + 1 );
  195. memcpy( pFileNameWithPath, pWildCard, nPathStrLen );
  196. pFileNameWithPath[nPathStrLen] = '\0';
  197. Q_strncat( pFileNameWithPath, pFileName, nPathStrLen + fileNameStrLen + 1 );
  198. if (!RecurseIntoDirectories( pFileNameWithPath, fn ))
  199. return false;
  200. }
  201. }
  202. return true;
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Populate the list of .WAV files
  206. //-----------------------------------------------------------------------------
  207. bool CSoundSystem::ProcessDirectory_RawFileList( char const* pDirectoryName )
  208. {
  209. if ( !g_pFileSystem )
  210. return false;
  211. Assert( Q_strnicmp( pDirectoryName, "sound", 5 ) == 0 );
  212. // Get all sound files out of this directory
  213. BuildFileListInDirectory( pDirectoryName, "wav", SOUND_TYPE_RAW );
  214. BuildFileListInDirectory( pDirectoryName, "mp3", SOUND_TYPE_RAW );
  215. return true;
  216. }
  217. //-----------------------------------------------------------------------------
  218. // Populate the list of .VCD files
  219. //-----------------------------------------------------------------------------
  220. bool CSoundSystem::ProcessDirectory_SceneFileList( char const* pDirectoryName )
  221. {
  222. if ( !g_pFileSystem )
  223. return false;
  224. // Get all sound files out of this directory
  225. BuildFileListInDirectory( pDirectoryName, "vcd", SOUND_TYPE_SCENE );
  226. return true;
  227. }
  228. //-----------------------------------------------------------------------------
  229. // Splits a name into 2
  230. //-----------------------------------------------------------------------------
  231. static void SplitName( char const *input, int splitchar, int splitlen, char *before, int beforelen, char *after, int afterlen )
  232. {
  233. char const *in = input;
  234. char *out = before;
  235. int c = 0;
  236. int l = 0;
  237. int maxl = beforelen;
  238. while ( *in )
  239. {
  240. if ( c == splitchar )
  241. {
  242. while ( --splitlen >= 0 )
  243. {
  244. in++;
  245. }
  246. *out = 0;
  247. out = after;
  248. maxl = afterlen;
  249. c++;
  250. continue;
  251. }
  252. if ( l >= maxl )
  253. {
  254. in++;
  255. c++;
  256. continue;
  257. }
  258. *out++ = *in++;
  259. l++;
  260. c++;
  261. }
  262. *out = 0;
  263. }
  264. //-----------------------------------------------------------------------------
  265. // Gamesounds may have macros embedded in them
  266. //-----------------------------------------------------------------------------
  267. void CSoundSystem::AddGameSoundToList( const char *pGameSound, char const *pFileName, const char *pSourceFile )
  268. {
  269. char const *p = Q_stristr( pFileName, SOUNDGENDER_MACRO );
  270. if ( !p )
  271. {
  272. AddSoundToList( SOUND_TYPE_GAMESOUND, pGameSound, pFileName, pSourceFile );
  273. return;
  274. }
  275. int offset = p - pFileName;
  276. Assert( offset >= 0 );
  277. int duration = SOUNDGENDER_MACRO_LENGTH;
  278. // Create a "male" version of the sound only for browsing
  279. char before[ 256 ], after[ 256 ];
  280. Q_memset( before, 0, sizeof( before ) );
  281. Q_memset( after, 0, sizeof( after ) );
  282. SplitName( pFileName, offset, duration, before, sizeof( before ), after, sizeof( after ) );
  283. char temp[ 256 ];
  284. Q_snprintf( temp, sizeof( temp ), "%s%s%s", before, "male", after );
  285. AddSoundToList( SOUND_TYPE_GAMESOUND, pGameSound, temp, pSourceFile );
  286. }
  287. // memdbgon must be the last include file in a .cpp file!!!
  288. #include <tier0/memdbgoff.h>
  289. //-----------------------------------------------------------------------------
  290. // Load all game sounds from a particular file
  291. //-----------------------------------------------------------------------------
  292. void CSoundSystem::AddGameSoundsFromFile( const char *pFileName )
  293. {
  294. KeyValues *kv = new KeyValues( pFileName );
  295. if ( !kv->LoadFromFile( g_pFileSystem, pFileName, "GAME" ) )
  296. {
  297. kv->deleteThis();
  298. return;
  299. }
  300. const char *pSourceFile = AddStringToCache( SOUND_TYPE_GAMESOUND, pFileName );
  301. // parse out all of the top level sections and save their names
  302. for ( KeyValues *pKeys = kv; pKeys; pKeys = pKeys->GetNextKey() )
  303. {
  304. if ( !pKeys->GetFirstSubKey() )
  305. continue;
  306. const char *pRawFile = pKeys->GetString( "wave", NULL );
  307. if ( pRawFile )
  308. {
  309. AddGameSoundToList( pKeys->GetName(), pRawFile, pSourceFile );
  310. }
  311. else
  312. {
  313. KeyValues *pRndWave = pKeys->FindKey( "rndwave" );
  314. if ( pRndWave )
  315. {
  316. KeyValues *pFirstFile = pRndWave->GetFirstSubKey();
  317. if ( pFirstFile )
  318. {
  319. AddGameSoundToList( pKeys->GetName(), pFirstFile->GetString(), pSourceFile );
  320. }
  321. }
  322. }
  323. }
  324. if ( kv )
  325. {
  326. kv->deleteThis();
  327. }
  328. }
  329. //-----------------------------------------------------------------------------
  330. // Populate the list of game sounds
  331. //-----------------------------------------------------------------------------
  332. bool CSoundSystem::BuildGameSoundList()
  333. {
  334. KeyValues *manifest = new KeyValues( MANIFEST_FILE );
  335. if ( !manifest->LoadFromFile( g_pFileSystem, MANIFEST_FILE, "GAME" ) )
  336. {
  337. manifest->deleteThis();
  338. return false;
  339. }
  340. for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() )
  341. {
  342. if ( !Q_stricmp( sub->GetName(), "precache_file" ) ||
  343. !Q_stricmp( sub->GetName(), "declare_file" ) ||
  344. !Q_stricmp( sub->GetName(), "preload_file" ) )
  345. {
  346. // Add and always precache
  347. AddGameSoundsFromFile( sub->GetString() );
  348. }
  349. }
  350. manifest->deleteThis();
  351. return true;
  352. }
  353. //-----------------------------------------------------------------------------
  354. // Plays a sound
  355. //-----------------------------------------------------------------------------
  356. bool CSoundSystem::FindSoundByName( const char *pFilename, SoundType_t *type, int *nIndex )
  357. {
  358. char searchStr[MAX_PATH];
  359. V_strncpy( searchStr, pFilename, sizeof( searchStr ) );
  360. V_FixSlashes( searchStr );
  361. for ( int i = SOUND_TYPE_COUNT; --i >= 0; )
  362. {
  363. for ( int j = SoundCount( (SoundType_t)i ); --j >= 0; )
  364. {
  365. if ( Q_stristr( searchStr, SoundName( (SoundType_t)i, j ) ) )
  366. {
  367. *type = (SoundType_t)i;
  368. *nIndex = j;
  369. return true;
  370. }
  371. }
  372. }
  373. return false;
  374. }
  375. bool CSoundSystem::PlayScene( const char *pFileName )
  376. {
  377. char fullFilename[MAX_PATH];
  378. V_snprintf( fullFilename, sizeof( fullFilename ), "scenes%c%s", CORRECT_PATH_SEPARATOR, pFileName );
  379. CChoreoScene *pScene = HammerLoadScene( fullFilename );
  380. if ( !pScene )
  381. return false;
  382. CScenePreviewDlg dlg( pScene, pFileName );
  383. dlg.DoModal();
  384. return true;
  385. }
  386. //-----------------------------------------------------------------------------
  387. // Plays a sound
  388. //-----------------------------------------------------------------------------
  389. bool CSoundSystem::Play( SoundType_t type, int nIndex )
  390. {
  391. const char *pFileName = SoundFile( type, nIndex );
  392. if ( !pFileName )
  393. return false;
  394. // If it's a scene, get the first sound in the scene.
  395. if ( type == SOUND_TYPE_SCENE )
  396. {
  397. return PlayScene( pFileName );
  398. }
  399. // Voiceover files have this.
  400. pFileName = PSkipSoundChars( pFileName );
  401. char pRelativePath[MAX_PATH];
  402. Q_snprintf( pRelativePath, MAX_PATH, "sound/%s", pFileName );
  403. // Stop any previously-playing sound.
  404. StopSound();
  405. // We used to use GetLocalPath, but that doesn't work under Steam.
  406. FileHandle_t fp = g_pFileSystem->Open( pRelativePath, "rb" );
  407. if ( fp )
  408. {
  409. g_SoundPlayData.SetSize( g_pFileSystem->Size( fp ) );
  410. if ( g_pFileSystem->Read( g_SoundPlayData.Base(), g_SoundPlayData.Count(), fp ) == g_SoundPlayData.Count() )
  411. {
  412. return (PlaySound( g_SoundPlayData.Base(), NULL, SND_ASYNC | SND_MEMORY ) != FALSE);
  413. }
  414. g_pFileSystem->Close( fp );
  415. }
  416. return false;
  417. }
  418. //-----------------------------------------------------------------------------
  419. // Stops any playing sound.
  420. //-----------------------------------------------------------------------------
  421. void CSoundSystem::StopSound()
  422. {
  423. PlaySound( NULL, NULL, SND_ASYNC | SND_MEMORY );
  424. }
  425. //-----------------------------------------------------------------------------
  426. // Opens the source file associated with a sound
  427. //-----------------------------------------------------------------------------
  428. void CSoundSystem::OpenSource( SoundType_t type, int nIndex )
  429. {
  430. if ( type == SOUND_TYPE_RAW )
  431. return;
  432. const char *pFileName = SoundSourceFile( type, nIndex );
  433. if ( pFileName )
  434. {
  435. char pRelativePath[MAX_PATH];
  436. Q_snprintf( pRelativePath, MAX_PATH, "%s", pFileName );
  437. char pFullPath[MAX_PATH];
  438. if ( g_pFullFileSystem->GetLocalPath( pRelativePath, pFullPath, MAX_PATH ) )
  439. {
  440. ShellExecute( NULL, "open", pFullPath, NULL, NULL, SW_SHOWNORMAL );
  441. }
  442. }
  443. }