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.

1312 lines
38 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $Workfile: $
  6. // $Date: $
  7. // $NoKeywords: $
  8. //=============================================================================//
  9. #include "cbase.h"
  10. #include "sharedInterface.h"
  11. #include "soundenvelope.h"
  12. #include "engine/IEngineSound.h"
  13. #include "IEffects.h"
  14. #include "isaverestore.h"
  15. #include "saverestore_utlvector.h"
  16. #include "gamestringpool.h"
  17. #include "igamesystem.h"
  18. #include "utlpriorityqueue.h"
  19. #include "mempool.h"
  20. #include "SoundEmitterSystem/isoundemittersystembase.h"
  21. #include "tier0/vprof.h"
  22. #include "gamerules.h"
  23. // memdbgon must be the last include file in a .cpp file!!!
  24. #include "tier0/memdbgon.h"
  25. static ConVar soundpatch_captionlength( "soundpatch_captionlength", "2.0", FCVAR_REPLICATED, "How long looping soundpatch captions should display for." );
  26. // Envelope
  27. // This is a class that controls a ramp for a sound (pitch / volume / etc)
  28. class CSoundEnvelope
  29. {
  30. public:
  31. DECLARE_SIMPLE_DATADESC();
  32. CSoundEnvelope()
  33. {
  34. m_current = 0.0f;
  35. m_target = 0.0f;
  36. m_rate = 0.0f;
  37. m_forceupdate = false;
  38. }
  39. void SetTarget( float target, float deltaTime );
  40. void SetValue( float value );
  41. bool ShouldUpdate( void );
  42. void Update( float time );
  43. inline float Value( void ) { return m_current; }
  44. private:
  45. float m_current;
  46. float m_target;
  47. float m_rate;
  48. bool m_forceupdate;
  49. };
  50. BEGIN_SIMPLE_DATADESC( CSoundEnvelope )
  51. DEFINE_FIELD( m_current, FIELD_FLOAT ),
  52. DEFINE_FIELD( m_target, FIELD_FLOAT ),
  53. DEFINE_FIELD( m_rate, FIELD_FLOAT ),
  54. DEFINE_FIELD( m_forceupdate, FIELD_BOOLEAN ),
  55. END_DATADESC()
  56. //-----------------------------------------------------------------------------
  57. // Purpose: Set the new target value for this ramp. Reach this target in deltaTime
  58. // seconds from now
  59. // Input : target - new target value
  60. // deltaTime - time to reach target
  61. //-----------------------------------------------------------------------------
  62. void CSoundEnvelope::SetTarget( float target, float deltaTime )
  63. {
  64. float deltaValue = target - m_current;
  65. if ( deltaValue && deltaTime > 0 )
  66. {
  67. m_target = target;
  68. m_rate = MAX( 0.1, fabs(deltaValue / deltaTime) );
  69. }
  70. else
  71. {
  72. if ( target != m_current )
  73. {
  74. m_forceupdate = true;
  75. }
  76. SetValue( target );
  77. }
  78. }
  79. //-----------------------------------------------------------------------------
  80. // Purpose: Instantaneously set the value of this ramp
  81. // Input : value - new value
  82. //-----------------------------------------------------------------------------
  83. void CSoundEnvelope::SetValue( float value )
  84. {
  85. if ( m_target != value )
  86. {
  87. m_forceupdate = true;
  88. }
  89. m_current = m_target = value;
  90. m_rate = 0;
  91. }
  92. //-----------------------------------------------------------------------------
  93. // Purpose: Check to see if I need to update this envelope
  94. // Output : Returns true if this envelope is changing
  95. //-----------------------------------------------------------------------------
  96. bool CSoundEnvelope::ShouldUpdate( void )
  97. {
  98. if ( m_forceupdate )
  99. {
  100. m_forceupdate = false;
  101. return true;
  102. }
  103. if ( m_current != m_target )
  104. {
  105. return true;
  106. }
  107. return false;
  108. }
  109. //-----------------------------------------------------------------------------
  110. // Purpose: Update the envelope for the current frame time
  111. // Input : time - amount of time that has passed
  112. //-----------------------------------------------------------------------------
  113. void CSoundEnvelope::Update( float deltaTime )
  114. {
  115. m_current = Approach( m_target, m_current, m_rate * deltaTime );
  116. }
  117. class CCopyRecipientFilter : public IRecipientFilter
  118. {
  119. public:
  120. DECLARE_SIMPLE_DATADESC();
  121. CCopyRecipientFilter() : m_Flags(0) {}
  122. void Init( IRecipientFilter *pSrc )
  123. {
  124. m_Flags = FLAG_ACTIVE;
  125. if ( pSrc->IsReliable() )
  126. {
  127. m_Flags |= FLAG_RELIABLE;
  128. }
  129. if ( pSrc->IsInitMessage() )
  130. {
  131. m_Flags |= FLAG_INIT_MESSAGE;
  132. }
  133. for ( int i = 0; i < pSrc->GetRecipientCount(); i++ )
  134. {
  135. int index = pSrc->GetRecipientIndex( i );
  136. if ( index >= 0 )
  137. m_Recipients.AddToTail( index );
  138. }
  139. }
  140. bool IsActive() const
  141. {
  142. return (m_Flags & FLAG_ACTIVE) != 0;
  143. }
  144. virtual bool IsReliable( void ) const
  145. {
  146. return (m_Flags & FLAG_RELIABLE) != 0;
  147. }
  148. virtual int GetRecipientCount( void ) const
  149. {
  150. return m_Recipients.Count();
  151. }
  152. virtual int GetRecipientIndex( int slot ) const
  153. {
  154. return m_Recipients[ slot ];
  155. }
  156. virtual bool IsInitMessage( void ) const
  157. {
  158. return (m_Flags & FLAG_INIT_MESSAGE) != 0;
  159. }
  160. virtual bool AddRecipient( CBasePlayer *player )
  161. {
  162. Assert( player );
  163. int index = player->entindex();
  164. if ( index < 0 )
  165. return false;
  166. // Already in list
  167. if ( m_Recipients.Find( index ) != m_Recipients.InvalidIndex() )
  168. return false;
  169. m_Recipients.AddToTail( index );
  170. return true;
  171. }
  172. private:
  173. enum
  174. {
  175. FLAG_ACTIVE = 0x1,
  176. FLAG_RELIABLE = 0x2,
  177. FLAG_INIT_MESSAGE = 0x4,
  178. };
  179. int m_Flags;
  180. CUtlVector< int > m_Recipients;
  181. };
  182. BEGIN_SIMPLE_DATADESC( CCopyRecipientFilter )
  183. DEFINE_FIELD( m_Flags, FIELD_INTEGER ),
  184. DEFINE_UTLVECTOR( m_Recipients, FIELD_INTEGER ),
  185. END_DATADESC()
  186. #include "tier0/memdbgoff.h"
  187. // This is the a basic sound controller, a "patch"
  188. // It has envelopes for pitch and volume and can manage state changes to those
  189. class CSoundPatch
  190. {
  191. public:
  192. DECLARE_SIMPLE_DATADESC();
  193. static int g_SoundPatchCount;
  194. CSoundPatch()
  195. {
  196. g_SoundPatchCount++;
  197. m_iszSoundName = NULL_STRING;
  198. m_iszSoundScriptName = NULL_STRING;
  199. m_flCloseCaptionDuration = soundpatch_captionlength.GetFloat();
  200. }
  201. ~CSoundPatch()
  202. {
  203. g_SoundPatchCount--;
  204. }
  205. void Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName,
  206. soundlevel_t iSoundLevel );
  207. void ChangePitch( float pitchTarget, float deltaTime );
  208. void ChangeVolume( float volumeTarget, float deltaTime );
  209. void FadeOut( float deltaTime, bool destroyOnFadeout );
  210. float GetPitch( void );
  211. float GetVolume( void );
  212. string_t GetName() { return m_iszSoundName; };
  213. string_t GetScriptName() { return m_iszSoundScriptName; }
  214. // UNDONE: Don't call this, use the controller to shut down
  215. void Shutdown( void );
  216. bool Update( float time, float deltaTime );
  217. void Reset( void );
  218. void StartSound( float flStartTime = 0 );
  219. void ResumeSound( void );
  220. int IsPlaying( void ) { return m_isPlaying; }
  221. void AddPlayerPost( CBasePlayer *pPlayer );
  222. void SetCloseCaptionDuration( float flDuration ) { m_flCloseCaptionDuration = flDuration; }
  223. void SetBaseFlags( int iFlags ) { m_baseFlags = iFlags; }
  224. // Returns the ent index
  225. int EntIndex() const;
  226. private:
  227. // SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume.
  228. // This function is an internal method of accessing the real volume passed into the engine (i.e. post multiply)
  229. float GetVolumeForEngine( void );
  230. private:
  231. CSoundEnvelope m_pitch;
  232. CSoundEnvelope m_volume;
  233. soundlevel_t m_soundlevel;
  234. float m_shutdownTime;
  235. float m_flLastTime;
  236. string_t m_iszSoundName;
  237. string_t m_iszSoundScriptName;
  238. EHANDLE m_hEnt;
  239. int m_entityChannel;
  240. int m_flags;
  241. int m_baseFlags;
  242. int m_isPlaying;
  243. float m_flScriptVolume; // Volume for this sound in sounds.txt
  244. CCopyRecipientFilter m_Filter;
  245. float m_flCloseCaptionDuration;
  246. #ifdef _DEBUG
  247. // Used to get the classname of the entity associated with the sound
  248. string_t m_iszClassName;
  249. #endif
  250. DECLARE_FIXEDSIZE_ALLOCATOR(CSoundPatch);
  251. };
  252. #include "tier0/memdbgon.h"
  253. int CSoundPatch::g_SoundPatchCount = 0;
  254. CON_COMMAND( report_soundpatch, "reports sound patch count" )
  255. {
  256. #ifndef CLIENT_DLL
  257. if ( !UTIL_IsCommandIssuedByServerAdmin() )
  258. return;
  259. #endif
  260. Msg("Current sound patches: %d\n", CSoundPatch::g_SoundPatchCount );
  261. }
  262. DEFINE_FIXEDSIZE_ALLOCATOR( CSoundPatch, 64, CUtlMemoryPool::GROW_FAST );
  263. BEGIN_SIMPLE_DATADESC( CSoundPatch )
  264. DEFINE_EMBEDDED( m_pitch ),
  265. DEFINE_EMBEDDED( m_volume ),
  266. DEFINE_FIELD( m_soundlevel, FIELD_INTEGER ),
  267. DEFINE_FIELD( m_shutdownTime, FIELD_TIME ),
  268. DEFINE_FIELD( m_flLastTime, FIELD_TIME ),
  269. DEFINE_FIELD( m_iszSoundName, FIELD_STRING ),
  270. DEFINE_FIELD( m_iszSoundScriptName, FIELD_STRING ),
  271. DEFINE_FIELD( m_hEnt, FIELD_EHANDLE ),
  272. DEFINE_FIELD( m_entityChannel, FIELD_INTEGER ),
  273. DEFINE_FIELD( m_flags, FIELD_INTEGER ),
  274. DEFINE_FIELD( m_baseFlags, FIELD_INTEGER ),
  275. DEFINE_FIELD( m_isPlaying, FIELD_INTEGER ),
  276. DEFINE_FIELD( m_flScriptVolume, FIELD_FLOAT ),
  277. DEFINE_EMBEDDED( m_Filter ),
  278. DEFINE_FIELD( m_flCloseCaptionDuration, FIELD_FLOAT ),
  279. // Not saved, it's debug only
  280. // DEFINE_FIELD( m_iszClassName, FIELD_STRING ),
  281. END_DATADESC()
  282. //-----------------------------------------------------------------------------
  283. // Purpose: Setup the patch
  284. // Input : nEntIndex - index of the edict that owns the sound channel
  285. // channel - This is a sound channel (CHAN_ITEM, CHAN_STATIC)
  286. // *pSoundName - sound script string name
  287. // attenuation - attenuation of this sound (not animated)
  288. //-----------------------------------------------------------------------------
  289. void CSoundPatch::Init( IRecipientFilter *pFilter, CBaseEntity *pEnt, int channel, const char *pSoundName,
  290. soundlevel_t soundlevel )
  291. {
  292. m_hEnt = pEnt;
  293. m_entityChannel = channel;
  294. // Get the volume from the script
  295. CSoundParameters params;
  296. if ( !Q_stristr( pSoundName, ".wav" ) && !Q_stristr( pSoundName, ".mp3" ) &&
  297. CBaseEntity::GetParametersForSound( pSoundName, params, NULL ) )
  298. {
  299. m_flScriptVolume = params.volume;
  300. // This has to be the actual .wav because rndwave would cause a bunch of new .wavs to play... bad...
  301. // e.g., when you pitch shift it would start a different wav instead.
  302. m_iszSoundScriptName = AllocPooledString( pSoundName );
  303. pSoundName = params.soundname;
  304. m_soundlevel = params.soundlevel;
  305. m_entityChannel = params.channel;
  306. }
  307. else
  308. {
  309. m_iszSoundScriptName = AllocPooledString( pSoundName );
  310. m_flScriptVolume = 1.0;
  311. m_soundlevel = soundlevel;
  312. }
  313. m_iszSoundName = AllocPooledString( pSoundName );
  314. m_volume.SetValue( 0 );
  315. m_pitch.SetValue( 0 );
  316. m_isPlaying = false;
  317. m_shutdownTime = 0;
  318. m_flLastTime = 0;
  319. m_Filter.Init( pFilter );
  320. m_baseFlags = 0;
  321. #ifdef _DEBUG
  322. if ( pEnt )
  323. {
  324. m_iszClassName = AllocPooledString( pEnt->GetClassname() );
  325. }
  326. #endif
  327. }
  328. //-----------------------------------------------------------------------------
  329. // Purpose: Ramps the pitch to a new value
  330. // Input : pitchTarget - new value
  331. // deltaTime - seconds to reach the value
  332. //-----------------------------------------------------------------------------
  333. void CSoundPatch::ChangePitch( float pitchTarget, float deltaTime )
  334. {
  335. m_flags |= SND_CHANGE_PITCH;
  336. m_pitch.SetTarget( pitchTarget, deltaTime );
  337. }
  338. //-----------------------------------------------------------------------------
  339. // Purpose: Ramps the volume to a new value
  340. // Input : volumeTarget - new volume
  341. // deltaTime - seconds to reach the new volume
  342. //-----------------------------------------------------------------------------
  343. void CSoundPatch::ChangeVolume( float volumeTarget, float deltaTime )
  344. {
  345. m_flags |= SND_CHANGE_VOL;
  346. if ( volumeTarget > 1.0 )
  347. volumeTarget = 1.0;
  348. m_volume.SetTarget( volumeTarget, deltaTime );
  349. }
  350. //-----------------------------------------------------------------------------
  351. // Purpose: Fade volume to zero AND SHUT DOWN THIS SOUND
  352. // Input : deltaTime - seconds before done/shutdown
  353. //-----------------------------------------------------------------------------
  354. void CSoundPatch::FadeOut( float deltaTime, bool destroyOnFadeout )
  355. {
  356. ChangeVolume( 0, deltaTime );
  357. if ( !destroyOnFadeout )
  358. {
  359. m_shutdownTime = g_pEffects->Time() + deltaTime;
  360. }
  361. }
  362. //-----------------------------------------------------------------------------
  363. // Purpose: Get the sound's current pitch
  364. //-----------------------------------------------------------------------------
  365. float CSoundPatch::GetPitch( void )
  366. {
  367. return m_pitch.Value();
  368. }
  369. //-----------------------------------------------------------------------------
  370. // Purpose: Get the sound's current volume
  371. //-----------------------------------------------------------------------------
  372. float CSoundPatch::GetVolume( void )
  373. {
  374. return m_volume.Value();
  375. }
  376. //-----------------------------------------------------------------------------
  377. // Returns the ent index
  378. //-----------------------------------------------------------------------------
  379. inline int CSoundPatch::EntIndex() const
  380. {
  381. Assert( !m_hEnt.IsValid() || m_hEnt.Get() );
  382. return m_hEnt.Get() ? m_hEnt->entindex() : -1;
  383. }
  384. //-----------------------------------------------------------------------------
  385. // Purpose: SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume.
  386. // This function is an internal method of accessing the real volume passed into the engine (i.e. post multiply)
  387. // Output : float
  388. //-----------------------------------------------------------------------------
  389. float CSoundPatch::GetVolumeForEngine( void )
  390. {
  391. return ( m_flScriptVolume * m_volume.Value() );
  392. }
  393. //-----------------------------------------------------------------------------
  394. // Purpose: Stop the sound
  395. //-----------------------------------------------------------------------------
  396. void CSoundPatch::Shutdown( void )
  397. {
  398. // Msg( "Removing sound %s\n", m_pszSoundName );
  399. if ( m_isPlaying )
  400. {
  401. int entIndex = EntIndex();
  402. Assert( entIndex >= 0 );
  403. // BUGBUG: Don't crash in release mode
  404. if ( entIndex >= 0 )
  405. {
  406. CBaseEntity::StopSound( entIndex, m_entityChannel, STRING( m_iszSoundName ) );
  407. }
  408. m_isPlaying = false;
  409. }
  410. }
  411. //-----------------------------------------------------------------------------
  412. // Purpose: Update all envelopes and send appropriate data to the client
  413. // Input : time - new global clock
  414. // deltaTime - amount of time that has passed
  415. // Output : Returns true on success, false on failure.
  416. //-----------------------------------------------------------------------------
  417. bool CSoundPatch::Update( float time, float deltaTime )
  418. {
  419. VPROF( "CSoundPatch::Update" );
  420. if ( m_shutdownTime && time > m_shutdownTime )
  421. {
  422. Shutdown();
  423. return false;
  424. }
  425. if ( EntIndex() < 0 )
  426. {
  427. // FIXME: The pointer to this soundpatch is probably leaked since no entity is around to clean it up (ywb)
  428. DevWarning( "CSoundPatch::Update: Removing CSoundPatch (%s) with NULL EHandle\n", STRING(m_iszSoundName) );
  429. return false;
  430. }
  431. if ( m_pitch.ShouldUpdate() )
  432. {
  433. m_pitch.Update( deltaTime );
  434. m_flags |= SND_CHANGE_PITCH;
  435. }
  436. else
  437. {
  438. m_flags &= ~SND_CHANGE_PITCH;
  439. }
  440. if ( m_volume.ShouldUpdate() )
  441. {
  442. m_volume.Update( deltaTime );
  443. m_flags |= SND_CHANGE_VOL;
  444. }
  445. else
  446. {
  447. m_flags &= ~SND_CHANGE_VOL;
  448. }
  449. if ( m_flags && m_Filter.IsActive() )
  450. {
  451. // SoundPatches take volumes between 0 & 1, and use that to multiply the sounds.txt specified volume.
  452. // Because of this, we need to always set the SND_CHANGE_VOL flag when we emit sound, or it'll use the scriptfile's instead.
  453. m_flags |= SND_CHANGE_VOL;
  454. EmitSound_t ep;
  455. ep.m_nChannel = m_entityChannel;
  456. ep.m_pSoundName = STRING(m_iszSoundName);
  457. ep.m_flVolume = GetVolumeForEngine();
  458. ep.m_SoundLevel = m_soundlevel;
  459. ep.m_nFlags = m_flags;
  460. ep.m_nPitch = (int)m_pitch.Value();
  461. CBaseEntity::EmitSound( m_Filter, EntIndex(), ep );
  462. m_flags = 0;
  463. }
  464. return true;
  465. }
  466. //-----------------------------------------------------------------------------
  467. // Purpose: Sound is going to start playing again, clear any shutdown time
  468. //-----------------------------------------------------------------------------
  469. void CSoundPatch::Reset( void )
  470. {
  471. m_shutdownTime = 0;
  472. }
  473. //-----------------------------------------------------------------------------
  474. // Purpose: Start playing the sound - send updates to the client
  475. //-----------------------------------------------------------------------------
  476. void CSoundPatch::StartSound( float flStartTime )
  477. {
  478. // Msg( "Start sound %s\n", m_pszSoundName );
  479. m_flags = 0;
  480. if ( m_Filter.IsActive() )
  481. {
  482. EmitSound_t ep;
  483. ep.m_nChannel = m_entityChannel;
  484. ep.m_pSoundName = STRING(m_iszSoundName);
  485. ep.m_flVolume = GetVolumeForEngine();
  486. ep.m_SoundLevel = m_soundlevel;
  487. ep.m_nFlags = (SND_CHANGE_VOL | m_baseFlags);
  488. ep.m_nPitch = (int)m_pitch.Value();
  489. ep.m_bEmitCloseCaption = false;
  490. if ( flStartTime )
  491. {
  492. ep.m_flSoundTime = flStartTime;
  493. }
  494. CBaseEntity::EmitSound( m_Filter, EntIndex(), ep );
  495. CBaseEntity::EmitCloseCaption( m_Filter, EntIndex(), STRING( m_iszSoundScriptName ), ep.m_UtlVecSoundOrigin, m_flCloseCaptionDuration, true );
  496. }
  497. m_isPlaying = true;
  498. }
  499. //-----------------------------------------------------------------------------
  500. // Purpose: resumes playing the sound on restore
  501. //-----------------------------------------------------------------------------
  502. void CSoundPatch::ResumeSound( void )
  503. {
  504. if ( IsPlaying() && m_Filter.IsActive() )
  505. {
  506. if ( EntIndex() >= 0 )
  507. {
  508. EmitSound_t ep;
  509. ep.m_nChannel = m_entityChannel;
  510. ep.m_pSoundName = STRING(m_iszSoundName);
  511. ep.m_flVolume = GetVolumeForEngine();
  512. ep.m_SoundLevel = m_soundlevel;
  513. ep.m_nFlags = (SND_CHANGE_VOL | SND_CHANGE_PITCH | m_baseFlags);
  514. ep.m_nPitch = (int)m_pitch.Value();
  515. CBaseEntity::EmitSound( m_Filter, EntIndex(), ep );
  516. }
  517. else
  518. {
  519. // FIXME: Lost the entity on restore. It might have been suppressed by the save/restore system.
  520. // This will probably leak the sound patch since there's no one to delete it, but the next
  521. // call to CSoundPatch::Update should at least remove it from the list of sound patches.
  522. DevWarning( "CSoundPatch::ResumeSound: Lost EHAndle on restore - destroy the sound patch in your entity's StopLoopingSounds! (%s)\n", STRING( m_iszSoundName ) );
  523. }
  524. }
  525. }
  526. //-----------------------------------------------------------------------------
  527. // Purpose: A new player's entered the game. See if we need to restart our sound.
  528. //-----------------------------------------------------------------------------
  529. void CSoundPatch::AddPlayerPost( CBasePlayer *pPlayer )
  530. {
  531. if ( m_Filter.IsActive() && m_Filter.AddRecipient(pPlayer) )
  532. {
  533. // Alrighty, he's new. We need to restart our sound just to him.
  534. // Create a new filter just to him.
  535. CSingleUserRecipientFilter filter( pPlayer );
  536. EmitSound_t ep;
  537. ep.m_nChannel = m_entityChannel;
  538. ep.m_pSoundName = STRING(m_iszSoundName);
  539. ep.m_flVolume = GetVolumeForEngine();
  540. ep.m_SoundLevel = m_soundlevel;
  541. ep.m_nFlags = (SND_CHANGE_VOL | m_baseFlags);
  542. ep.m_nPitch = (int)m_pitch.Value();
  543. CBaseEntity::EmitSound( filter, EntIndex(), ep );
  544. }
  545. }
  546. // This is an entry in the command queue. It's used to queue up various pitch and volume changes
  547. // so you can define an envelope without writing timing code in an entity. Existing queued commands
  548. // can be deleted later if the envelope changes dynamically.
  549. #include "tier0/memdbgoff.h"
  550. struct SoundCommand_t
  551. {
  552. SoundCommand_t( void ) { memset( this, 0, sizeof(*this) ); }
  553. SoundCommand_t( CSoundPatch *pSound, float executeTime, soundcommands_t command, float deltaTime, float value ) : m_pPatch(pSound), m_time(executeTime), m_deltaTime(deltaTime), m_command(command), m_value(value) {}
  554. CSoundPatch *m_pPatch;
  555. float m_time;
  556. float m_deltaTime;
  557. soundcommands_t m_command;
  558. float m_value;
  559. SoundCommand_t *m_pNext;
  560. DECLARE_SIMPLE_DATADESC();
  561. DECLARE_FIXEDSIZE_ALLOCATOR(SoundCommand_t);
  562. };
  563. #include "tier0/memdbgon.h"
  564. DEFINE_FIXEDSIZE_ALLOCATOR( SoundCommand_t, 32, CUtlMemoryPool::GROW_FAST );
  565. BEGIN_SIMPLE_DATADESC( SoundCommand_t )
  566. // NOTE: This doesn't need to be saved, sound commands are saved right after the patch
  567. // they are associated with
  568. // DEFINE_FIELD( m_pPatch, FIELD_????? )
  569. DEFINE_FIELD( m_time, FIELD_TIME ),
  570. DEFINE_FIELD( m_deltaTime, FIELD_FLOAT ),
  571. DEFINE_FIELD( m_command, FIELD_INTEGER ),
  572. DEFINE_FIELD( m_value, FIELD_FLOAT ),
  573. // DEFINE_FIELD( m_pNext, FIELD_????? )
  574. END_DATADESC()
  575. typedef SoundCommand_t *SOUNDCOMMANDPTR;
  576. bool SoundCommandLessFunc( const SOUNDCOMMANDPTR &lhs, const SOUNDCOMMANDPTR &rhs )
  577. {
  578. // NOTE: A greater time means "less" priority
  579. return ( lhs->m_time > rhs->m_time );
  580. }
  581. // This implements the sound controller
  582. class CSoundControllerImp : public CSoundEnvelopeController, public CAutoGameSystemPerFrame
  583. {
  584. //-----------------------------------------------------------------------------
  585. // internal functions, private to this file
  586. //-----------------------------------------------------------------------------
  587. public:
  588. CSoundControllerImp( void ) : CAutoGameSystemPerFrame( "CSoundControllerImp" )
  589. {
  590. m_commandList.SetLessFunc( SoundCommandLessFunc );
  591. }
  592. void ProcessCommand( SoundCommand_t *pCmd );
  593. void RemoveFromList( CSoundPatch *pSound );
  594. void SaveSoundPatch( CSoundPatch *pSound, ISave *pSave );
  595. void RestoreSoundPatch( CSoundPatch **ppSound, IRestore *pRestore );
  596. virtual void OnRestore();
  597. //-----------------------------------------------------------------------------
  598. // external interface functions (from CSoundEnvelopeController)
  599. //-----------------------------------------------------------------------------
  600. public:
  601. // Start this sound playing, or reset if already playing with new volume/pitch
  602. void Play( CSoundPatch *pSound, float volume, float pitch, float flStartTime = 0 );
  603. void CommandAdd( CSoundPatch *pSound, float executeDeltaTime, soundcommands_t command, float commandTime, float commandValue );
  604. void SystemReset( void );
  605. void SystemUpdate( void );
  606. void CommandClear( CSoundPatch *pSound );
  607. void Shutdown( CSoundPatch *pSound );
  608. CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, const char *pSoundName );
  609. CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName,
  610. float attenuation );
  611. CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel, const char *pSoundName,
  612. soundlevel_t soundlevel );
  613. CSoundPatch *SoundCreate( IRecipientFilter& filter, int nEntIndex, const EmitSound_t &es );
  614. void SoundDestroy( CSoundPatch *pSound );
  615. void SoundChangePitch( CSoundPatch *pSound, float pitchTarget, float deltaTime );
  616. void SoundChangeVolume( CSoundPatch *pSound, float volumeTarget, float deltaTime );
  617. void SoundFadeOut( CSoundPatch *pSound, float deltaTime, bool destroyOnFadeout );
  618. float SoundGetPitch( CSoundPatch *pSound );
  619. float SoundGetVolume( CSoundPatch *pSound );
  620. string_t SoundGetName( CSoundPatch *pSound ) { return pSound->GetName(); }
  621. void SoundSetCloseCaptionDuration( CSoundPatch *pSound, float flDuration ) { pSound->SetCloseCaptionDuration(flDuration); }
  622. float SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopePoint_t *points, int numPoints );
  623. float SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopeDescription_t *envelope );
  624. void CheckLoopingSoundsForPlayer( CBasePlayer *pPlayer );
  625. // Inserts the command into the list, sorted by time
  626. void CommandInsert( SoundCommand_t *pCommand );
  627. #ifdef CLIENT_DLL
  628. // CAutoClientSystem
  629. virtual void Update( float frametime )
  630. {
  631. SystemUpdate();
  632. }
  633. #else
  634. virtual void PreClientUpdate()
  635. {
  636. SystemUpdate();
  637. }
  638. #endif
  639. virtual void LevelShutdownPreEntity()
  640. {
  641. SystemReset();
  642. }
  643. private:
  644. CUtlVector<CSoundPatch *> m_soundList;
  645. CUtlPriorityQueue<SoundCommand_t *> m_commandList;
  646. float m_flLastTime;
  647. };
  648. // Execute a command from the list
  649. // currently only 3 commands
  650. // UNDONE: Add start command?
  651. void CSoundControllerImp::ProcessCommand( SoundCommand_t *pCmd )
  652. {
  653. switch( pCmd->m_command )
  654. {
  655. case SOUNDCTRL_CHANGE_VOLUME:
  656. pCmd->m_pPatch->ChangeVolume( pCmd->m_value, pCmd->m_deltaTime );
  657. break;
  658. case SOUNDCTRL_CHANGE_PITCH:
  659. pCmd->m_pPatch->ChangePitch( pCmd->m_value, pCmd->m_deltaTime );
  660. break;
  661. case SOUNDCTRL_STOP:
  662. pCmd->m_pPatch->Shutdown();
  663. break;
  664. case SOUNDCTRL_DESTROY:
  665. RemoveFromList( pCmd->m_pPatch );
  666. delete pCmd->m_pPatch;
  667. pCmd->m_pPatch = NULL;
  668. break;
  669. }
  670. }
  671. //-----------------------------------------------------------------------------
  672. // Purpose: Remove this sound from the sound list & shutdown (not in external interface)
  673. // Input : *pSound - patch to remove
  674. //-----------------------------------------------------------------------------
  675. void CSoundControllerImp::RemoveFromList( CSoundPatch *pSound )
  676. {
  677. m_soundList.FindAndRemove( pSound );
  678. pSound->Shutdown();
  679. }
  680. //-----------------------------------------------------------------------------
  681. // Start this sound playing, or reset if already playing with new volume/pitch
  682. //-----------------------------------------------------------------------------
  683. void CSoundControllerImp::Play( CSoundPatch *pSound, float volume, float pitch, float flStartTime )
  684. {
  685. // reset the vars
  686. pSound->Reset();
  687. pSound->ChangeVolume( volume, 0 );
  688. pSound->ChangePitch( pitch, 0 );
  689. if ( pSound->IsPlaying() )
  690. {
  691. // remove any previous commands in the queue
  692. CommandClear( pSound );
  693. }
  694. else
  695. {
  696. m_soundList.AddToTail( pSound );
  697. pSound->StartSound( flStartTime );
  698. }
  699. }
  700. //-----------------------------------------------------------------------------
  701. // Inserts the command into the list, sorted by time
  702. //-----------------------------------------------------------------------------
  703. void CSoundControllerImp::CommandInsert( SoundCommand_t *pCommand )
  704. {
  705. m_commandList.Insert( pCommand );
  706. }
  707. //-----------------------------------------------------------------------------
  708. // Purpose: puts a command into the queue
  709. // Input : *pSound - patch this command affects
  710. // executeDeltaTime - relative time to execute this command
  711. // command - command to execute (SOUNDCTRL_*)
  712. // commandTime - commands have 2 parameters, a time and a value
  713. // value -
  714. // Output : void
  715. //-----------------------------------------------------------------------------
  716. void CSoundControllerImp::CommandAdd( CSoundPatch *pSound, float executeDeltaTime, soundcommands_t command, float commandTime, float commandValue )
  717. {
  718. SoundCommand_t *pCommand = new SoundCommand_t( pSound, g_pEffects->Time() + executeDeltaTime, command, commandTime, commandValue );
  719. CommandInsert( pCommand );
  720. }
  721. // Reset the whole system (level change, etc.)
  722. void CSoundControllerImp::SystemReset( void )
  723. {
  724. for ( int i = m_soundList.Count()-1; i >=0; i-- )
  725. {
  726. CSoundPatch *pNode = m_soundList[i];
  727. // shutdown all active sounds
  728. pNode->Shutdown();
  729. }
  730. // clear the list
  731. m_soundList.Purge();
  732. // clear the command queue
  733. m_commandList.RemoveAll();
  734. }
  735. //-----------------------------------------------------------------------------
  736. // Purpose: Update the active sounds, dequeue any events and move the ramps
  737. //-----------------------------------------------------------------------------
  738. void CSoundControllerImp::SystemUpdate( void )
  739. {
  740. VPROF( "CSoundControllerImp::SystemUpdate" );
  741. float time = g_pEffects->Time();
  742. float deltaTime = time - m_flLastTime;
  743. // handle clock resets
  744. if ( deltaTime < 0 )
  745. deltaTime = 0;
  746. m_flLastTime = time;
  747. {
  748. VPROF( "CSoundControllerImp::SystemUpdate:processcommandlist" );
  749. while ( m_commandList.Count() )
  750. {
  751. SoundCommand_t *pCmd = m_commandList.ElementAtHead();
  752. // Commands are sorted by time.
  753. // process any that should occur by the current time
  754. if ( time >= pCmd->m_time )
  755. {
  756. m_commandList.RemoveAtHead();
  757. ProcessCommand( pCmd );
  758. delete pCmd;
  759. }
  760. else
  761. {
  762. break;
  763. }
  764. }
  765. }
  766. // NOTE: Because this loop goes from the end to the beginning
  767. // we can fast remove inside it without breaking the indexing
  768. {
  769. VPROF( "CSoundControllerImp::SystemUpdate:removesounds" );
  770. for ( int i = m_soundList.Count()-1; i >=0; i-- )
  771. {
  772. CSoundPatch *pNode = m_soundList[i];
  773. if ( !pNode->Update( time, deltaTime ) )
  774. {
  775. pNode->Reset();
  776. m_soundList.FastRemove( i );
  777. }
  778. }
  779. }
  780. }
  781. // Remove any envelope commands from the list (dynamically changing envelope)
  782. void CSoundControllerImp::CommandClear( CSoundPatch *pSound )
  783. {
  784. for ( int i = m_commandList.Count()-1; i >= 0; i-- )
  785. {
  786. SoundCommand_t *pCmd = m_commandList.Element( i );
  787. if ( pCmd->m_pPatch == pSound )
  788. {
  789. m_commandList.RemoveAt(i);
  790. delete pCmd;
  791. }
  792. }
  793. }
  794. //-----------------------------------------------------------------------------
  795. // Saves the sound patch + associated commands
  796. //-----------------------------------------------------------------------------
  797. void CSoundControllerImp::SaveSoundPatch( CSoundPatch *pSoundPatch, ISave *pSave )
  798. {
  799. int i;
  800. // Write out the sound patch
  801. pSave->StartBlock();
  802. pSave->WriteAll( pSoundPatch );
  803. pSave->EndBlock();
  804. // Count the number of commands that refer to the sound patch
  805. int nCount = 0;
  806. for ( i = m_commandList.Count()-1; i >= 0; i-- )
  807. {
  808. SoundCommand_t *pCmd = m_commandList.Element( i );
  809. if ( pCmd->m_pPatch == pSoundPatch )
  810. {
  811. nCount++;
  812. }
  813. }
  814. // Write out the number of commands, followed by each command itself
  815. pSave->StartBlock();
  816. pSave->WriteInt( &nCount );
  817. for ( i = m_commandList.Count()-1; i >= 0; i-- )
  818. {
  819. SoundCommand_t *pCmd = m_commandList.Element( i );
  820. if ( pCmd->m_pPatch == pSoundPatch )
  821. {
  822. pSave->StartBlock();
  823. pSave->WriteAll( pCmd );
  824. pSave->EndBlock();
  825. }
  826. }
  827. pSave->EndBlock();
  828. }
  829. //-----------------------------------------------------------------------------
  830. // Restores the sound patch + associated commands
  831. //-----------------------------------------------------------------------------
  832. void CSoundControllerImp::RestoreSoundPatch( CSoundPatch **ppSoundPatch, IRestore *pRestore )
  833. {
  834. CSoundPatch *pPatch = new CSoundPatch;
  835. // read the sound patch data from the memory block
  836. pRestore->StartBlock();
  837. bool bOk = ( pRestore->ReadAll( pPatch ) != 0 );
  838. pRestore->EndBlock();
  839. bOk = (bOk && pPatch->IsPlaying()) ? true : false;
  840. if (bOk)
  841. {
  842. m_soundList.AddToTail( pPatch );
  843. }
  844. // Count the number of commands that refer to the sound patch
  845. pRestore->StartBlock();
  846. if ( bOk )
  847. {
  848. int nCount;
  849. pRestore->ReadInt( &nCount );
  850. while ( --nCount >= 0 )
  851. {
  852. SoundCommand_t *pCommand = new SoundCommand_t;
  853. pRestore->StartBlock();
  854. if ( pRestore->ReadAll( pCommand ) )
  855. {
  856. pCommand->m_pPatch = pPatch;
  857. CommandInsert( pCommand );
  858. }
  859. pRestore->EndBlock();
  860. }
  861. }
  862. pRestore->EndBlock();
  863. *ppSoundPatch = pPatch;
  864. }
  865. //-----------------------------------------------------------------------------
  866. // Purpose: immediately stop playing this sound
  867. // Input : *pSound - Patch to shut down
  868. //-----------------------------------------------------------------------------
  869. void CSoundControllerImp::Shutdown( CSoundPatch *pSound )
  870. {
  871. if ( !pSound )
  872. return;
  873. pSound->Shutdown();
  874. CommandClear( pSound );
  875. RemoveFromList( pSound );
  876. }
  877. CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, const char *pSoundName )
  878. {
  879. #ifdef CLIENT_DLL
  880. if ( GameRules() )
  881. {
  882. pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName );
  883. }
  884. #endif
  885. CSoundPatch *pSound = new CSoundPatch;
  886. // FIXME: This is done so we don't have to futz with the public interface
  887. EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
  888. pSound->Init( &filter, hEnt.Get(), CHAN_AUTO, pSoundName, SNDLVL_NORM );
  889. return pSound;
  890. }
  891. CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel,
  892. const char *pSoundName, float attenuation )
  893. {
  894. #ifdef CLIENT_DLL
  895. if ( GameRules() )
  896. {
  897. pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName );
  898. }
  899. #endif
  900. CSoundPatch *pSound = new CSoundPatch;
  901. EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
  902. pSound->Init( &filter, hEnt.Get(), channel, pSoundName, ATTN_TO_SNDLVL( attenuation ) );
  903. return pSound;
  904. }
  905. CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, int channel,
  906. const char *pSoundName, soundlevel_t soundlevel )
  907. {
  908. #ifdef CLIENT_DLL
  909. if ( GameRules() )
  910. {
  911. pSoundName = GameRules()->TranslateEffectForVisionFilter( "sounds", pSoundName );
  912. }
  913. #endif
  914. CSoundPatch *pSound = new CSoundPatch;
  915. EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
  916. pSound->Init( &filter, hEnt.Get(), channel, pSoundName, soundlevel );
  917. return pSound;
  918. }
  919. CSoundPatch *CSoundControllerImp::SoundCreate( IRecipientFilter& filter, int nEntIndex, const EmitSound_t &es )
  920. {
  921. CSoundPatch *pSound = new CSoundPatch;
  922. // FIXME: This is done so we don't have to futz with the public interface
  923. EHANDLE hEnt = (nEntIndex != -1) ? g_pEntityList->GetNetworkableHandle( nEntIndex ) : NULL;
  924. pSound->Init( &filter, hEnt.Get(), es.m_nChannel, es.m_pSoundName, es.m_SoundLevel );
  925. pSound->ChangeVolume( es.m_flVolume, 0 );
  926. pSound->ChangePitch( es.m_nPitch, 0 );
  927. if ( es.m_nFlags & SND_SHOULDPAUSE )
  928. {
  929. pSound->SetBaseFlags( SND_SHOULDPAUSE );
  930. }
  931. return pSound;
  932. }
  933. void CSoundControllerImp::SoundDestroy( CSoundPatch *pSound )
  934. {
  935. if ( !pSound )
  936. return;
  937. Shutdown( pSound );
  938. delete pSound;
  939. }
  940. void CSoundControllerImp::SoundChangePitch( CSoundPatch *pSound, float pitchTarget, float deltaTime )
  941. {
  942. pSound->ChangePitch( pitchTarget, deltaTime );
  943. }
  944. void CSoundControllerImp::SoundChangeVolume( CSoundPatch *pSound, float volumeTarget, float deltaTime )
  945. {
  946. pSound->ChangeVolume( volumeTarget, deltaTime );
  947. }
  948. float CSoundControllerImp::SoundGetPitch( CSoundPatch *pSound )
  949. {
  950. return pSound->GetPitch();
  951. }
  952. float CSoundControllerImp::SoundGetVolume( CSoundPatch *pSound )
  953. {
  954. return pSound->GetVolume();
  955. }
  956. void CSoundControllerImp::SoundFadeOut( CSoundPatch *pSound, float deltaTime, bool destroyOnFadeout )
  957. {
  958. if ( destroyOnFadeout && (deltaTime == 0.0f) )
  959. {
  960. SoundDestroy( pSound );
  961. return;
  962. }
  963. pSound->FadeOut( deltaTime, destroyOnFadeout );
  964. if ( destroyOnFadeout )
  965. {
  966. CommandAdd( pSound, deltaTime, SOUNDCTRL_DESTROY, 0.0f, 0.0f );
  967. }
  968. }
  969. //-----------------------------------------------------------------------------
  970. // Purpose: Queue a list of envelope points into a sound patch's event list
  971. // Input : *pSound - The sound patch to be operated on
  972. // soundCommand - Type of operation the envelope describes
  973. // *points - List of enevelope points
  974. // numPoints - Number of points provided
  975. // Output : float - Returns the total duration of the envelope
  976. //-----------------------------------------------------------------------------
  977. float CSoundControllerImp::SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopePoint_t *points, int numPoints )
  978. {
  979. float amplitude = 0.0f;
  980. float duration = 0.0f;
  981. float totalDuration = 0.0f;
  982. Assert( points );
  983. // Clear out all previously acting commands
  984. CommandClear( pSound );
  985. // Evaluate and queue all points
  986. for ( int i = 0; i < numPoints; i++ )
  987. {
  988. // See if we're keeping our last amplitude for this new point
  989. if ( ( points[i].amplitudeMin != -1.0f ) || ( points[i].amplitudeMax != -1.0f ) )
  990. {
  991. amplitude = random->RandomFloat( points[i].amplitudeMin, points[i].amplitudeMax );
  992. }
  993. else if ( i == 0 )
  994. {
  995. // Can't do this on the first entry
  996. Msg( "Invalid starting amplitude value in envelope! (Cannot be -1)\n" );
  997. }
  998. // See if we're keeping our last duration for this new point
  999. if ( ( points[i].durationMin != -1.0f ) || ( points[i].durationMax != -1.0f ) )
  1000. {
  1001. duration = random->RandomFloat( points[i].durationMin, points[i].durationMax );
  1002. //duration = points[i].durationMin;
  1003. }
  1004. else if ( i == 0 )
  1005. {
  1006. // Can't do this on the first entry
  1007. Msg( "Invalid starting duration value in envelope! (Cannot be -1)\n" );
  1008. }
  1009. // Queue the command
  1010. CommandAdd( pSound, totalDuration, soundCommand, duration, amplitude );
  1011. // Tack this command's duration onto the running duration
  1012. totalDuration += duration;
  1013. }
  1014. return totalDuration;
  1015. }
  1016. //-----------------------------------------------------------------------------
  1017. // Purpose: Queue a list of envelope points into a sound patch's event list
  1018. // Input : *pSound - The sound patch to be operated on
  1019. // soundCommand - Type of operation the envelope describes
  1020. // *envelope - The envelope description to be queued
  1021. // Output : float - Returns the total duration of the envelope
  1022. //-----------------------------------------------------------------------------
  1023. float CSoundControllerImp::SoundPlayEnvelope( CSoundPatch *pSound, soundcommands_t soundCommand, envelopeDescription_t *envelope )
  1024. {
  1025. return SoundPlayEnvelope( pSound, soundCommand, envelope->pPoints, envelope->nNumPoints );
  1026. }
  1027. //-----------------------------------------------------------------------------
  1028. // Purpose: Looping sounds are often started in entity spawn/activate functions.
  1029. // In singleplayer, the player's not ready to receive sounds then, so restart
  1030. // and SoundPatches that are active and have no receivers.
  1031. //-----------------------------------------------------------------------------
  1032. void CSoundControllerImp::CheckLoopingSoundsForPlayer( CBasePlayer *pPlayer )
  1033. {
  1034. for ( int i = m_soundList.Count()-1; i >=0; i-- )
  1035. {
  1036. CSoundPatch *pNode = m_soundList[i];
  1037. pNode->AddPlayerPost( pPlayer );
  1038. }
  1039. }
  1040. //-----------------------------------------------------------------------------
  1041. // Purpose: Resumes saved soundpatches
  1042. //-----------------------------------------------------------------------------
  1043. void CSoundControllerImp::OnRestore()
  1044. {
  1045. for ( int i = m_soundList.Count()-1; i >=0; i-- )
  1046. {
  1047. CSoundPatch *pNode = m_soundList[i];
  1048. if ( pNode && pNode->IsPlaying() )
  1049. {
  1050. pNode->ResumeSound();
  1051. }
  1052. }
  1053. }
  1054. //-----------------------------------------------------------------------------
  1055. // Singleton accessors
  1056. //-----------------------------------------------------------------------------
  1057. static CSoundControllerImp g_Controller;
  1058. CSoundEnvelopeController &CSoundEnvelopeController::GetController( void )
  1059. {
  1060. return g_Controller;
  1061. }
  1062. //-----------------------------------------------------------------------------
  1063. // Queues up sound patches to save/load
  1064. //-----------------------------------------------------------------------------
  1065. class CSoundPatchSaveRestoreOps : public CClassPtrSaveRestoreOps
  1066. {
  1067. public:
  1068. virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
  1069. {
  1070. pSave->StartBlock();
  1071. int nSoundPatchCount = fieldInfo.pTypeDesc->fieldSize;
  1072. CSoundPatch **ppSoundPatch = (CSoundPatch**)fieldInfo.pField;
  1073. while ( --nSoundPatchCount >= 0 )
  1074. {
  1075. // Write out commands associated with this sound patch
  1076. g_Controller.SaveSoundPatch( *ppSoundPatch, pSave );
  1077. ++ppSoundPatch;
  1078. }
  1079. pSave->EndBlock();
  1080. }
  1081. virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
  1082. {
  1083. pRestore->StartBlock();
  1084. int nSoundPatchCount = fieldInfo.pTypeDesc->fieldSize;
  1085. CSoundPatch **ppSoundPatch = (CSoundPatch**)fieldInfo.pField;
  1086. while ( --nSoundPatchCount >= 0 )
  1087. {
  1088. // Write out commands associated with this sound patch
  1089. g_Controller.RestoreSoundPatch( ppSoundPatch, pRestore );
  1090. ++ppSoundPatch;
  1091. }
  1092. pRestore->EndBlock();
  1093. }
  1094. };
  1095. static CSoundPatchSaveRestoreOps s_SoundPatchSaveRestoreOps;
  1096. ISaveRestoreOps *GetSoundSaveRestoreOps( )
  1097. {
  1098. return &s_SoundPatchSaveRestoreOps;
  1099. }