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.

8401 lines
220 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Main control for any streaming sound output device.
  4. //
  5. //===========================================================================//
  6. #include "audio_pch.h"
  7. #include "const.h"
  8. #include "cdll_int.h"
  9. #include "client_class.h"
  10. #include "icliententitylist.h"
  11. #include "tier0/vcrmode.h"
  12. #include "con_nprint.h"
  13. #include "tier0/icommandline.h"
  14. #include "vox_private.h"
  15. #include "../../traceinit.h"
  16. #include "../../cmd.h"
  17. #include "toolframework/itoolframework.h"
  18. #include "vstdlib/random.h"
  19. #include "vstdlib/jobthread.h"
  20. #include "vaudio/ivaudio.h"
  21. #include "../../client.h"
  22. #include "../../cl_main.h"
  23. #include "utldict.h"
  24. #include "mempool.h"
  25. #include "../../enginetrace.h" // for traceline
  26. #include "../../public/bspflags.h" // for traceline
  27. #include "../../public/gametrace.h" // for traceline
  28. #include "vphysics_interface.h" // for surface props
  29. #include "../../ispatialpartitioninternal.h" // for entity enumerator
  30. #include "../../debugoverlay.h"
  31. #include "icliententity.h"
  32. #include "../../cmodel_engine.h"
  33. #include "../../staticpropmgr.h"
  34. #include "../../server.h"
  35. #include "edict.h"
  36. #include "../../pure_server.h"
  37. #include "filesystem/IQueuedLoader.h"
  38. #include "voice.h"
  39. #if defined( _X360 )
  40. #include "xbox/xbox_console.h"
  41. #include "xmp.h"
  42. #endif
  43. #include "replay/iclientreplaycontext.h"
  44. #include "replay/ireplaymovierenderer.h"
  45. #include "video/ivideoservices.h"
  46. extern IVideoServices *g_pVideo;
  47. /*
  48. #include "gl_model_private.h"
  49. #include "world.h"
  50. #include "vphysics_interface.h"
  51. #include "client_class.h"
  52. #include "server_class.h"
  53. */
  54. // memdbgon must be the last include file in a .cpp file!!!
  55. #include "tier0/memdbgon.h"
  56. ///////////////////////////////////
  57. // DEBUGGING
  58. //
  59. // Turn this on to print channel output msgs.
  60. //
  61. //#define DEBUG_CHANNELS
  62. #define SNDLVL_TO_DIST_MULT( sndlvl ) ( sndlvl ? ((pow( 10.0f, snd_refdb.GetFloat() / 20 ) / pow( 10.0f, (float)sndlvl / 20 )) / snd_refdist.GetFloat()) : 0 )
  63. #define DIST_MULT_TO_SNDLVL( dist_mult ) (soundlevel_t)(int)( dist_mult ? ( 20 * log10( pow( 10.0f, snd_refdb.GetFloat() / 20 ) / (dist_mult * snd_refdist.GetFloat()) ) ) : 0 )
  64. extern ConVar dsp_spatial;
  65. extern IPhysicsSurfaceProps *physprop;
  66. extern bool IsReplayRendering();
  67. static void S_Play( const CCommand &args );
  68. static void S_PlayVol( const CCommand &args );
  69. void S_SoundList(void);
  70. static void S_Say ( const CCommand &args );
  71. void S_Update_(float);
  72. void S_StopAllSounds(bool clear);
  73. void S_StopAllSoundsC(void);
  74. void S_ShutdownMixThread();
  75. const char *GetClientClassname( SoundSource soundsource );
  76. float SND_GetGainObscured( channel_t *ch, bool fplayersound, bool flooping, bool bAttenuated );
  77. void DSP_ChangePresetValue( int idsp, int channel, int iproc, float value );
  78. bool DSP_CheckDspAutoEnabled( void );
  79. void DSP_SetDspAuto( int dsp_preset );
  80. float dB_To_Radius ( float db );
  81. int dsp_room_GetInt ( void );
  82. bool MXR_LoadAllSoundMixers( void );
  83. void MXR_ReleaseMemory( void );
  84. int MXR_GetMixGroupListFromDirName( const char *pDirname, byte *pList, int listMax );
  85. void MXR_GetMixGroupFromSoundsource( channel_t *pchan, SoundSource soundsource, soundlevel_t soundlevel);
  86. float MXR_GetVolFromMixGroup( int rgmixgroupid[8], int *plast_mixgroupid );
  87. char *MXR_GetGroupnameFromId( int mixgroupid );
  88. int MXR_GetMixgroupFromName( const char *pszgroupname );
  89. void MXR_DebugShowMixVolumes( void );
  90. #ifdef _DEBUG
  91. static void MXR_DebugSetMixGroupVolume( const CCommand &args );
  92. #endif //_DEBUG
  93. void MXR_UpdateAllDuckerVolumes( void );
  94. void ChannelSetVolTargets( channel_t *pch, int *pvolumes, int ivol_offset, int cvol );
  95. void ChannelUpdateVolXfade( channel_t *pch );
  96. void ChannelClearVolumes( channel_t *pch );
  97. float VOX_GetChanVol(channel_t *ch);
  98. void ConvertListenerVectorTo2D( Vector *pvforward, Vector *pvright );
  99. int ChannelGetMaxVol( channel_t *pch );
  100. // Forceably ends voice tweak mode (only occurs during snd_restart
  101. void VoiceTweak_EndVoiceTweakMode();
  102. bool VoiceTweak_IsStillTweaking();
  103. // Only does anything for voice tweak channel so if view entity changes it doesn't fade out to zero volume
  104. void Voice_Spatialize( channel_t *channel );
  105. // =======================================================================
  106. // Internal sound data & structures
  107. // =======================================================================
  108. channel_t channels[MAX_CHANNELS];
  109. int total_channels;
  110. CActiveChannels g_ActiveChannels;
  111. static double g_LastSoundFrame = 0.0f; // last full frame of sound
  112. static double g_LastMixTime = 0.0f; // last time we did mixing
  113. static float g_EstFrameTime = 0.1f; // estimated frame time running average
  114. // x360 override to fade out game music when the user is playing music through the dashboard
  115. static float g_DashboardMusicMixValue = 1.0f;
  116. static float g_DashboardMusicMixTarget = 1.0f;
  117. const float g_DashboardMusicFadeRate = 0.5f; // Fades one half full-scale volume per second (two seconds for complete fadeout)
  118. // sound mixers
  119. int g_csoundmixers = 0; // total number of soundmixers found
  120. int g_cgrouprules = 0; // total number of group rules found
  121. int g_cgroupclass = 0;
  122. // this is used to enable/disable music playback on x360 when the user selects his own soundtrack to play
  123. void S_EnableMusic( bool bEnable )
  124. {
  125. if ( bEnable )
  126. {
  127. g_DashboardMusicMixTarget = 1.0f;
  128. }
  129. else
  130. {
  131. g_DashboardMusicMixTarget = 0.0f;
  132. }
  133. }
  134. bool IsSoundSourceLocalPlayer( int soundsource )
  135. {
  136. if ( soundsource == SOUND_FROM_UI_PANEL )
  137. return true;
  138. return ( soundsource == g_pSoundServices->GetViewEntity() );
  139. }
  140. CThreadMutex g_SndMutex;
  141. #define THREAD_LOCK_SOUND() AUTO_LOCK( g_SndMutex )
  142. const int MASK_BLOCK_AUDIO = CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW;
  143. void CActiveChannels::Add( channel_t *pChannel )
  144. {
  145. Assert( pChannel->activeIndex == 0 );
  146. m_list[m_count] = pChannel - channels;
  147. m_count++;
  148. pChannel->activeIndex = m_count;
  149. }
  150. void CActiveChannels::Remove( channel_t *pChannel )
  151. {
  152. if ( pChannel->activeIndex == 0 )
  153. return;
  154. int activeIndex = pChannel->activeIndex - 1;
  155. Assert( activeIndex >= 0 && activeIndex < m_count );
  156. Assert( pChannel == &channels[m_list[activeIndex]] );
  157. m_count--;
  158. // Not the last one? Swap the last one with this one and fix its index
  159. if ( activeIndex < m_count )
  160. {
  161. m_list[activeIndex] = m_list[m_count];
  162. channels[m_list[activeIndex]].activeIndex = activeIndex+1;
  163. }
  164. pChannel->activeIndex = 0;
  165. }
  166. void CActiveChannels::GetActiveChannels( CChannelList &list )
  167. {
  168. list.m_count = m_count;
  169. if ( m_count )
  170. {
  171. Q_memcpy( list.m_list, m_list, sizeof(m_list[0])*m_count );
  172. }
  173. for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i )
  174. {
  175. paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i );
  176. if ( pSpecialBuffer->nSpecialDSP != 0 )
  177. {
  178. list.m_nSpecialDSPs.AddToTail( pSpecialBuffer->nSpecialDSP );
  179. }
  180. }
  181. list.m_hasSpeakerChannels = true;
  182. list.m_has11kChannels = true;
  183. list.m_has22kChannels = true;
  184. list.m_has44kChannels = true;
  185. list.m_hasDryChannels = true;
  186. }
  187. void CActiveChannels::Init()
  188. {
  189. m_count = 0;
  190. }
  191. bool snd_initialized = false;
  192. Vector listener_origin;
  193. static Vector listener_forward;
  194. Vector listener_right;
  195. static Vector listener_up;
  196. static bool s_bIsListenerUnderwater;
  197. static vec_t sound_nominal_clip_dist=SOUND_NORMAL_CLIP_DIST;
  198. // @TODO (toml 05-08-02): put this somewhere more reasonable
  199. vec_t S_GetNominalClipDist()
  200. {
  201. return sound_nominal_clip_dist;
  202. }
  203. int g_soundtime = 0; // sample PAIRS output since start
  204. int g_paintedtime = 0; // sample PAIRS mixed since start
  205. float g_ReplaySoundTimeFracAccumulator = 0.0f; // Used by replay
  206. float g_ClockSyncArray[NUM_CLOCK_SYNCS] = {0};
  207. int g_SoundClockPaintTime[NUM_CLOCK_SYNCS] = {0};
  208. // default 10ms
  209. ConVar snd_delay_sound_shift("snd_delay_sound_shift","0.01");
  210. // this forces the clock to resync on the next delayed/sync sound
  211. void S_SyncClockAdjust( clocksync_index_t syncIndex )
  212. {
  213. g_ClockSyncArray[syncIndex] = 0;
  214. g_SoundClockPaintTime[syncIndex] = 0;
  215. }
  216. float S_ComputeDelayForSoundtime( float soundtime, clocksync_index_t syncIndex )
  217. {
  218. // reset clock and return 0
  219. if ( g_ClockSyncArray[syncIndex] == 0 )
  220. {
  221. // Put the current time marker one tick back to impose a minimum delay on the first sample
  222. // this shifts the drift over so the sounds are more likely to delay (rather than skip)
  223. // over the burst
  224. // NOTE: The first sound after a sync MUST have a non-zero delay for the delay channel
  225. // detection logic to work (otherwise we keep resetting the clock)
  226. g_ClockSyncArray[syncIndex] = soundtime - host_state.interval_per_tick;
  227. g_SoundClockPaintTime[syncIndex] = g_paintedtime;
  228. }
  229. // how much time has passed in the game since we did a clock sync?
  230. float gameDeltaTime = soundtime - g_ClockSyncArray[syncIndex];
  231. // how many samples have been mixed since we did a clock sync?
  232. int paintedSamples = g_paintedtime - g_SoundClockPaintTime[syncIndex];
  233. int dmaSpeed = g_AudioDevice->DeviceDmaSpeed();
  234. int gameSamples = (gameDeltaTime * dmaSpeed);
  235. int delaySamples = gameSamples - paintedSamples;
  236. float delay = delaySamples / float(dmaSpeed);
  237. if ( gameDeltaTime < 0 || fabs(delay) > 0.500f )
  238. {
  239. // Note that the equations assume a correlation between game time and real time
  240. // some kind of clock error. This can happen with large host_timescale or when the
  241. // framerate hitches drastically (game time is a smaller clamped value wrt real time).
  242. // The current sync estimate has probably drifted due to this or some other problem, recompute.
  243. //Msg("Clock ERROR!: %.2f %.2f\n", gameDeltaTime, delay);
  244. S_SyncClockAdjust(syncIndex);
  245. return 0;
  246. }
  247. return delay + snd_delay_sound_shift.GetFloat();
  248. }
  249. static int s_buffers = 0;
  250. static int s_oldsampleOutCount = 0;
  251. static float s_lastsoundtime = 0.0f;
  252. bool s_bOnLoadScreen = false;
  253. static CClassMemoryPool< CSfxTable > s_SoundPool( MAX_SFX );
  254. struct SfxDictEntry
  255. {
  256. CSfxTable *pSfx;
  257. };
  258. static CUtlMap< FileNameHandle_t, SfxDictEntry > s_Sounds( 0, 0, DefLessFunc( FileNameHandle_t ) );
  259. class CDummySfx : public CSfxTable
  260. {
  261. public:
  262. virtual const char *getname()
  263. {
  264. return name;
  265. }
  266. void setname( const char *pName )
  267. {
  268. Q_strncpy( name, pName, sizeof( name ) );
  269. OnNameChanged(name);
  270. }
  271. private:
  272. char name[MAX_PATH];
  273. };
  274. static CDummySfx dummySfx;
  275. // returns true if ok to procede with TraceRay calls
  276. bool SND_IsInGame( void )
  277. {
  278. return cl.IsActive();
  279. }
  280. CSfxTable::CSfxTable()
  281. {
  282. m_namePoolIndex = s_Sounds.InvalidIndex();
  283. pSource = NULL;
  284. m_bUseErrorFilename = false;
  285. m_bIsUISound = false;
  286. m_bIsLateLoad = false;
  287. m_bMixGroupsCached = false;
  288. m_pDebugName = NULL;
  289. }
  290. void CSfxTable::SetNamePoolIndex( int index )
  291. {
  292. m_namePoolIndex = index;
  293. if ( m_namePoolIndex != s_Sounds.InvalidIndex() )
  294. {
  295. OnNameChanged(getname());
  296. }
  297. #ifdef _DEBUG
  298. m_pDebugName = strdup( getname() );
  299. #endif
  300. }
  301. void CSfxTable::OnNameChanged( const char *pName )
  302. {
  303. if ( pName && g_cgrouprules )
  304. {
  305. char szString[MAX_PATH];
  306. Q_strncpy( szString, pName, sizeof(szString) );
  307. Q_FixSlashes( szString, '/' );
  308. m_mixGroupCount = MXR_GetMixGroupListFromDirName( szString, m_mixGroupList, ARRAYSIZE(m_mixGroupList) );
  309. m_bMixGroupsCached = true;
  310. }
  311. }
  312. //-----------------------------------------------------------------------------
  313. // Purpose: Wrapper for sfxtable->getname()
  314. // Output : char const
  315. //-----------------------------------------------------------------------------
  316. const char *CSfxTable::getname()
  317. {
  318. if ( s_Sounds.InvalidIndex() != m_namePoolIndex )
  319. {
  320. char* pString = tmpstr512();
  321. if ( g_pFileSystem )
  322. g_pFileSystem->String( s_Sounds.Key( m_namePoolIndex ), pString, 512 );
  323. else
  324. {
  325. pString[0] = 0;
  326. }
  327. return pString;
  328. }
  329. return NULL;
  330. }
  331. FileNameHandle_t CSfxTable::GetFileNameHandle()
  332. {
  333. if ( s_Sounds.InvalidIndex() != m_namePoolIndex )
  334. {
  335. return s_Sounds.Key( m_namePoolIndex );
  336. }
  337. return NULL;
  338. }
  339. const char *CSfxTable::GetFileName()
  340. {
  341. if ( IsX360() && m_bUseErrorFilename )
  342. {
  343. // Redirecting error sounds to a valid empty wave, prevents a bad loading retry pattern during gameplay
  344. // which may event sounds skipped by preload, because they don't exist.
  345. return "common/null.wav";
  346. }
  347. const char *pName = getname();
  348. return pName ? PSkipSoundChars( pName ) : NULL;
  349. }
  350. bool CSfxTable::IsPrecachedSound()
  351. {
  352. const char *pName = getname();
  353. if ( sv.IsActive() )
  354. {
  355. // Server uses zero to mark invalid sounds
  356. return sv.LookupSoundIndex( pName ) != 0 ? true : false;
  357. }
  358. // Client uses -1
  359. // WE SHOULD FIX THIS!!!
  360. return ( cl.LookupSoundIndex( pName ) != -1 ) ? true : false;
  361. }
  362. float g_DuckScale = 1.0f;
  363. // Structure used for fading in and out client sound volume.
  364. typedef struct
  365. {
  366. float initial_percent;
  367. // How far to adjust client's volume down by.
  368. float percent;
  369. // GetHostTime() when we started adjusting volume
  370. float starttime;
  371. // # of seconds to get to faded out state
  372. float fadeouttime;
  373. // # of seconds to hold
  374. float holdtime;
  375. // # of seconds to restore
  376. float fadeintime;
  377. } soundfade_t;
  378. static soundfade_t soundfade; // Client sound fading singleton object
  379. // 0)headphones 2)stereo speakers 4)quad 5)5point1
  380. // autodetected from windows settings
  381. ConVar snd_surround( "snd_surround_speakers", "-1", FCVAR_INTERNAL_USE );
  382. ConVar snd_legacy_surround( "snd_legacy_surround", "0", FCVAR_ARCHIVE );
  383. ConVar snd_noextraupdate( "snd_noextraupdate", "0" );
  384. ConVar snd_show( "snd_show", "0", FCVAR_CHEAT, "Show sounds info" );
  385. ConVar snd_visualize ("snd_visualize", "0", FCVAR_CHEAT, "Show sounds location in world" );
  386. ConVar snd_pitchquality( "snd_pitchquality", "1", FCVAR_ARCHIVE ); // 1) use high quality pitch shifters
  387. // master volume
  388. static ConVar volume( "volume", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Sound volume", true, 0.0f, true, 1.0f );
  389. // user configurable music volume
  390. ConVar snd_musicvolume( "snd_musicvolume", "1.0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "Music volume", true, 0.0f, true, 1.0f );
  391. ConVar snd_mixahead( "snd_mixahead", "0.1", FCVAR_ARCHIVE );
  392. ConVar snd_mix_async( "snd_mix_async", "0" );
  393. #ifdef _DEBUG
  394. static ConCommand snd_mixvol("snd_mixvol", MXR_DebugSetMixGroupVolume, "Set named Mixgroup to mix volume.");
  395. #endif
  396. // vaudio DLL
  397. IVAudio *vaudio = NULL;
  398. CSysModule *g_pVAudioModule = NULL;
  399. //-----------------------------------------------------------------------------
  400. // Resource loading for sound
  401. //-----------------------------------------------------------------------------
  402. class CResourcePreloadSound : public CResourcePreload
  403. {
  404. public:
  405. CResourcePreloadSound() : m_SoundNames( 0, 0, true )
  406. {
  407. }
  408. virtual bool CreateResource( const char *pName )
  409. {
  410. CSfxTable *pSfx = S_PrecacheSound( pName );
  411. if ( !pSfx )
  412. {
  413. return false;
  414. }
  415. m_SoundNames.AddString( pSfx->GetFileName() );
  416. return true;
  417. }
  418. virtual void PurgeUnreferencedResources()
  419. {
  420. bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0;
  421. for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
  422. {
  423. // the master sound table grows forever
  424. // remove sound sources from the master sound table that were not in the preload list
  425. CSfxTable *pSfx = s_Sounds[i].pSfx;
  426. if ( pSfx && pSfx->pSource )
  427. {
  428. if ( pSfx->m_bIsUISound )
  429. {
  430. // never purge ui
  431. continue;
  432. }
  433. UtlSymId_t symbol = m_SoundNames.Find( pSfx->GetFileName() );
  434. if ( symbol == UTL_INVAL_SYMBOL )
  435. {
  436. // sound was not part of preload, purge it
  437. if ( bSpew )
  438. {
  439. Msg( "CResourcePreloadSound: Purging: %s\n", pSfx->GetFileName() );
  440. }
  441. pSfx->pSource->CacheUnload();
  442. delete pSfx->pSource;
  443. pSfx->pSource = NULL;
  444. }
  445. }
  446. }
  447. m_SoundNames.RemoveAll();
  448. if ( !g_pQueuedLoader->IsSameMapLoading() )
  449. {
  450. wavedatacache->Flush();
  451. }
  452. }
  453. virtual void PurgeAll()
  454. {
  455. bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0;
  456. for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
  457. {
  458. // the master sound table grows forever
  459. // remove sound sources from the master sound table that were not in the preload list
  460. CSfxTable *pSfx = s_Sounds[i].pSfx;
  461. if ( pSfx && pSfx->pSource )
  462. {
  463. if ( pSfx->m_bIsUISound )
  464. {
  465. // never purge ui
  466. if ( bSpew )
  467. {
  468. Msg( "CResourcePreloadSound: Skipping: %s\n", pSfx->GetFileName() );
  469. }
  470. continue;
  471. }
  472. // sound was not part of preload, purge it
  473. if ( bSpew )
  474. {
  475. Msg( "CResourcePreloadSound: Purging: %s\n", pSfx->GetFileName() );
  476. }
  477. pSfx->pSource->CacheUnload();
  478. delete pSfx->pSource;
  479. pSfx->pSource = NULL;
  480. }
  481. }
  482. m_SoundNames.RemoveAll();
  483. wavedatacache->Flush();
  484. }
  485. private:
  486. CUtlSymbolTable m_SoundNames;
  487. };
  488. static CResourcePreloadSound s_ResourcePreloadSound;
  489. //-----------------------------------------------------------------------------
  490. // Purpose:
  491. // Output : float
  492. //-----------------------------------------------------------------------------
  493. float S_GetMasterVolume( void )
  494. {
  495. float scale = 1.0f;
  496. if ( soundfade.percent != 0 )
  497. {
  498. scale = clamp( (float)soundfade.percent / 100.0f, 0.0f, 1.0f );
  499. scale = 1.0f - scale;
  500. }
  501. return volume.GetFloat() * scale;
  502. }
  503. void S_SoundInfo_f(void)
  504. {
  505. if ( !g_AudioDevice->IsActive() )
  506. {
  507. Msg( "Sound system not started\n" );
  508. return;
  509. }
  510. Msg( "Sound Device: %s\n", g_AudioDevice->DeviceName() );
  511. Msg( " Channels: %d\n", g_AudioDevice->DeviceChannels() );
  512. Msg( " Samples: %d\n", g_AudioDevice->DeviceSampleCount() );
  513. Msg( " Bits/Sample: %d\n", g_AudioDevice->DeviceSampleBits() );
  514. Msg( " Rate: %d\n", g_AudioDevice->DeviceDmaSpeed() );
  515. Msg( "total_channels: %d\n", total_channels);
  516. if ( IsX360() )
  517. {
  518. // dump a glimpse of the mixing state
  519. CChannelList list;
  520. g_ActiveChannels.GetActiveChannels( list );
  521. Msg( "\nActive Channels: (%d)\n", list.Count() );
  522. for ( int i = 0; i < list.Count(); i++ )
  523. {
  524. channel_t *pChannel = list.GetChannel(i);
  525. Msg( "%s (Mixer: 0x%p)\n", pChannel->sfx->GetFileName(), pChannel->pMixer );
  526. }
  527. }
  528. }
  529. /*
  530. ================
  531. S_Startup
  532. ================
  533. */
  534. void S_Startup( void )
  535. {
  536. if ( !snd_initialized )
  537. return;
  538. if ( !g_AudioDevice || g_AudioDevice == Audio_GetNullDevice() )
  539. {
  540. g_AudioDevice = IAudioDevice::AutoDetectInit( CommandLine()->CheckParm( "-wavonly" ) != 0 );
  541. if ( !g_AudioDevice )
  542. {
  543. Error( "Unable to init audio" );
  544. }
  545. }
  546. }
  547. static ConCommand play("play", S_Play, "Play a sound.", FCVAR_SERVER_CAN_EXECUTE );
  548. static ConCommand playflush( "playflush", S_Play, "Play a sound, reloading from disk in case of changes." );
  549. static ConCommand playvol( "playvol", S_PlayVol, "Play a sound at a specified volume." );
  550. static ConCommand speak( "speak", S_Say, "Play a constructed sentence." );
  551. static ConCommand stopsound( "stopsound", S_StopAllSoundsC, 0, FCVAR_CHEAT); // Marked cheat because it gives an advantage to players minimising ambient noise.
  552. static ConCommand soundlist( "soundlist", S_SoundList, "List all known sounds." );
  553. static ConCommand soundinfo( "soundinfo", S_SoundInfo_f, "Describe the current sound device." );
  554. bool IsValidSampleRate( int rate )
  555. {
  556. return rate == SOUND_11k || rate == SOUND_22k || rate == SOUND_44k;
  557. }
  558. void VAudioInit()
  559. {
  560. if ( IsPC() )
  561. {
  562. if ( !IsPosix() )
  563. {
  564. // vaudio_miles.dll will load this...
  565. g_pFileSystem->GetLocalCopy( "mss32.dll" );
  566. }
  567. g_pVAudioModule = FileSystem_LoadModule( "vaudio_miles" );
  568. if ( g_pVAudioModule )
  569. {
  570. CreateInterfaceFn vaudioFactory = Sys_GetFactory( g_pVAudioModule );
  571. vaudio = (IVAudio *)vaudioFactory( VAUDIO_INTERFACE_VERSION, NULL );
  572. }
  573. }
  574. }
  575. /*
  576. ================
  577. S_Init
  578. ================
  579. */
  580. void S_Init( void )
  581. {
  582. if ( sv.IsDedicated() && !CommandLine()->CheckParm( "-forcesound" ) )
  583. return;
  584. DevMsg( "Sound Initialization: Start\n" );
  585. // KDB: init sentence array
  586. TRACEINIT( VOX_Init(), VOX_Shutdown() );
  587. VAudioInit();
  588. if ( CommandLine()->CheckParm( "-nosound" ) )
  589. {
  590. g_AudioDevice = Audio_GetNullDevice();
  591. TRACEINIT( audiosourcecache->Init( host_parms.memsize >> 2 ), audiosourcecache->Shutdown() );
  592. return;
  593. }
  594. snd_initialized = true;
  595. g_ActiveChannels.Init();
  596. S_Startup();
  597. MIX_InitAllPaintbuffers();
  598. SND_InitScaletable();
  599. MXR_LoadAllSoundMixers();
  600. S_StopAllSounds( true );
  601. TRACEINIT( audiosourcecache->Init( host_parms.memsize >> 2 ), audiosourcecache->Shutdown() );
  602. AllocDsps( true );
  603. if ( IsX360() )
  604. {
  605. g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_SOUND, &s_ResourcePreloadSound );
  606. }
  607. DevMsg( "Sound Initialization: Finish, Sampling Rate: %i\n", g_AudioDevice->DeviceDmaSpeed() );
  608. #ifdef _X360
  609. BOOL bPlaybackControl;
  610. // get initial state of the x360 media player
  611. if ( XMPTitleHasPlaybackControl( &bPlaybackControl ) == ERROR_SUCCESS )
  612. {
  613. S_EnableMusic(bPlaybackControl!=0);
  614. }
  615. Assert( g_pVideo != NULL );
  616. if ( g_pVideo != NULL )
  617. {
  618. if ( g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::HOOK_X_AUDIO, NULL ) != VideoResult::SUCCESS )
  619. {
  620. Assert( 0 );
  621. }
  622. }
  623. #endif
  624. }
  625. // =======================================================================
  626. // Shutdown sound engine
  627. // =======================================================================
  628. void S_Shutdown(void)
  629. {
  630. #if !defined( _X360 )
  631. if ( VoiceTweak_IsStillTweaking() )
  632. {
  633. VoiceTweak_EndVoiceTweakMode();
  634. }
  635. #endif
  636. S_StopAllSounds( true );
  637. S_ShutdownMixThread();
  638. TRACESHUTDOWN( audiosourcecache->Shutdown() );
  639. SNDDMA_Shutdown();
  640. for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
  641. {
  642. if ( s_Sounds[i].pSfx )
  643. {
  644. delete s_Sounds[i].pSfx->pSource;
  645. s_Sounds[i].pSfx->pSource = NULL;
  646. }
  647. }
  648. s_Sounds.RemoveAll();
  649. s_SoundPool.Clear();
  650. // release DSP resources
  651. FreeDsps( true );
  652. MXR_ReleaseMemory();
  653. // release sentences resources
  654. TRACESHUTDOWN( VOX_Shutdown() );
  655. if ( IsPC() )
  656. {
  657. // shutdown vaudio
  658. if ( vaudio )
  659. delete vaudio;
  660. FileSystem_UnloadModule( g_pVAudioModule );
  661. g_pVAudioModule = NULL;
  662. vaudio = NULL;
  663. }
  664. MIX_FreeAllPaintbuffers();
  665. snd_initialized = false;
  666. g_paintedtime = 0;
  667. g_soundtime = 0;
  668. g_ReplaySoundTimeFracAccumulator = 0.0f;
  669. s_buffers = 0;
  670. s_oldsampleOutCount = 0;
  671. s_lastsoundtime = 0.0f;
  672. #if !defined( _X360 )
  673. Voice_Deinit();
  674. #endif
  675. }
  676. bool S_IsInitted()
  677. {
  678. return snd_initialized;
  679. }
  680. // =======================================================================
  681. // Load a sound
  682. // =======================================================================
  683. //-----------------------------------------------------------------------------
  684. // Return sfx and set pfInCache to 1 if
  685. // name is in name cache. Otherwise, alloc
  686. // a new spot in name cache and return 0
  687. // in pfInCache.
  688. //-----------------------------------------------------------------------------
  689. CSfxTable *S_FindName( const char *szName, int *pfInCache )
  690. {
  691. int i;
  692. CSfxTable *sfx = NULL;
  693. char szBuff[MAX_PATH];
  694. const char *pName;
  695. if ( !szName )
  696. {
  697. Error( "S_FindName: NULL\n" );
  698. }
  699. pName = szName;
  700. if ( IsX360() )
  701. {
  702. Q_strncpy( szBuff, pName, sizeof( szBuff ) );
  703. int len = Q_strlen( szBuff )-4;
  704. if ( len > 0 && !Q_strnicmp( szBuff+len, ".mp3", 4 ) )
  705. {
  706. // convert unsupported .mp3 to .wav
  707. Q_strcpy( szBuff+len, ".wav" );
  708. }
  709. pName = szBuff;
  710. if ( pName[0] == CHAR_STREAM )
  711. {
  712. // streaming (or not) is hardcoded to alternate criteria
  713. // prevent the same sound from creating disparate instances
  714. pName++;
  715. }
  716. }
  717. // see if already loaded
  718. FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pName );
  719. i = s_Sounds.Find( fnHandle );
  720. if ( i != s_Sounds.InvalidIndex() )
  721. {
  722. sfx = s_Sounds[i].pSfx;
  723. Assert( sfx );
  724. if ( pfInCache )
  725. {
  726. // indicate whether or not sound is currently in the cache.
  727. *pfInCache = ( sfx->pSource && sfx->pSource->IsCached() ) ? 1 : 0;
  728. }
  729. return sfx;
  730. }
  731. else
  732. {
  733. SfxDictEntry entry;
  734. entry.pSfx = ( CSfxTable * )s_SoundPool.Alloc();
  735. Assert( entry.pSfx );
  736. i = s_Sounds.Insert( fnHandle, entry );
  737. sfx = s_Sounds[i].pSfx;
  738. sfx->SetNamePoolIndex( i );
  739. sfx->pSource = NULL;
  740. if ( pfInCache )
  741. {
  742. *pfInCache = 0;
  743. }
  744. }
  745. return sfx;
  746. }
  747. //-----------------------------------------------------------------------------
  748. // S_LoadSound
  749. //
  750. // Check to see if wave data is in the cache. If so, return pointer to data.
  751. // If not, allocate cache space for wave data, load wave file into temporary heap
  752. // space, and dump/convert file data into cache.
  753. //-----------------------------------------------------------------------------
  754. double g_flAccumulatedSoundLoadTime = 0.0f;
  755. CAudioSource *S_LoadSound( CSfxTable *pSfx, channel_t *ch )
  756. {
  757. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  758. VPROF("S_LoadSound");
  759. if ( !pSfx->pSource )
  760. {
  761. if ( IsX360() )
  762. {
  763. if ( SND_IsInGame() && !g_pQueuedLoader->IsMapLoading() )
  764. {
  765. // sound should be present (due to reslists), but NOT allowing a load hitch during gameplay
  766. // loading a sound during gameplay is a bad experience, causes a very expensive sync i/o to fetch the header
  767. // and in the case of a memory wave, the actual audio data
  768. bool bFound = false;
  769. if ( !pSfx->m_bIsLateLoad )
  770. {
  771. if ( pSfx->getname() != PSkipSoundChars( pSfx->getname() ) )
  772. {
  773. // the sound might already exist as an undecorated audio source
  774. FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pSfx->GetFileName() );
  775. int i = s_Sounds.Find( fnHandle );
  776. if ( i != s_Sounds.InvalidIndex() )
  777. {
  778. CSfxTable *pOtherSfx = s_Sounds[i].pSfx;
  779. Assert( pOtherSfx );
  780. CAudioSource *pOtherSource = pOtherSfx->pSource;
  781. if ( pOtherSource && pOtherSource->IsCached() )
  782. {
  783. // Can safely let the "load" continue because the headers are expected to be in the preload
  784. // that are now persisted and the wave data cache will find an existing audio buffer match,
  785. // so no sync i/o should occur from either.
  786. bFound = true;
  787. }
  788. }
  789. }
  790. if ( !bFound )
  791. {
  792. // warn once
  793. DevWarning( "S_LoadSound: Late load '%s', skipping.\n", pSfx->getname() );
  794. pSfx->m_bIsLateLoad = true;
  795. }
  796. }
  797. if ( !bFound )
  798. {
  799. return NULL;
  800. }
  801. }
  802. else if ( pSfx->m_bIsLateLoad )
  803. {
  804. // outside of gameplay, let the load happen
  805. pSfx->m_bIsLateLoad = false;
  806. }
  807. }
  808. double st = Plat_FloatTime();
  809. bool bStream = false;
  810. bool bUserVox = false;
  811. // sound chars can explicitly categorize usage
  812. bStream = TestSoundChar( pSfx->getname(), CHAR_STREAM );
  813. if ( !bStream )
  814. {
  815. bUserVox = TestSoundChar( pSfx->getname(), CHAR_USERVOX );
  816. }
  817. // override streaming
  818. if ( IsX360() )
  819. {
  820. const char *s_CriticalSounds[] =
  821. {
  822. "common",
  823. "items",
  824. "physics",
  825. "player",
  826. "ui",
  827. "weapons",
  828. };
  829. // stream everything but critical sounds
  830. bStream = true;
  831. const char *pFileName = pSfx->GetFileName();
  832. for ( int i = 0; i<ARRAYSIZE( s_CriticalSounds ); i++ )
  833. {
  834. size_t len = strlen( s_CriticalSounds[i] );
  835. if ( !Q_strnicmp( pFileName, s_CriticalSounds[i], len ) && ( pFileName[len] == '\\' || pFileName[len] == '/' ) )
  836. {
  837. // never stream these, regardless of sound chars
  838. bStream = false;
  839. break;
  840. }
  841. }
  842. }
  843. if ( bStream )
  844. {
  845. // setup as a streaming resource
  846. pSfx->pSource = Audio_CreateStreamedWave( pSfx );
  847. }
  848. else
  849. {
  850. if ( bUserVox )
  851. {
  852. if ( !IsX360() )
  853. {
  854. pSfx->pSource = Voice_SetupAudioSource( ch->soundsource, ch->entchannel );
  855. }
  856. else
  857. {
  858. // not supporting
  859. Assert( 0 );
  860. }
  861. }
  862. else
  863. {
  864. // load all into memory directly
  865. pSfx->pSource = Audio_CreateMemoryWave( pSfx );
  866. }
  867. }
  868. double ed = Plat_FloatTime();
  869. g_flAccumulatedSoundLoadTime += ( ed - st );
  870. }
  871. else
  872. {
  873. pSfx->pSource->CheckAudioSourceCache();
  874. }
  875. if ( !pSfx->pSource )
  876. {
  877. return NULL;
  878. }
  879. // first time to load? Create the mixer
  880. if ( ch && !ch->pMixer )
  881. {
  882. ch->pMixer = pSfx->pSource->CreateMixer( ch->initialStreamPosition );
  883. if ( !ch->pMixer )
  884. {
  885. return NULL;
  886. }
  887. }
  888. return pSfx->pSource;
  889. }
  890. //-----------------------------------------------------------------------------
  891. // S_PrecacheSound
  892. //
  893. // Reserve space for the name of the sound in a global array.
  894. // Load the data for the non-streaming sound. Streaming sounds
  895. // defer loading of data until just before playback.
  896. //-----------------------------------------------------------------------------
  897. CSfxTable *S_PrecacheSound( const char *name )
  898. {
  899. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  900. if ( !g_AudioDevice )
  901. return NULL;
  902. if ( !g_AudioDevice->IsActive() )
  903. return NULL;
  904. CSfxTable *sfx = S_FindName( name, NULL );
  905. if ( sfx )
  906. {
  907. // cache sound
  908. S_LoadSound( sfx, NULL );
  909. }
  910. else
  911. {
  912. Assert( !"S_PrecacheSound: Failed to create sfx" );
  913. }
  914. return sfx;
  915. }
  916. void S_InternalReloadSound( CSfxTable *sfx )
  917. {
  918. if ( !sfx || !sfx->pSource )
  919. return;
  920. sfx->pSource->CacheUnload();
  921. delete sfx->pSource;
  922. sfx->pSource = NULL;
  923. char pExt[10];
  924. Q_ExtractFileExtension( sfx->getname(), pExt, sizeof(pExt) );
  925. int nSource = !Q_stricmp( pExt, "mp3" ) ? CAudioSource::AUDIO_SOURCE_MP3 : CAudioSource::AUDIO_SOURCE_WAV;
  926. // audiosourcecache->RebuildCacheEntry( nSource, sfx->IsPrecachedSound(), sfx );
  927. audiosourcecache->GetInfo( nSource, sfx->IsPrecachedSound(), sfx ); // Do a size/date check and rebuild the cache entry if necessary.
  928. }
  929. //-----------------------------------------------------------------------------
  930. // Refresh a sound in the cache
  931. //-----------------------------------------------------------------------------
  932. void S_ReloadSound( const char *name )
  933. {
  934. if ( IsX360() )
  935. {
  936. // not supporting
  937. Assert( 0 );
  938. return;
  939. }
  940. if ( !g_AudioDevice )
  941. return;
  942. if ( !g_AudioDevice->IsActive() )
  943. return;
  944. CSfxTable *sfx = S_FindName( name, NULL );
  945. #ifdef _DEBUG
  946. if ( sfx )
  947. {
  948. Assert( Q_stricmp( sfx->getname(), name ) == 0 );
  949. }
  950. #endif
  951. S_InternalReloadSound( sfx );
  952. }
  953. // See comments on CL_HandlePureServerWhitelist for details of what we're doing here.
  954. void S_ReloadFilesInList( IFileList *pFilesToReload )
  955. {
  956. if ( !IsPC() )
  957. return;
  958. S_StopAllSounds( true );
  959. wavedatacache->Flush();
  960. audiosourcecache->ForceRecheckDiskInfo(); // Force all cached audio data to recheck size/date info next time it's accessed.
  961. CUtlVector< CSfxTable * > processed;
  962. int iLast = s_Sounds.LastInorder();
  963. for ( int i = s_Sounds.FirstInorder(); i != iLast; i = s_Sounds.NextInorder( i ) )
  964. {
  965. FileNameHandle_t fnHandle = s_Sounds.Key( i );
  966. char filename[MAX_PATH * 3];
  967. if ( !g_pFileSystem->String( fnHandle, filename, sizeof( filename ) ) )
  968. {
  969. Assert( !"S_HandlePureServerWhitelist - can't get a filename." );
  970. continue;
  971. }
  972. // If the file isn't cached in yet, then the filesystem hasn't touched its file, so don't bother.
  973. CSfxTable *sfx = s_Sounds[i].pSfx;
  974. if ( sfx && ( processed.Find( sfx ) == processed.InvalidIndex() ) )
  975. {
  976. char fullFilename[MAX_PATH*2];
  977. if ( IsSoundChar( filename[0] ) )
  978. Q_snprintf( fullFilename, sizeof( fullFilename ), "sound/%s", &filename[1] );
  979. else
  980. Q_snprintf( fullFilename, sizeof( fullFilename ), "sound/%s", filename );
  981. if ( !pFilesToReload->IsFileInList( fullFilename ) )
  982. continue;
  983. processed.AddToTail( sfx );
  984. S_InternalReloadSound( sfx );
  985. }
  986. }
  987. }
  988. //-----------------------------------------------------------------------------
  989. // Unfortunate confusing terminology.
  990. // Here prefetching means hinting to the audio source (which may be a stream)
  991. // to get its async data in flight.
  992. //-----------------------------------------------------------------------------
  993. void S_PrefetchSound( char const *name, bool bPlayOnce )
  994. {
  995. CSfxTable *sfx;
  996. if ( !g_AudioDevice )
  997. return;
  998. if ( !g_AudioDevice->IsActive() )
  999. return;
  1000. sfx = S_FindName( name, NULL );
  1001. if ( sfx )
  1002. {
  1003. // cache sound
  1004. S_LoadSound( sfx, NULL );
  1005. }
  1006. if ( !sfx || !sfx->pSource )
  1007. {
  1008. return;
  1009. }
  1010. // hint the sound to start loading
  1011. sfx->pSource->Prefetch();
  1012. if ( bPlayOnce )
  1013. {
  1014. sfx->pSource->SetPlayOnce( true );
  1015. }
  1016. }
  1017. void S_MarkUISound( CSfxTable *pSfx )
  1018. {
  1019. pSfx->m_bIsUISound = true;
  1020. }
  1021. unsigned int RemainingSamples( channel_t *pChannel )
  1022. {
  1023. if ( !pChannel || !pChannel->sfx || !pChannel->sfx->pSource )
  1024. return 0;
  1025. unsigned int timeleft = pChannel->sfx->pSource->SampleCount();
  1026. if ( pChannel->sfx->pSource->IsLooped() )
  1027. {
  1028. return pChannel->sfx->pSource->SampleRate();
  1029. }
  1030. if ( pChannel->pMixer )
  1031. {
  1032. timeleft -= pChannel->pMixer->GetSamplePosition();
  1033. }
  1034. return timeleft;
  1035. }
  1036. // chooses the voice stealing algorithm
  1037. ConVar voice_steal("voice_steal", "2");
  1038. /*
  1039. =================
  1040. SND_StealDynamicChannel
  1041. Select a channel from the dynamic channel allocation area. For the given entity,
  1042. override any other sound playing on the same channel (see code comments below for
  1043. exceptions).
  1044. =================
  1045. */
  1046. channel_t *SND_StealDynamicChannel(SoundSource soundsource, int entchannel, const Vector &origin, CSfxTable *sfx, float flDelay, bool bDoNotOverwriteExisting)
  1047. {
  1048. int canSteal[MAX_DYNAMIC_CHANNELS];
  1049. int canStealCount = 0;
  1050. int sameSoundCount = 0;
  1051. unsigned int sameSoundRemaining = 0xFFFFFFFF;
  1052. int sameSoundIndex = -1;
  1053. int sameVol = 0xFFFF;
  1054. int availableChannel = -1;
  1055. bool bDelaySame = false;
  1056. int nExactMatch[MAX_DYNAMIC_CHANNELS];
  1057. int nExactCount = 0;
  1058. // first pass to replace sounds on same ent/channel, and search for free or stealable channels otherwise
  1059. for ( int ch_idx = 0; ch_idx < MAX_DYNAMIC_CHANNELS ; ch_idx++)
  1060. {
  1061. channel_t *ch = &channels[ch_idx];
  1062. if ( ch->activeIndex )
  1063. {
  1064. // channel CHAN_AUTO never overrides sounds on same channel
  1065. if ( entchannel != CHAN_AUTO )
  1066. {
  1067. int checkChannel = entchannel;
  1068. if ( checkChannel == -1 )
  1069. {
  1070. if ( ch->entchannel != CHAN_STREAM && ch->entchannel != CHAN_VOICE && ch->entchannel != CHAN_VOICE2 )
  1071. {
  1072. checkChannel = ch->entchannel;
  1073. }
  1074. }
  1075. if ( ch->soundsource == soundsource && (soundsource != -1) && ch->entchannel == checkChannel )
  1076. {
  1077. // we found an exact match for this entity and this channel, but the sound we want to play is considered
  1078. // low priority so instead of stomping this entry pretend we couldn't find a free slot to play and let
  1079. // the existing sound keep going
  1080. if ( bDoNotOverwriteExisting )
  1081. return NULL;
  1082. if ( ch->flags.delayed_start )
  1083. {
  1084. nExactMatch[nExactCount] = ch_idx;
  1085. nExactCount++;
  1086. continue;
  1087. }
  1088. return ch; // always override sound from same entity
  1089. }
  1090. }
  1091. // Never steal the channel of a streaming sound that is currently playing or
  1092. // voice over IP data that is playing or any sound on CHAN_VOICE( acting )
  1093. if ( ch->entchannel == CHAN_STREAM || ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_VOICE2 )
  1094. continue;
  1095. // don't let monster sounds override player sounds
  1096. if ( g_pSoundServices->IsPlayer( ch->soundsource ) && !g_pSoundServices->IsPlayer(soundsource) )
  1097. continue;
  1098. if ( ch->sfx == sfx )
  1099. {
  1100. bDelaySame = ch->flags.delayed_start ? true : bDelaySame;
  1101. sameSoundCount++;
  1102. int maxVolume = ChannelGetMaxVol( ch );
  1103. unsigned int remaining = RemainingSamples(ch);
  1104. if ( maxVolume < sameVol || (maxVolume == sameVol && remaining < sameSoundRemaining) )
  1105. {
  1106. sameSoundIndex = ch_idx;
  1107. sameVol = maxVolume;
  1108. sameSoundRemaining = remaining;
  1109. }
  1110. }
  1111. canSteal[canStealCount++] = ch_idx;
  1112. }
  1113. else
  1114. {
  1115. if ( availableChannel < 0 )
  1116. {
  1117. availableChannel = ch_idx;
  1118. }
  1119. }
  1120. }
  1121. // coalesce the timeline for this channel
  1122. if ( nExactCount > 0 )
  1123. {
  1124. uint nFreeSampleTime = g_paintedtime + (flDelay * SOUND_DMA_SPEED);
  1125. channel_t *pReturn = &channels[nExactMatch[0]];
  1126. uint nMinRemaining = RemainingSamples( pReturn );
  1127. if ( pReturn->nFreeChannelAtSampleTime == 0 || pReturn->nFreeChannelAtSampleTime > nFreeSampleTime )
  1128. {
  1129. pReturn->nFreeChannelAtSampleTime = nFreeSampleTime;
  1130. }
  1131. for ( int i = 1; i < nExactCount; i++ )
  1132. {
  1133. channel_t *pChannel = &channels[nExactMatch[i]];
  1134. if ( pChannel->nFreeChannelAtSampleTime == 0 || pChannel->nFreeChannelAtSampleTime > nFreeSampleTime )
  1135. {
  1136. pChannel->nFreeChannelAtSampleTime = nFreeSampleTime;
  1137. }
  1138. uint nRemain = RemainingSamples( pChannel );
  1139. if ( nRemain < nMinRemaining )
  1140. {
  1141. pReturn = pChannel;
  1142. nMinRemaining = nRemain;
  1143. }
  1144. }
  1145. // if there's only one, mark it to be freed but don't reuse it.
  1146. // otherwise mark all others to be freed and use the closest one to being done
  1147. if ( nExactCount > 1 )
  1148. {
  1149. return pReturn;
  1150. }
  1151. }
  1152. // Limit the number of times a given sfx/wave can play simultaneously
  1153. if ( voice_steal.GetInt() > 1 && sameSoundIndex >= 0 )
  1154. {
  1155. // if sounds of this type are normally delayed, then add an extra slot for stealing
  1156. // NOTE: In HL2 these are usually NPC gunshot sounds - and stealing too soon will cut
  1157. // them off early. This is a safe heuristic to avoid that problem. There's probably a better
  1158. // long-term solution involving only counting channels that are actually going to play (delay included)
  1159. // at the same time as this one.
  1160. int maxSameSounds = bDelaySame ? 5 : 4;
  1161. float distSqr = 0.0f;
  1162. if ( sfx->pSource )
  1163. {
  1164. distSqr = origin.DistToSqr(listener_origin);
  1165. if ( sfx->pSource->IsLooped() )
  1166. {
  1167. maxSameSounds = 3;
  1168. }
  1169. }
  1170. // don't play more than N copies of the same sound, steal the quietest & closest one otherwise
  1171. if ( sameSoundCount >= maxSameSounds )
  1172. {
  1173. channel_t *ch = &channels[sameSoundIndex];
  1174. // you're already playing a closer version of this sound, don't steal
  1175. if ( distSqr > 0.0f && ch->origin.DistToSqr(listener_origin) < distSqr && entchannel != CHAN_WEAPON )
  1176. return NULL;
  1177. //Msg("Sound playing %d copies, stole %s (%d)\n", sameSoundCount, ch->sfx->getname(), sameVol );
  1178. return ch;
  1179. }
  1180. }
  1181. // if there's a free channel, just take that one - don't steal
  1182. if ( availableChannel >= 0 )
  1183. return &channels[availableChannel];
  1184. // Still haven't found a suitable channel, so choose the one with the least amount of time left to play
  1185. float life_left = FLT_MAX;
  1186. int first_to_die = -1;
  1187. bool bAllowVoiceSteal = voice_steal.GetBool();
  1188. for ( int i = 0; i < canStealCount; i++ )
  1189. {
  1190. int ch_idx = canSteal[i];
  1191. channel_t *ch = &channels[ch_idx];
  1192. float timeleft = 0;
  1193. if ( bAllowVoiceSteal )
  1194. {
  1195. int maxVolume = ChannelGetMaxVol( ch );
  1196. if ( maxVolume < 5 )
  1197. {
  1198. //Msg("Sound quiet, stole %s for %s\n", ch->sfx->getname(), sfx->getname() );
  1199. return ch;
  1200. }
  1201. if ( ch->sfx && ch->sfx->pSource )
  1202. {
  1203. unsigned int sampleCount = RemainingSamples( ch );
  1204. timeleft = (float)sampleCount / (float)ch->sfx->pSource->SampleRate();
  1205. }
  1206. }
  1207. else
  1208. {
  1209. // UNDONE: Kill this when voice_steal 0,1,2 has been tested
  1210. // UNDONE: This is the old buggy code that we're trying to replace
  1211. if ( ch->sfx )
  1212. {
  1213. // basically steals the first one you come to
  1214. timeleft = 1; //ch->end - paintedtime
  1215. }
  1216. }
  1217. if ( timeleft < life_left )
  1218. {
  1219. life_left = timeleft;
  1220. first_to_die = ch_idx;
  1221. }
  1222. }
  1223. if ( first_to_die >= 0 )
  1224. {
  1225. //Msg("Stole %s, timeleft %d\n", channels[first_to_die].sfx->getname(), life_left );
  1226. return &channels[first_to_die];
  1227. }
  1228. return NULL;
  1229. }
  1230. channel_t *SND_PickDynamicChannel(SoundSource soundsource, int entchannel, const Vector &origin, CSfxTable *sfx, float flDelay, bool bDoNotOverwriteExisting)
  1231. {
  1232. channel_t *pChannel = SND_StealDynamicChannel( soundsource, entchannel, origin, sfx, flDelay, bDoNotOverwriteExisting );
  1233. if ( !pChannel )
  1234. return NULL;
  1235. if (pChannel->sfx)
  1236. {
  1237. // Don't restart looping sounds for the same entity
  1238. CAudioSource *pSource = pChannel->sfx->pSource;
  1239. if ( pSource )
  1240. {
  1241. if ( pSource->IsLooped() )
  1242. {
  1243. if ( pChannel->soundsource == soundsource && pChannel->entchannel == entchannel && pChannel->sfx == sfx )
  1244. {
  1245. // same looping sound, same ent, same channel, don't restart the sound
  1246. return NULL;
  1247. }
  1248. }
  1249. }
  1250. // be sure and release previous channel
  1251. // if sentence.
  1252. // ("Stealing channel from %s\n", channels[first_to_die].sfx->getname() );
  1253. S_FreeChannel(pChannel);
  1254. }
  1255. return pChannel;
  1256. }
  1257. /*
  1258. =====================
  1259. SND_PickStaticChannel
  1260. =====================
  1261. Pick an empty channel from the static sound area, or allocate a new
  1262. channel. Only fails if we're at max_channels (128!!!) or if
  1263. we're trying to allocate a channel for a stream sound that is
  1264. already playing.
  1265. */
  1266. channel_t *SND_PickStaticChannel(int soundsource, CSfxTable *pSfx)
  1267. {
  1268. int i;
  1269. channel_t *ch = NULL;
  1270. // Check for replacement sound, or find the best one to replace
  1271. for (i = MAX_DYNAMIC_CHANNELS; i<total_channels; i++)
  1272. if (channels[i].sfx == NULL)
  1273. break;
  1274. if (i < total_channels)
  1275. {
  1276. // reuse an empty static sound channel
  1277. ch = &channels[i];
  1278. }
  1279. else
  1280. {
  1281. // no empty slots, alloc a new static sound channel
  1282. if (total_channels == MAX_CHANNELS)
  1283. {
  1284. DevMsg ("total_channels == MAX_CHANNELS\n");
  1285. return NULL;
  1286. }
  1287. // get a channel for the static sound
  1288. ch = &channels[total_channels];
  1289. total_channels++;
  1290. }
  1291. return ch;
  1292. }
  1293. void S_SpatializeChannel( int pVolume[CCHANVOLUMES/2], int master_vol, const Vector *psourceDir, float gain, float mono )
  1294. {
  1295. float lscale, rscale, scale;
  1296. vec_t dotRight;
  1297. Vector sourceDir = *psourceDir;
  1298. dotRight = DotProduct(listener_right, sourceDir);
  1299. // clear volumes
  1300. for (int i = 0; i < CCHANVOLUMES/2; i++)
  1301. pVolume[i] = 0;
  1302. if (mono > 0.0)
  1303. {
  1304. // sound has radius, within which spatialization becomes mono:
  1305. // mono is 0.0 -> 1.0, from radius 100% to radius 50%
  1306. // at radius * 0.5, dotRight is 0 (ie: sound centered left/right)
  1307. // at radius * 1.0, dotRight == dotRight
  1308. dotRight *= (1.0 - mono);
  1309. }
  1310. rscale = 1.0 + dotRight;
  1311. lscale = 1.0 - dotRight;
  1312. // add in distance effect
  1313. scale = gain * rscale / 2;
  1314. pVolume[IFRONT_RIGHT] = (int) (master_vol * scale);
  1315. scale = gain * lscale / 2;
  1316. pVolume[IFRONT_LEFT] = (int) (master_vol * scale);
  1317. pVolume[IFRONT_RIGHT] = clamp( pVolume[IFRONT_RIGHT], 0, 255 );
  1318. pVolume[IFRONT_LEFT] = clamp( pVolume[IFRONT_LEFT], 0, 255 );
  1319. }
  1320. bool S_IsMusic( channel_t *pChannel )
  1321. {
  1322. if ( !pChannel->flags.bdry )
  1323. return false;
  1324. CSfxTable *sfx = pChannel->sfx;
  1325. if ( !sfx )
  1326. return false;
  1327. CAudioSource *source = sfx->pSource;
  1328. if ( !source )
  1329. return false;
  1330. // Don't save restore looping sounds as you can end up with an entity restarting them again and have
  1331. // them accumulate, etc.
  1332. if ( source->IsLooped() )
  1333. return false;
  1334. CAudioMixer *pMixer = pChannel->pMixer;
  1335. if ( !pMixer )
  1336. return false;
  1337. for ( int i = 0; i < 8; i++ )
  1338. {
  1339. if ( pChannel->mixgroups[i] != -1 )
  1340. {
  1341. char *pGroupName = MXR_GetGroupnameFromId( pChannel->mixgroups[i] );
  1342. if ( !Q_strcmp( pGroupName, "Music" ) )
  1343. {
  1344. return true;
  1345. }
  1346. }
  1347. }
  1348. return false;
  1349. }
  1350. //-----------------------------------------------------------------------------
  1351. // Purpose: For save/restore of currently playing music
  1352. // Input : list -
  1353. //-----------------------------------------------------------------------------
  1354. void S_GetCurrentlyPlayingMusic( CUtlVector< musicsave_t >& musiclist )
  1355. {
  1356. CChannelList list;
  1357. g_ActiveChannels.GetActiveChannels( list );
  1358. for ( int i = 0; i < list.Count(); i++ )
  1359. {
  1360. channel_t *pChannel = &channels[list.GetChannelIndex(i)];
  1361. if ( !S_IsMusic( pChannel ) )
  1362. continue;
  1363. musicsave_t song;
  1364. Q_strncpy( song.songname, pChannel->sfx->getname(), sizeof( song.songname ) );
  1365. song.sampleposition = pChannel->pMixer->GetPositionForSave();
  1366. song.master_volume = pChannel->master_vol;
  1367. musiclist.AddToTail( song );
  1368. }
  1369. }
  1370. //-----------------------------------------------------------------------------
  1371. // Purpose:
  1372. // Input : *song -
  1373. //-----------------------------------------------------------------------------
  1374. void S_RestartSong( const musicsave_t *song )
  1375. {
  1376. Assert( song );
  1377. // Start the song
  1378. CSfxTable *pSound = S_PrecacheSound( song->songname );
  1379. if ( pSound )
  1380. {
  1381. StartSoundParams_t params;
  1382. params.staticsound = true;
  1383. params.soundsource = SOUND_FROM_WORLD;
  1384. params.entchannel = CHAN_STATIC;
  1385. params.pSfx = pSound;
  1386. params.origin = vec3_origin;
  1387. params.fvol = ( (float)song->master_volume / 255.0f );
  1388. params.soundlevel = SNDLVL_NONE;
  1389. params.flags = SND_NOFLAGS;
  1390. params.pitch = PITCH_NORM;
  1391. params.initialStreamPosition = song->sampleposition;
  1392. S_StartSound( params );
  1393. if ( IsPC() )
  1394. {
  1395. // Now find the channel this went on and skip ahead in the mixer
  1396. for (int i = 0; i < total_channels; i++)
  1397. {
  1398. channel_t *ch = &channels[i];
  1399. if ( !ch->pMixer ||
  1400. !ch->pMixer->GetSource() )
  1401. {
  1402. continue;
  1403. }
  1404. if ( ch->pMixer->GetSource() != pSound->pSource )
  1405. {
  1406. continue;
  1407. }
  1408. ch->pMixer->SetPositionFromSaved( song->sampleposition );
  1409. break;
  1410. }
  1411. }
  1412. }
  1413. }
  1414. soundlevel_t SND_GetSndlvl ( channel_t *pchannel );
  1415. // calculate ammount of sound to be mixed to dsp, based on distance from listener
  1416. ConVar dsp_dist_min("dsp_dist_min", "0.0", FCVAR_DEMO|FCVAR_CHEAT); // range at which sounds are mixed at dsp_mix_min
  1417. ConVar dsp_dist_max("dsp_dist_max", "1440.0", FCVAR_DEMO|FCVAR_CHEAT); // range at which sounds are mixed at dsp_mix_max
  1418. ConVar dsp_mix_min("dsp_mix_min", "0.2", FCVAR_DEMO ); // dsp mix at dsp_dist_min distance "near"
  1419. ConVar dsp_mix_max("dsp_mix_max", "0.8", FCVAR_DEMO ); // dsp mix at dsp_dist_max distance "far"
  1420. ConVar dsp_db_min("dsp_db_min", "80", FCVAR_DEMO ); // sounds with sndlvl below this get dsp_db_mixdrop % less dsp mix
  1421. ConVar dsp_db_mixdrop("dsp_db_mixdrop", "0.5", FCVAR_DEMO ); // sounds with sndlvl below dsp_db_min get dsp_db_mixdrop % less mix
  1422. float DSP_ROOM_MIX = 1.0; // mix volume of dsp_room sounds when added back to 'dry' sounds
  1423. float DSP_NOROOM_MIX = 1.0; // mix volume of facing + facing away sounds. added to dsp_room_mix sounds
  1424. extern ConVar dsp_off;
  1425. // returns 0-1.0 dsp mix value. If sound source is at a range >= DSP_DIST_MAX, return a mix value of
  1426. // DSP_MIX_MAX. This mix value is used later to determine wet/dry mix ratio of sounds.
  1427. // This ramp changes with db level of sound source, and is set in the dsp room presets by room size
  1428. // empirical data: 0.78 is nominal mix for sound 100% at far end of room, 0.24 is mix for sound 25% into room
  1429. float SND_GetDspMix( channel_t *pchannel, int idist)
  1430. {
  1431. float mix;
  1432. float dist = (float)idist;
  1433. float dist_min = dsp_dist_min.GetFloat();
  1434. float dist_max = dsp_dist_max.GetFloat();
  1435. float mix_min;
  1436. float mix_max;
  1437. // only set dsp mix_min & mix_max when sound is first started
  1438. if ( pchannel->dsp_mix_min < 0 && pchannel->dsp_mix_max < 0 )
  1439. {
  1440. mix_min = dsp_mix_min.GetFloat(); // set via dsp_room preset
  1441. mix_max = dsp_mix_max.GetFloat(); // set via dsp_room preset
  1442. // set mix_min & mix_max based on db level of sound:
  1443. // sounds below dsp_db_min decrease dsp_mix_min & dsp_mix_max by N%
  1444. // ie: quiet sounds get less dsp mix than loud sounds
  1445. soundlevel_t sndlvl = SND_GetSndlvl( pchannel );
  1446. soundlevel_t sndlvl_min = (soundlevel_t)(dsp_db_min.GetInt());
  1447. if (sndlvl <= sndlvl_min)
  1448. {
  1449. mix_min *= dsp_db_mixdrop.GetFloat();
  1450. mix_max *= dsp_db_mixdrop.GetFloat();
  1451. }
  1452. pchannel->dsp_mix_min = mix_min;
  1453. pchannel->dsp_mix_max = mix_max;
  1454. }
  1455. else
  1456. {
  1457. mix_min = pchannel->dsp_mix_min;
  1458. mix_max = pchannel->dsp_mix_max;
  1459. }
  1460. // dspmix is 0 (100% mix to facing buffer) if dsp_off
  1461. if ( dsp_off.GetInt() )
  1462. return 0.0;
  1463. // doppler wavs are mixed dry
  1464. if ( pchannel->wavtype == CHAR_DOPPLER )
  1465. return 0.0;
  1466. // linear ramp - get dry mix %
  1467. // dist: 0->(max - min)
  1468. dist = clamp( dist, dist_min, dist_max ) - dist_min;
  1469. // dist: 0->1.0
  1470. dist = dist / (dist_max - dist_min);
  1471. // mix: min->max
  1472. mix = ((mix_max - mix_min) * dist) + mix_min;
  1473. return mix;
  1474. }
  1475. // calculate crossfade between wav left (close sound) and wav right (far sound) based on
  1476. // distance fron listener
  1477. #define DVAR_DIST_MIN (20.0 * 12.0) // play full 'near' sound at 20' or less
  1478. #define DVAR_DIST_MAX (110.0 * 12.0) // play full 'far' sound at 110' or more
  1479. #define DVAR_MIX_MIN 0.0
  1480. #define DVAR_MIX_MAX 1.0
  1481. // calculate mixing parameter for CHAR_DISTVAR wavs
  1482. // returns 0 - 1.0, 1.0 is 100% far sound (wav right)
  1483. float SND_GetDistanceMix( channel_t *pchannel, int idist)
  1484. {
  1485. float mix;
  1486. float dist = (float)idist;
  1487. // doppler wavs are 100% near - their spatialization is calculated later.
  1488. if ( pchannel->wavtype == CHAR_DOPPLER )
  1489. return 0.0;
  1490. // linear ramp - get dry mix %
  1491. // dist 0->(max - min)
  1492. dist = clamp( dist, (float) DVAR_DIST_MIN, (float) DVAR_DIST_MAX ) - (float) DVAR_DIST_MIN;
  1493. // dist 0->1.0
  1494. dist = dist / (DVAR_DIST_MAX - DVAR_DIST_MIN);
  1495. // mix min->max
  1496. mix = ((DVAR_MIX_MAX - DVAR_MIX_MIN) * dist) + DVAR_MIX_MIN;
  1497. return mix;
  1498. }
  1499. // given facing direction of source, and channel,
  1500. // return -1.0 - 1.0, where -1.0 is source facing away from listener
  1501. // and 1.0 is source facing listener
  1502. float SND_GetFacingDirection( channel_t *pChannel, const QAngle &source_angles )
  1503. {
  1504. Vector SF; // sound source forward direction unit vector
  1505. Vector SL; // sound -> listener unit vector
  1506. float dotSFSL;
  1507. // no facing direction unless wavtyp CHAR_DIRECTIONAL
  1508. if ( pChannel->wavtype != CHAR_DIRECTIONAL )
  1509. return 1.0;
  1510. VectorSubtract(listener_origin, pChannel->origin, SL);
  1511. VectorNormalize(SL);
  1512. // compute forward vector for sound entity
  1513. AngleVectors( source_angles, &SF, NULL, NULL );
  1514. // dot source forward unit vector with source to listener unit vector to get -1.0 - 1.0 facing.
  1515. // ie: projection of SF onto SL
  1516. dotSFSL = DotProduct( SF, SL );
  1517. return dotSFSL;
  1518. }
  1519. // calculate point of closest approach - caller must ensure that the
  1520. // forward facing vector of the entity playing this sound points in exactly the direction of
  1521. // travel of the sound. ie: for bullets or tracers, forward vector must point in traceline direction.
  1522. // return true if sound is to be played, false if sound cannot be heard (shot away from player)
  1523. bool SND_GetClosestPoint( channel_t *pChannel, QAngle &source_angles, Vector &vnearpoint )
  1524. {
  1525. // S - sound source origin
  1526. // L - listener origin
  1527. Vector SF; // sound source forward direction unit vector
  1528. Vector SL; // sound -> listener vector
  1529. Vector SD; // sound->closest point vector
  1530. vec_t dSLSF; // magnitude of project of SL onto SF
  1531. // P = SF (SF . SL) + S
  1532. // only perform this calculation for doppler wavs
  1533. if ( pChannel->wavtype != CHAR_DOPPLER )
  1534. return false;
  1535. // get vector 'SL' from sound source to listener
  1536. VectorSubtract(listener_origin, pChannel->origin, SL);
  1537. // compute sound->forward vector 'SF' for sound entity
  1538. AngleVectors( source_angles, &SF );
  1539. VectorNormalize( SF );
  1540. dSLSF = DotProduct( SL, SF );
  1541. if ( dSLSF <= 0 && !toolframework->IsToolRecording() )
  1542. {
  1543. // source is pointing away from listener, don't play anything
  1544. // unless we're recording in the tool, since we may play back from in front of the source
  1545. return false;
  1546. }
  1547. // project dSLSF along forward unit vector from sound source
  1548. VectorMultiply( SF, dSLSF, SD );
  1549. // output vector - add SD to sound source origin
  1550. VectorAdd( SD, pChannel->origin, vnearpoint );
  1551. return true;
  1552. }
  1553. // given point of nearest approach and sound source facing angles,
  1554. // return vector pointing into quadrant in which to play
  1555. // doppler left wav (incomming) and doppler right wav (outgoing).
  1556. // doppler left is point in space to play left doppler wav
  1557. // doppler right is point in space to play right doppler wav
  1558. // Also modifies channel pitch based on distance to nearest approach point
  1559. #define DOPPLER_DIST_LEFT_TO_RIGHT (4*12) // separate left/right sounds by 4'
  1560. #define DOPPLER_DIST_MAX (20*12) // max distance - causes min pitch
  1561. #define DOPPLER_DIST_MIN (1*12) // min distance - causes max pitch
  1562. #define DOPPLER_PITCH_MAX 1.5 // max pitch change due to distance
  1563. #define DOPPLER_PITCH_MIN 0.25 // min pitch change due to distance
  1564. #define DOPPLER_RANGE_MAX (10*12) // don't play doppler wav unless within this range
  1565. // UNDONE: should be set by caller!
  1566. void SND_GetDopplerPoints( channel_t *pChannel, QAngle &source_angles, Vector &vnearpoint, Vector &source_doppler_left, Vector &source_doppler_right)
  1567. {
  1568. Vector SF; // direction sound source is facing (forward)
  1569. Vector LN; // vector from listener to closest approach point
  1570. Vector DL;
  1571. Vector DR;
  1572. // nearpoint is closest point of approach, when playing CHAR_DOPPLER sounds
  1573. // SF is normalized vector in direction sound source is facing
  1574. AngleVectors( source_angles, &SF );
  1575. VectorNormalize( SF );
  1576. // source_doppler_left - location in space to play doppler left wav (incomming)
  1577. // source_doppler_right - location in space to play doppler right wav (outgoing)
  1578. VectorMultiply( SF, -1*DOPPLER_DIST_LEFT_TO_RIGHT, DL );
  1579. VectorMultiply( SF, DOPPLER_DIST_LEFT_TO_RIGHT, DR );
  1580. VectorAdd( vnearpoint, DL, source_doppler_left );
  1581. VectorAdd( vnearpoint, DR, source_doppler_right );
  1582. // set pitch of channel based on nearest distance to listener
  1583. // LN is vector from listener to closest approach point
  1584. VectorSubtract(vnearpoint, listener_origin, LN);
  1585. float pitch;
  1586. float dist = VectorLength( LN );
  1587. // dist varies 0->1
  1588. dist = clamp(dist, (float)DOPPLER_DIST_MIN, (float)DOPPLER_DIST_MAX);
  1589. dist = (dist - DOPPLER_DIST_MIN) / (DOPPLER_DIST_MAX - DOPPLER_DIST_MIN);
  1590. // pitch varies from max to min
  1591. pitch = DOPPLER_PITCH_MAX - dist * (DOPPLER_PITCH_MAX - DOPPLER_PITCH_MIN);
  1592. pChannel->basePitch = (int)(pitch * 100.0);
  1593. }
  1594. // console variables used to construct gain curve - don't change these!
  1595. extern ConVar snd_foliage_db_loss;
  1596. extern ConVar snd_gain;
  1597. extern ConVar snd_refdb;
  1598. extern ConVar snd_refdist;
  1599. extern ConVar snd_gain_max;
  1600. extern ConVar snd_gain_min;
  1601. ConVar snd_showstart( "snd_showstart", "0", FCVAR_CHEAT ); // showstart always skips info on player footsteps!
  1602. // 1 - show sound name, channel, volume, time
  1603. // 2 - show dspmix, distmix, dspface, l/r/f/r vols
  1604. // 3 - show sound origin coords
  1605. // 4 - show gain of dsp_room
  1606. // 5 - show dB loss due to obscured sound
  1607. // 6 - reserved
  1608. // 7 - show 2 and total gain & dist in ft. to sound source
  1609. #define SND_DB_MAX 140.0 // max db of any sound source
  1610. #define SND_DB_MED 90.0 // db at which compression curve changes
  1611. #define SND_DB_MIN 60.0 // min db of any sound source
  1612. #define SND_GAIN_PLAYER_WEAPON_DB 2.0 // increase player weapon gain by N dB
  1613. // dB = 20 log (amplitude/32768) 0 to -90.3dB
  1614. // amplitude = 32768 * 10 ^ (dB/20) 0 to +/- 32768
  1615. // gain = amplitude/32768 0 to 1.0
  1616. float Gain_To_dB ( float gain )
  1617. {
  1618. float dB = 20 * log ( gain );
  1619. return dB;
  1620. }
  1621. float dB_To_Gain ( float dB )
  1622. {
  1623. float gain = powf (10, dB / 20.0);
  1624. return gain;
  1625. }
  1626. float Gain_To_Amplitude ( float gain )
  1627. {
  1628. return gain * 32768;
  1629. }
  1630. float Amplitude_To_Gain ( float amplitude )
  1631. {
  1632. return amplitude / 32768;
  1633. }
  1634. soundlevel_t SND_GetSndlvl ( channel_t *pchannel )
  1635. {
  1636. return DIST_MULT_TO_SNDLVL( pchannel->dist_mult );
  1637. }
  1638. // The complete gain calculation, with SNDLVL given in dB is:
  1639. //
  1640. // GAIN = 1/dist * snd_refdist * 10 ^ ( ( SNDLVL - snd_refdb - (dist * snd_foliage_db_loss / 1200)) / 20 )
  1641. //
  1642. // for gain > SND_GAIN_THRESH, start curve smoothing with
  1643. //
  1644. // GAIN = 1 - 1 / (Y * GAIN ^ SND_GAIN_POWER)
  1645. //
  1646. // where Y = -1 / ( (SND_GAIN_THRESH ^ SND_GAIN_POWER) * (SND_GAIN_THRESH - 1) )
  1647. //
  1648. float SND_GetGainFromMult( float gain, float dist_mult, vec_t dist );
  1649. // gain curve construction
  1650. float SND_GetGain( channel_t *ch, bool fplayersound, bool fmusicsound, bool flooping, vec_t dist, bool bAttenuated )
  1651. {
  1652. VPROF_("SND_GetGain",2,VPROF_BUDGETGROUP_OTHER_SOUND,false,BUDGETFLAG_OTHER);
  1653. if ( ch->flags.m_bCompatibilityAttenuation )
  1654. {
  1655. // Convert to the original attenuation value.
  1656. soundlevel_t soundlevel = DIST_MULT_TO_SNDLVL( ch->dist_mult );
  1657. float flAttenuation = SNDLVL_TO_ATTN( soundlevel );
  1658. // Now get the goldsrc dist_mult and use the same calculation it uses in SND_Spatialize.
  1659. // Straight outta Goldsrc!!!
  1660. vec_t nominal_clip_dist = 1000.0;
  1661. float flGoldsrcDistMult = flAttenuation / nominal_clip_dist;
  1662. dist *= flGoldsrcDistMult;
  1663. float flReturnValue = 1.0f - dist;
  1664. flReturnValue = clamp( flReturnValue, 0.f, 1.f );
  1665. return flReturnValue;
  1666. }
  1667. else
  1668. {
  1669. float gain = snd_gain.GetFloat();
  1670. if ( fmusicsound )
  1671. {
  1672. gain = gain * snd_musicvolume.GetFloat();
  1673. gain = gain * g_DashboardMusicMixValue;
  1674. }
  1675. if ( ch->dist_mult )
  1676. {
  1677. gain = SND_GetGainFromMult( gain, ch->dist_mult, dist );
  1678. }
  1679. if ( fplayersound )
  1680. {
  1681. // player weapon sounds get extra gain - this compensates
  1682. // for npc distance effect weapons which mix louder as L+R into L,R
  1683. // Hack.
  1684. if ( ch->entchannel == CHAN_WEAPON )
  1685. gain = gain * dB_To_Gain( SND_GAIN_PLAYER_WEAPON_DB );
  1686. }
  1687. // modify gain if sound source not visible to player
  1688. gain = gain * SND_GetGainObscured( ch, fplayersound, flooping, bAttenuated );
  1689. if (snd_showstart.GetInt() == 6)
  1690. {
  1691. DevMsg( "(gain %1.3f : dist ft %1.1f) ", gain, (float)dist/12.0 );
  1692. snd_showstart.SetValue(5); // display once
  1693. }
  1694. return gain;
  1695. }
  1696. }
  1697. // always ramp channel gain changes over time
  1698. // returns ramped gain, given new target gain
  1699. #define SND_GAIN_FADE_TIME 0.25 // xfade seconds between obscuring gain changes
  1700. float SND_FadeToNewGain( channel_t *ch, float gain_new )
  1701. {
  1702. if ( gain_new == -1.0 )
  1703. {
  1704. // if -1 passed in, just keep fading to existing target
  1705. gain_new = ch->ob_gain_target;
  1706. }
  1707. // if first time updating, store new gain into gain & target, return
  1708. // if gain_new is close to existing gain, store new gain into gain & target, return
  1709. if ( ch->flags.bfirstpass || (fabs (gain_new - ch->ob_gain) < 0.01))
  1710. {
  1711. ch->ob_gain = gain_new;
  1712. ch->ob_gain_target = gain_new;
  1713. ch->ob_gain_inc = 0.0;
  1714. return gain_new;
  1715. }
  1716. // set up new increment to new target
  1717. float frametime = g_pSoundServices->GetHostFrametime();
  1718. float speed;
  1719. speed = ( frametime / SND_GAIN_FADE_TIME ) * (gain_new - ch->ob_gain);
  1720. ch->ob_gain_inc = fabs(speed);
  1721. // ch->ob_gain_inc = fabs(gain_new - ch->ob_gain) / 10.0;
  1722. ch->ob_gain_target = gain_new;
  1723. // if not hit target, keep approaching
  1724. if ( fabs( ch->ob_gain - ch->ob_gain_target ) > 0.01 )
  1725. {
  1726. ch->ob_gain = Approach( ch->ob_gain_target, ch->ob_gain, ch->ob_gain_inc );
  1727. }
  1728. else
  1729. {
  1730. // close enough, set gain = target
  1731. ch->ob_gain = ch->ob_gain_target;
  1732. }
  1733. return ch->ob_gain;
  1734. }
  1735. #define SND_TRACE_UPDATE_MAX 2 // max of N channels may be checked for obscured source per frame
  1736. static int g_snd_trace_count = 0; // total tracelines for gain obscuring made this frame
  1737. // All new sounds must traceline once,
  1738. // but cap the max number of tracelines performed per frame
  1739. // for longer or looping sounds to SND_TRACE_UPDATE_MAX.
  1740. bool SND_ChannelOkToTrace( channel_t *ch )
  1741. {
  1742. // always trace first time sound is spatialized (doesn't update counter)
  1743. if ( ch->flags.bfirstpass )
  1744. {
  1745. ch->flags.bTraced = true;
  1746. return true;
  1747. }
  1748. // if already traced max channels this frame, return
  1749. if ( g_snd_trace_count >= SND_TRACE_UPDATE_MAX )
  1750. return false;
  1751. // ok to trace if this sound hasn't yet been traced in this round
  1752. if ( ch->flags.bTraced )
  1753. return false;
  1754. // set flag - don't traceline this sound again until all others have
  1755. // been traced
  1756. ch->flags.bTraced = true;
  1757. g_snd_trace_count++; // total traces this frame
  1758. return true;
  1759. }
  1760. // determine if we need to reset all flags for traceline limiting -
  1761. // this happens if we hit a frame whein no tracelines occur ie: all currently
  1762. // playing sounds are blocked.
  1763. void SND_ChannelTraceReset( void )
  1764. {
  1765. if ( g_snd_trace_count )
  1766. return;
  1767. // if no tracelines performed this frame, then reset all
  1768. // trace flags
  1769. for (int i = 0; i < total_channels; i++)
  1770. channels[i].flags.bTraced = false;
  1771. }
  1772. bool SND_IsLongWave( channel_t *pChannel )
  1773. {
  1774. CAudioSource *pSource = pChannel->sfx ? pChannel->sfx->pSource : NULL;
  1775. if ( pSource )
  1776. {
  1777. if ( pSource->IsStreaming() )
  1778. return true;
  1779. // UNDONE: Do this on long wave files too?
  1780. #if 0
  1781. float length = (float)pSource->SampleCount() / (float)pSource->SampleRate();
  1782. if ( length > 0.75f )
  1783. return true;
  1784. #endif
  1785. }
  1786. return false;
  1787. }
  1788. ConVar snd_obscured_gain_db( "snd_obscured_gain_dB", "-2.70", FCVAR_CHEAT ); // dB loss due to obscured sound source
  1789. // drop gain on channel if sound emitter obscured by
  1790. // world, unbroken windows, closed doors, large solid entities etc.
  1791. float SND_GetGainObscured( channel_t *ch, bool fplayersound, bool flooping, bool bAttenuated )
  1792. {
  1793. float gain = 1.0;
  1794. int count = 1;
  1795. float snd_gain_db; // dB loss due to obscured sound source
  1796. // Unattenuated sounds don't get obscured.
  1797. if ( !bAttenuated )
  1798. return 1.0f;
  1799. if ( fplayersound )
  1800. return gain;
  1801. // During signon just apply regular state machine since world hasn't been
  1802. // created or settled yet...
  1803. if ( !SND_IsInGame() )
  1804. {
  1805. if ( !toolframework->InToolMode() )
  1806. {
  1807. gain = SND_FadeToNewGain( ch, -1.0 );
  1808. }
  1809. return gain;
  1810. }
  1811. // don't do gain obscuring more than once on short one-shot sounds
  1812. if ( !ch->flags.bfirstpass && !ch->flags.isSentence && !flooping && !SND_IsLongWave(ch) )
  1813. {
  1814. gain = SND_FadeToNewGain( ch, -1.0 );
  1815. return gain;
  1816. }
  1817. snd_gain_db = snd_obscured_gain_db.GetFloat();
  1818. // if long or looping sound, process N channels per frame - set 'processed' flag, clear by
  1819. // cycling through all channels - this maintains a cap on traces per frame
  1820. if ( !SND_ChannelOkToTrace( ch ) )
  1821. {
  1822. // just keep updating fade to existing target gain - no new trace checking
  1823. gain = SND_FadeToNewGain( ch, -1.0 );
  1824. return gain;
  1825. }
  1826. // set up traceline from player eyes to sound emitting entity origin
  1827. Vector endpoint = ch->origin;
  1828. trace_t tr;
  1829. CTraceFilterWorldOnly filter; // UNDONE: also test for static props?
  1830. Ray_t ray;
  1831. ray.Init( MainViewOrigin(), endpoint );
  1832. g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
  1833. if (tr.DidHit() && tr.fraction < 0.99)
  1834. {
  1835. // can't see center of sound source:
  1836. // build extents based on dB sndlvl of source,
  1837. // test to see how many extents are visible,
  1838. // drop gain by snd_gain_db per extent hidden
  1839. Vector endpoints[4];
  1840. soundlevel_t sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult );
  1841. float radius;
  1842. Vector vsrc_forward;
  1843. Vector vsrc_right;
  1844. Vector vsrc_up;
  1845. Vector vecl;
  1846. Vector vecr;
  1847. Vector vecl2;
  1848. Vector vecr2;
  1849. int i;
  1850. // get radius
  1851. if ( ch->radius > 0 )
  1852. radius = ch->radius;
  1853. else
  1854. radius = dB_To_Radius( sndlvl); // approximate radius from soundlevel
  1855. // set up extent endpoints - on upward or downward diagonals, facing player
  1856. for (i = 0; i < 4; i++)
  1857. endpoints[i] = endpoint;
  1858. // vsrc_forward is normalized vector from sound source to listener
  1859. VectorSubtract( listener_origin, endpoint, vsrc_forward );
  1860. VectorNormalize( vsrc_forward );
  1861. VectorVectors( vsrc_forward, vsrc_right, vsrc_up );
  1862. VectorAdd( vsrc_up, vsrc_right, vecl );
  1863. // if src above listener, force 'up' vector to point down - create diagonals up & down
  1864. if ( endpoint.z > listener_origin.z + (10 * 12) )
  1865. vsrc_up.z = -vsrc_up.z;
  1866. VectorSubtract( vsrc_up, vsrc_right, vecr );
  1867. VectorNormalize( vecl );
  1868. VectorNormalize( vecr );
  1869. // get diagonal vectors from sound source
  1870. vecl2 = radius * vecl;
  1871. vecr2 = radius * vecr;
  1872. vecl = (radius / 2.0) * vecl;
  1873. vecr = (radius / 2.0) * vecr;
  1874. // endpoints from diagonal vectors
  1875. endpoints[0] += vecl;
  1876. endpoints[1] += vecr;
  1877. endpoints[2] += vecl2;
  1878. endpoints[3] += vecr2;
  1879. // drop gain for each point on radius diagonal that is obscured
  1880. for (count = 0, i = 0; i < 4; i++)
  1881. {
  1882. // UNDONE: some endpoints are in walls - in this case, trace from the wall hit location
  1883. ray.Init( MainViewOrigin(), endpoints[i] );
  1884. g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
  1885. if (tr.DidHit() && tr.fraction < 0.99 && !tr.startsolid )
  1886. {
  1887. count++; // skip first obscured point: at least 2 points + center should be obscured to hear db loss
  1888. if (count > 1)
  1889. gain = gain * dB_To_Gain( snd_gain_db );
  1890. }
  1891. }
  1892. }
  1893. if ( flooping && snd_showstart.GetInt() == 7)
  1894. {
  1895. static float g_drop_prev = 0;
  1896. float drop = (count-1) * snd_gain_db;
  1897. if (drop != g_drop_prev)
  1898. {
  1899. DevMsg( "dB drop: %1.4f \n", drop);
  1900. g_drop_prev = drop;
  1901. }
  1902. }
  1903. // crossfade to new gain
  1904. gain = SND_FadeToNewGain( ch, gain );
  1905. return gain;
  1906. }
  1907. // convert sound db level to approximate sound source radius,
  1908. // used only for determining how much of sound is obscured by world
  1909. #define SND_RADIUS_MAX (20.0 * 12.0) // max sound source radius
  1910. #define SND_RADIUS_MIN (2.0 * 12.0) // min sound source radius
  1911. inline float dB_To_Radius ( float db )
  1912. {
  1913. float radius = SND_RADIUS_MIN + (SND_RADIUS_MAX - SND_RADIUS_MIN) * (db - SND_DB_MIN) / (SND_DB_MAX - SND_DB_MIN);
  1914. return radius;
  1915. }
  1916. struct snd_spatial_t
  1917. {
  1918. int chan; // 0..4 cycles through up to 5 channels
  1919. int cycle; // 0..2 cycles through 3 vectors per channel
  1920. int dist[5][3]; // stores last 3 channel distance values [channel][cycle]
  1921. float value_prev[5]; // previous value per channel
  1922. double last_change;
  1923. };
  1924. bool g_ssp_init = false;
  1925. snd_spatial_t g_ssp;
  1926. // return 0..1 percent difference between a & b
  1927. float PercentDifference( float a, float b )
  1928. {
  1929. float vp;
  1930. if (!(int)a && !(int)b)
  1931. return 0.0;
  1932. if (!(int)a || !(int)b)
  1933. return 1.0;
  1934. if (a > b)
  1935. vp = b / a;
  1936. else
  1937. vp = a / b;
  1938. return (1.0 - vp);
  1939. }
  1940. // NOTE: Do not change SND_WALL_TRACE_LEN without also changing PRC_MDY6 delay value in snd_dsp.cpp!
  1941. #define SND_WALL_TRACE_LEN (100.0*12.0) // trace max of 100' = max of 100 milliseconds of linear delay
  1942. #define SND_SPATIAL_WAIT (0.25) // seconds to wait between traces
  1943. // change mod delay value on chan 0..3 to v (inches)
  1944. void DSP_SetSpatialDelay( int chan, float v )
  1945. {
  1946. // remap delay value 0..1200 to 1.0 to -1.0 for modulation
  1947. float value = ( v / SND_WALL_TRACE_LEN) - 1.0; // -1.0...0
  1948. value = value * 2.0; // -2.0...0
  1949. value += 1.0; // -1.0...1.0 (0...1200)
  1950. value *= -1.0; // 1.0...-1.0 (0...1200)
  1951. // assume first processor in dsp_spatial is the modulating delay unit for DSP_ChangePresetValue
  1952. int iproc = 0;
  1953. DSP_ChangePresetValue( idsp_spatial, chan, iproc, value );
  1954. /*
  1955. if (chan & 0x01)
  1956. DevMsg("RDly: %3.0f \n", v/12 );
  1957. else
  1958. DevMsg("LDly: %3.0f \n", v/12 );
  1959. */
  1960. }
  1961. // use non-feedback delay to stereoize (or make quad, or quad + center) the mono dsp_room fx,
  1962. // This simulates the average sum of delays caused by reflections
  1963. // from the left and right walls relative to the player. The average delay
  1964. // difference between left & right wall is (l + r)/2. This becomes the average
  1965. // delay difference between left & right ear.
  1966. // call at most once per frame to update player->wall spatial delays
  1967. void SND_SetSpatialDelays()
  1968. {
  1969. VPROF("SoundSpatialDelays");
  1970. float dist, v, vp;
  1971. Vector v_dir, v_dir2;
  1972. int chan_max = (g_AudioDevice->IsSurround() ? 4 : 2) + (g_AudioDevice->IsSurroundCenter() ? 1 : 0); // 2, 4, 5 channels
  1973. // use listener_forward2d, which doesn't change when player looks up/down.
  1974. Vector listener_forward2d;
  1975. ConvertListenerVectorTo2D( &listener_forward2d, &listener_right );
  1976. // init struct if 1st time through
  1977. if ( !g_ssp_init )
  1978. {
  1979. Q_memset(&g_ssp, 0, sizeof(snd_spatial_t));
  1980. g_ssp_init = true;
  1981. }
  1982. // return if dsp_spatial is 0
  1983. if ( !dsp_spatial.GetInt() )
  1984. return;
  1985. // if listener has not been updated, do nothing
  1986. if ((listener_origin == vec3_origin) &&
  1987. (listener_forward == vec3_origin) &&
  1988. (listener_right == vec3_origin) &&
  1989. (listener_up == vec3_origin) )
  1990. return;
  1991. if ( !SND_IsInGame() )
  1992. return;
  1993. // get time
  1994. double dtime = g_pSoundServices->GetHostTime();
  1995. // compare to previous time - if starting new check - don't check for new room until timer expires
  1996. if (!g_ssp.chan && !g_ssp.cycle)
  1997. {
  1998. if (fabs(dtime - g_ssp.last_change) < SND_SPATIAL_WAIT)
  1999. return;
  2000. }
  2001. // cycle through forward, left, rearward vectors, averaging to get left/right delay
  2002. // count[chan][cycle] 0,1 0,2 0,3 1,1 1,2 1,3 2,1 2,2 2,3 ...
  2003. g_ssp.cycle++;
  2004. if (g_ssp.cycle == 3)
  2005. {
  2006. g_ssp.cycle = 0;
  2007. // cycle through front left, front right, rear left, rear right, front center delays
  2008. g_ssp.chan++;
  2009. if (g_ssp.chan >= chan_max )
  2010. g_ssp.chan = 0;
  2011. }
  2012. // set up traceline from player eyes to surrounding walls
  2013. switch( g_ssp.chan )
  2014. {
  2015. default:
  2016. case 0: // front left: trace max 100' 'cone' to player's left
  2017. if ( g_AudioDevice->IsSurround() )
  2018. {
  2019. // 4-5 speaker case - front left
  2020. v_dir = (-listener_right + listener_forward2d) / 2.0;
  2021. v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? -listener_right * 0.5: listener_forward2d * 0.5) : v_dir;
  2022. }
  2023. else
  2024. {
  2025. // 2 speaker case - left
  2026. v_dir = listener_right * -1.0;
  2027. v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_forward2d * 0.5 : -listener_forward2d * 0.5) : v_dir;
  2028. v_dir = (v_dir + v_dir2) / 2.0;
  2029. }
  2030. break;
  2031. case 1: // front right: trace max 100' 'cone' to player's right
  2032. if ( g_AudioDevice->IsSurround() )
  2033. {
  2034. // 4-5 speaker case - front right
  2035. v_dir = (listener_right + listener_forward2d) / 2.0;
  2036. v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.5: listener_forward2d * 0.5) : v_dir;
  2037. }
  2038. else
  2039. {
  2040. // 2 speaker case - right
  2041. v_dir = listener_right;
  2042. v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_forward2d * 0.5 : -listener_forward2d * 0.5) : v_dir;
  2043. v_dir = (v_dir + v_dir2) / 2.0;
  2044. }
  2045. break;
  2046. case 2: // rear left: trace max 100' 'cone' to player's rear left
  2047. v_dir = (listener_right + listener_forward2d) / -2.0;
  2048. v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? -listener_right * 0.5 : -listener_forward2d * 0.5) : v_dir;
  2049. break;
  2050. case 3: // rear right: trace max 100' 'cone' to player's rear right
  2051. v_dir = (listener_right - listener_forward2d) / 2.0;
  2052. v_dir = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.5: -listener_forward2d * 0.5) : v_dir;
  2053. break;
  2054. case 4: // front center: trace max 100' 'cone' to player's front
  2055. v_dir = listener_forward2d;
  2056. v_dir2 = g_ssp.cycle ? (g_ssp.cycle == 1 ? listener_right * 0.15 : -listener_right * 0.15) : v_dir;
  2057. v_dir = (v_dir + v_dir2);
  2058. break;
  2059. }
  2060. Vector endpoint;
  2061. trace_t tr;
  2062. CTraceFilterWorldOnly filter;
  2063. endpoint = MainViewOrigin() + v_dir * SND_WALL_TRACE_LEN;
  2064. Ray_t ray;
  2065. ray.Init( MainViewOrigin(), endpoint );
  2066. g_pEngineTraceClient->TraceRay( ray, MASK_BLOCK_AUDIO, &filter, &tr );
  2067. dist = SND_WALL_TRACE_LEN;
  2068. if ( tr.DidHit() )
  2069. {
  2070. dist = VectorLength( tr.endpos - MainViewOrigin() );
  2071. }
  2072. g_ssp.dist[g_ssp.chan][g_ssp.cycle] = dist;
  2073. // set new result in dsp_spatial delay params when all delay values have been filled in
  2074. if (!g_ssp.cycle && !g_ssp.chan)
  2075. {
  2076. // update delay for each channel
  2077. for (int chan = 0; chan < chan_max; chan++)
  2078. {
  2079. // compute average of 3 traces per channel
  2080. v = (g_ssp.dist[chan][0] + g_ssp.dist[chan][1] + g_ssp.dist[chan][2]) / 3.0;
  2081. vp = g_ssp.value_prev[chan];
  2082. // only change if 10% difference from previous
  2083. if ((vp != v) && int(v) && (PercentDifference( v, vp ) >= 0.1))
  2084. {
  2085. // update when we have data for all L/R && RL/RR channels...
  2086. if (chan & 0x1)
  2087. {
  2088. float vr = fpmin( v, (50*12.0f) );
  2089. float vl = fpmin(g_ssp.value_prev[chan-1], (50*12.0f));
  2090. /* UNDONE: not needed, now that this applies only to dsp 'room' buffer
  2091. // ensure minimum separation = average distance to walls
  2092. float dmin = (vl + vr) / 2.0; // average distance to walls
  2093. float d = vl - vr; // l/r separation
  2094. // if separation is less than average, increase min
  2095. if (abs(d) < dmin/2)
  2096. {
  2097. if (vl > vr)
  2098. vl += dmin/2 - d;
  2099. else
  2100. vr += dmin/2 - d;
  2101. }
  2102. */
  2103. DSP_SetSpatialDelay(chan-1, vl);
  2104. DSP_SetSpatialDelay(chan, vr);
  2105. }
  2106. // update center chan
  2107. if (chan == 4)
  2108. {
  2109. float vl = fpmin( v, (50*12.0f) );
  2110. DSP_SetSpatialDelay(chan, vl);
  2111. }
  2112. }
  2113. g_ssp.value_prev[chan] = v;
  2114. }
  2115. // update wait timer now that all values have been checked
  2116. g_ssp.last_change = dtime;
  2117. }
  2118. }
  2119. // Dsp Automatic Selection:
  2120. // a) enabled by setting dsp_room to DSP_AUTOMATIC. Subsequently, dsp_automatic is the actual dsp value for dsp_room.
  2121. // b) disabled by setting dsp_room to anything else
  2122. // c) while enabled, detection nodes are placed as player moves into a new space
  2123. // i. at each node, a new dsp setting is calculated and dsp_automatic is set to an appropriate preset
  2124. // ii. new nodes are set when player moves out of sight of previous node
  2125. // iii. moving into line of sight of a detection node causes closest node to player to set dsp_automatic
  2126. // see void DAS_CheckNewRoomDSP( ) for main entrypoint
  2127. ConVar das_debug( "adsp_debug", "0", FCVAR_ARCHIVE );
  2128. // >0: draw blue dsp detection node location
  2129. // >1: draw green room trace height detection bars
  2130. // 3: draw yellow horizontal trace bars for room width/depth detection
  2131. // 4: draw yellow upward traces for height detection
  2132. // 5: draw teal box around all props around player
  2133. // 6: draw teal box around room as detected
  2134. #define DAS_CWALLS 20 // # of wall traces to save for calculating room dimensions
  2135. #define DAS_ROOM_TRACE_LEN (400.0*12.0) // max size of trace to check for room dimensions
  2136. #define DAS_AUTO_WAIT 0.25 // wait min of n seconds between dsp_room changes and update checks
  2137. #define DAS_WIDTH_MIN 0.4 // min % change in avg width of any wall pair to cause new dsp
  2138. #define DAS_REFL_MIN 0.5 // min % change in avg refl of any wall to cause new dsp
  2139. #define DAS_SKYHIT_MIN 0.8 // min % change in # of sky hits per wall
  2140. #define DAS_DIST_MIN (4.0 * 12.0) // min distance between room dsp changes
  2141. #define DAS_DIST_MAX (40.0 * 12.0) // max distance to preserve room dsp changes
  2142. #define DAS_DIST_MIN_OUTSIDE (6.0 * 12.0) // min distance between room dsp changes outside
  2143. #define DAS_DIST_MAX_OUTSIDE (100.0 * 12.0) // max distance to preserve room dsp changes outside
  2144. #define IVEC_DIAG_UP 8 // start of diagonal up vectors
  2145. #define IVEC_UP 18 // up vector
  2146. #define IVEC_DOWN 19 // down vector
  2147. #define DAS_REFLECTIVITY_NORM 0.5
  2148. #define DAS_REFLECTIVITY_SKY 0.0
  2149. // auto dsp room struct
  2150. struct das_room_t
  2151. {
  2152. int dist[DAS_CWALLS]; // distance in units from player to axis aligned and diagonal walls
  2153. float reflect[DAS_CWALLS]; // acoustic reflectivity per wall
  2154. float skyhits[DAS_CWALLS]; // every sky hit adds 0.1
  2155. Vector hit[DAS_CWALLS]; // location of trace hit on wall - used for calculating average centers
  2156. Vector norm[DAS_CWALLS]; // wall normal at hit location
  2157. Vector vplayer; // 'frozen' location above player's head
  2158. Vector vplayer_eyes; // 'frozen' location player's eyes
  2159. int width_max; // max width
  2160. int length_max; // max length
  2161. int height_max; // max height
  2162. float refl_avg; // running average of reflectivity of all walls
  2163. float refl_walls[6]; // left,right,front,back,ceiling,floor reflectivities
  2164. float sky_pct; // percent of sky hits
  2165. Vector room_mins; // room bounds
  2166. Vector room_maxs;
  2167. double last_dsp_change; // time since last dsp change
  2168. float diffusion; // 0..1.0 check radius (avg of width_avg) for # of props - scale diffusion based on # found
  2169. short iwall; // cycles through walls 0..5, ensuring only one trace per frame
  2170. short ent_count; // count of entities found in radius
  2171. bool bskyabove; // true if sky found above player (ie: outside)
  2172. bool broomready; // true if all distances are filled in and room is ready to check
  2173. short lowceiling; // if non-zero, ceiling directly above player if < 112 units
  2174. };
  2175. // dsp detection node
  2176. struct das_node_t
  2177. {
  2178. Vector vplayer; // position
  2179. bool fused; // true if valid node
  2180. bool fseesplayer; // true if node sees player on last check
  2181. short dsp_preset; // preset
  2182. int range_min; // min,max detection ranges
  2183. int range_max;
  2184. int dist; // last distance to player
  2185. // room parameters when node was created:
  2186. das_room_t room;
  2187. };
  2188. #define DAS_CNODES 40 // keep around last n nodes - must be same as DSP_CAUTO_PRESETS!!!
  2189. das_node_t g_das_nodes[DAS_CNODES]; // all dsp detection nodes
  2190. das_node_t *g_pdas_last_node = NULL; // last node that saw player
  2191. int g_das_check_next; // next node to check
  2192. int g_das_store_next; // next place to store node
  2193. bool g_das_all_checked; // true if all nodes checked
  2194. int g_das_checked_count; // count of nodes checked in latest pass
  2195. das_room_t g_das_room; // room detector
  2196. bool g_bdas_room_init = 0;
  2197. bool g_bdas_init_nodes = 0;
  2198. bool g_bdas_create_new_node = 0;
  2199. bool DAS_TraceNodeToPlayer( das_room_t *proom, das_node_t *pnode );
  2200. void DAS_InitAutoRoom( das_room_t *proom);
  2201. void DAS_DebugDrawTrace ( trace_t *ptr, int r, int g, int b, float duration, int imax );
  2202. Vector g_das_vec3[DAS_CWALLS]; // trace vectors to walls, ceiling, floor
  2203. void DAS_InitNodes( void )
  2204. {
  2205. Q_memset(g_das_nodes, 0, sizeof(das_node_t) * DAS_CNODES);
  2206. g_das_check_next = 0;
  2207. g_das_store_next = 0;
  2208. g_das_all_checked = 0;
  2209. g_das_checked_count = 0;
  2210. // init all rooms
  2211. for (int i = 0; i < DAS_CNODES; i++)
  2212. DAS_InitAutoRoom( &(g_das_nodes[i].room) );
  2213. // init trace vectors
  2214. // set up trace vectors for max, min width
  2215. float vl = DAS_ROOM_TRACE_LEN;
  2216. float vlu = DAS_ROOM_TRACE_LEN * 0.52;
  2217. float vlu2 = DAS_ROOM_TRACE_LEN * 0.48; // don't use 'perfect' diagonals
  2218. g_das_vec3[0].Init(vl, 0.0, 0.0); // x left
  2219. g_das_vec3[1].Init(-vl, 0.0, 0.0); // x right
  2220. g_das_vec3[2].Init(0.0, vl, 0.0); // y front
  2221. g_das_vec3[3].Init(0.0, -vl, 0.0); // y back
  2222. g_das_vec3[4].Init(-vlu, vlu2, 0.0); // diagonal front left
  2223. g_das_vec3[5].Init(vlu, -vlu2, 0.0); // diagonal rear right
  2224. g_das_vec3[6].Init(vlu, vlu2, 0.0); // diagonal front right
  2225. g_das_vec3[7].Init(-vlu, -vlu2, 0.0); // diagonal rear left
  2226. // set up trace vectors for max height - on x=y diagonal
  2227. g_das_vec3[8].Init(vlu, vlu2, vlu/2.0); // front right up A x,y,z/2 (IVEC_DIAG_UP)
  2228. g_das_vec3[9].Init(vlu, vlu2, vlu); // front right up B x,y,z
  2229. g_das_vec3[10].Init(vlu/2.0, vlu2/2.0, vlu); // front right up C x/2,y/2,z
  2230. g_das_vec3[11].Init(-vlu, -vlu2, vlu/2.0); // rear left up A -x,-y,z/2
  2231. g_das_vec3[12].Init(-vlu, -vlu2, vlu); // rear left up B -x,-y,z
  2232. g_das_vec3[13].Init(-vlu/2.0, -vlu2/2.0, vlu); // rear left up C -x/2,-y/2,z
  2233. // set up trace vectors for max height - on x axis & y axis
  2234. g_das_vec3[14].Init(-vlu, 0, vlu); // left up B -x,0,z
  2235. g_das_vec3[15].Init(0, vlu/2.0, vlu); // front up C -x/2,0,z
  2236. g_das_vec3[16].Init(0, -vlu, vlu); // rear up B x,0,z
  2237. g_das_vec3[17].Init(vlu/2.0, 0, vlu); // right up C x/2,0,z
  2238. g_das_vec3[18].Init(0.0, 0.0, vl); // up (IVEC_UP)
  2239. g_das_vec3[19].Init(0.0, 0.0, -vl); // down (IVEC_DOWN)
  2240. }
  2241. void DAS_InitAutoRoom( das_room_t *proom)
  2242. {
  2243. Q_memset(proom, 0, sizeof (das_room_t));
  2244. }
  2245. // reset all nodes for next round of visibility checks between player & nodes
  2246. void DAS_ResetNodes( void )
  2247. {
  2248. for (int i = 0; i < DAS_CNODES; i++)
  2249. {
  2250. g_das_nodes[i].fseesplayer = false;
  2251. g_das_nodes[i].dist = 0;
  2252. }
  2253. g_das_all_checked = false;
  2254. g_das_checked_count = 0;
  2255. g_bdas_create_new_node = false;
  2256. }
  2257. // utility function - return next index, wrap at max
  2258. int DAS_GetNextIndex( int *pindex, int max )
  2259. {
  2260. int i = *pindex;
  2261. int j;
  2262. j = i+1;
  2263. if ( j >= max )
  2264. j = 0;
  2265. *pindex = j;
  2266. return i;
  2267. }
  2268. // returns true if dsp node is within range of player
  2269. bool DAS_NodeInRange( das_room_t *proom, das_node_t *pnode )
  2270. {
  2271. float dist;
  2272. dist = VectorLength( proom->vplayer - pnode->vplayer );
  2273. // player can still see previous room selection point, and it's less than n feet away,
  2274. // then flag this node as visible
  2275. pnode->dist = dist;
  2276. return ( dist <= pnode->range_max );
  2277. }
  2278. // update next valid node - set up internal node state if it can see player
  2279. // called once per frame
  2280. // returns true if all nodes have been checked
  2281. bool DAS_CheckNextNode( das_room_t *proom )
  2282. {
  2283. int i, j;
  2284. if ( g_das_all_checked )
  2285. return true;
  2286. // find next valid node
  2287. for (j = 0; j < DAS_CNODES; j++)
  2288. {
  2289. // track number of nodes checked
  2290. g_das_checked_count++;
  2291. // get next node in range to check
  2292. i = DAS_GetNextIndex( &g_das_check_next, DAS_CNODES );
  2293. if ( g_das_nodes[i].fused && DAS_NodeInRange( proom, &(g_das_nodes[i]) ) )
  2294. {
  2295. // trace to see if player can still see node,
  2296. // if so stop checking
  2297. if ( DAS_TraceNodeToPlayer( proom, &(g_das_nodes[i]) ))
  2298. goto checknode_exit;
  2299. }
  2300. }
  2301. checknode_exit:
  2302. // flag that all nodes have been checked
  2303. if ( g_das_checked_count >= DAS_CNODES )
  2304. g_das_all_checked = true;
  2305. return g_das_all_checked;
  2306. }
  2307. int DAS_GetNextNodeIndex()
  2308. {
  2309. return g_das_store_next;
  2310. }
  2311. // store new node for room
  2312. void DAS_StoreNode( das_room_t *proom, int dsp_preset)
  2313. {
  2314. // overwrite node in cyclic list
  2315. int i = DAS_GetNextIndex( &g_das_store_next, DAS_CNODES );
  2316. g_das_nodes[i].dsp_preset = dsp_preset;
  2317. g_das_nodes[i].fused = true;
  2318. g_das_nodes[i].vplayer = proom->vplayer;
  2319. // calculate node scanning range_max based on room size
  2320. if ( !proom->bskyabove )
  2321. {
  2322. // inside range - halls & tunnels have nodes every 5*width
  2323. g_das_nodes[i].range_max = fpmin((int)DAS_DIST_MAX, min(proom->width_max * 5, proom->length_max) );
  2324. g_das_nodes[i].range_min = DAS_DIST_MIN;
  2325. }
  2326. else
  2327. {
  2328. // outside range
  2329. g_das_nodes[i].range_max = DAS_DIST_MAX_OUTSIDE;
  2330. g_das_nodes[i].range_min = DAS_DIST_MIN_OUTSIDE;
  2331. }
  2332. g_das_nodes[i].fseesplayer = false;
  2333. g_das_nodes[i].dist = 0;
  2334. g_das_nodes[i].room = *proom;
  2335. // update last node visible as this node
  2336. g_pdas_last_node = &(g_das_nodes[i]);
  2337. }
  2338. // check all updated nodes,
  2339. // return dsp_preset of largest node (by area) that can see player
  2340. // return -1 if no preset found
  2341. // NOTE: outside nodes can't see player if player is inside and vice versa
  2342. // foutside is true if player is outside
  2343. int DAS_GetDspPreset( bool foutside )
  2344. {
  2345. int dsp_preset = -1;
  2346. int i;
  2347. // int dist_min = 100000;
  2348. int area_max = 0;
  2349. int area;
  2350. // find node that represents room with greatest floor area, return its preset.
  2351. for (i = 0; i < DAS_CNODES; i++)
  2352. {
  2353. if (g_das_nodes[i].fused && g_das_nodes[i].fseesplayer)
  2354. {
  2355. area = (g_das_nodes[i].room.width_max * g_das_nodes[i].room.length_max);
  2356. if ( g_das_nodes[i].room.bskyabove == foutside )
  2357. {
  2358. if (area > area_max)
  2359. {
  2360. area_max = area;
  2361. dsp_preset = g_das_nodes[i].dsp_preset;
  2362. // save pointer to last node that saw player
  2363. g_pdas_last_node = &(g_das_nodes[i]);
  2364. }
  2365. }
  2366. /*
  2367. // find nearest node, return its preset
  2368. if (g_das_nodes[i].dist < dist_min)
  2369. {
  2370. if ( g_das_nodes[i].room.bskyabove == foutside )
  2371. {
  2372. dist_min = g_das_nodes[i].dist;
  2373. dsp_preset = g_das_nodes[i].dsp_preset;
  2374. // save pointer to last node that saw player
  2375. g_pdas_last_node = &(g_das_nodes[i]);
  2376. }
  2377. }
  2378. */
  2379. }
  2380. }
  2381. return dsp_preset;
  2382. }
  2383. // custom trace filter:
  2384. // a) never hit player or monsters or entities
  2385. // b) always hit world, or moveables or static props
  2386. class CTraceFilterDAS : public ITraceFilter
  2387. {
  2388. public:
  2389. bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  2390. {
  2391. IClientUnknown *pUnk = static_cast<IClientUnknown*>(pHandleEntity);
  2392. IClientEntity *pEntity;
  2393. if ( !pUnk )
  2394. return false;
  2395. // don't hit non-collideable props
  2396. if ( StaticPropMgr()->IsStaticProp( pHandleEntity ) )
  2397. {
  2398. ICollideable *pCollide = StaticPropMgr()->GetStaticProp( pHandleEntity);
  2399. if (!pCollide)
  2400. return false;
  2401. }
  2402. // don't hit any ents
  2403. pEntity = pUnk->GetIClientEntity();
  2404. if ( pEntity )
  2405. return false;
  2406. return true;
  2407. }
  2408. virtual TraceType_t GetTraceType() const
  2409. {
  2410. return TRACE_EVERYTHING_FILTER_PROPS;
  2411. }
  2412. };
  2413. #define DAS_TRACE_MASK (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_WINDOW)
  2414. // returns true if clear line exists between node and player
  2415. // if node can see player, sets up node distance and flag fseesplayer
  2416. bool DAS_TraceNodeToPlayer( das_room_t *proom, das_node_t *pnode )
  2417. {
  2418. trace_t trP;
  2419. CTraceFilterDAS filterP;
  2420. bool fseesplayer = false;
  2421. float dist;
  2422. Ray_t ray;
  2423. ray.Init( proom->vplayer, pnode->vplayer );
  2424. g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterP, &trP );
  2425. dist = VectorLength( proom->vplayer - pnode->vplayer );
  2426. // player can still see previous room selection point, and it's less than n feet away,
  2427. // then flag this node as visible
  2428. if ( !trP.DidHit() && (dist <= DAS_DIST_MAX) )
  2429. {
  2430. fseesplayer = true;
  2431. pnode->dist = dist;
  2432. }
  2433. pnode->fseesplayer = fseesplayer;
  2434. return fseesplayer;
  2435. }
  2436. // update room boundary maxs, mins
  2437. void DAS_SetRoomBounds( das_room_t *proom, Vector &hit, bool bheight )
  2438. {
  2439. Vector maxs, mins;
  2440. maxs = proom->room_maxs;
  2441. mins = proom->room_mins;
  2442. if (!bheight)
  2443. {
  2444. if (hit.x > maxs.x)
  2445. maxs.x = hit.x;
  2446. if (hit.x < mins.x)
  2447. mins.x = hit.x;
  2448. if (hit.z > maxs.z)
  2449. maxs.z = hit.z;
  2450. if (hit.z < mins.z)
  2451. mins.z = hit.z;
  2452. }
  2453. if (bheight)
  2454. {
  2455. if (hit.y > maxs.y)
  2456. maxs.y = hit.y;
  2457. if (hit.y < mins.y)
  2458. mins.y = hit.y;
  2459. }
  2460. proom->room_maxs = maxs;
  2461. proom->room_mins = mins;
  2462. }
  2463. // when all walls are updated, calculate max length, width, height, reflectivity, sky hit%, room center
  2464. // returns true if room parameters are in good location to place a node
  2465. // returns false if room parameters are not in good location to place a node
  2466. // note: false occurs if up vector doesn't hit sky, but one or more up diagonal vectors do hit sky
  2467. bool DAS_CalcRoomProps( das_room_t *proom )
  2468. {
  2469. int length_max = 0;
  2470. int width_max = 0;
  2471. int height_max = 0;
  2472. int dist[4];
  2473. float area1, area2;
  2474. int height;
  2475. int i;
  2476. int j;
  2477. int k;
  2478. bool b_diaghitsky = false;
  2479. // reject this location if up vector doesn't hit sky, but
  2480. // one or more up diagonals do hit sky -
  2481. // in this case, player is under a slight overhang, narrow bridge, or
  2482. // standing just inside a window or doorway. keep looking for better node location
  2483. for (i = IVEC_DIAG_UP; i < IVEC_UP; i++)
  2484. {
  2485. if (proom->skyhits[i] > 0.0)
  2486. b_diaghitsky = true;
  2487. }
  2488. if (b_diaghitsky && !(proom->skyhits[IVEC_UP] > 0.0))
  2489. return false;
  2490. // get all distance pairs
  2491. for (i = 0; i < IVEC_DIAG_UP; i+=2)
  2492. dist[i/2] = proom->dist[i] + proom->dist[i+1]; // 1st pair is width
  2493. // if areas differ by more than 25%
  2494. // select the pair with the greater area
  2495. // if areas do not differ by more than 25%, select the pair with the
  2496. // longer measured distance. Filters incorrect selection due to diagonals.
  2497. area1 = (float)(dist[0] * dist[1]);
  2498. area2 = (float)(dist[2] * dist[3]);
  2499. area1 = (int)area1 == 0 ? 1.0 : area1;
  2500. area2 = (int)area2 == 0 ? 1.0 : area2;
  2501. if ( PercentDifference(area1, area2) > 0.25 )
  2502. {
  2503. // areas are more than 25% different - select pair with greater area
  2504. j = area1 > area2 ? 0 : 2;
  2505. }
  2506. else
  2507. {
  2508. // select pair with longer measured distance
  2509. int iMaxDist = 0; // index to max dist
  2510. int dmax = 0;
  2511. for (i = 0; i < 4; i++)
  2512. {
  2513. if (dist[i] > dmax)
  2514. {
  2515. dmax = dist[i];
  2516. iMaxDist = i;
  2517. }
  2518. }
  2519. j = iMaxDist > 1 ? 2 : 0;
  2520. }
  2521. // width is always the smaller of the dimensions
  2522. width_max = min (dist[j], dist[j+1]);
  2523. length_max = max (dist[j], dist[j+1]);
  2524. // get max height
  2525. for (i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
  2526. {
  2527. height = proom->dist[i];
  2528. if (height > height_max)
  2529. height_max = height;
  2530. }
  2531. proom->length_max = length_max;
  2532. proom->width_max = width_max;
  2533. proom->height_max = height_max;
  2534. // get room max,min from chosen width, depth
  2535. // 0..3 or 4..7
  2536. for ( i = j*2; i < 4+(j*2); i++)
  2537. DAS_SetRoomBounds( proom, proom->hit[i], false );
  2538. // get room height min from down trace
  2539. proom->room_mins.z = proom->hit[IVEC_DOWN].z;
  2540. // reset room height max to player trace height
  2541. proom->room_maxs.z = proom->vplayer.z;
  2542. // draw box around room max,min
  2543. if (das_debug.GetInt() == 6)
  2544. {
  2545. // draw box around all objects detected
  2546. Vector maxs = proom->room_maxs;
  2547. Vector mins = proom->room_mins;
  2548. Vector orig = (maxs + mins) / 2.0;
  2549. Vector absMax = maxs - orig;
  2550. Vector absMin = mins - orig;
  2551. CDebugOverlay::AddBoxOverlay( orig, absMax, absMin, vec3_angle, 255, 0, 255, 0, 60.0f );
  2552. }
  2553. // calculate average reflectivity
  2554. float refl = 0.0;
  2555. // average reflectivity for walls
  2556. // 0..3 or 4..7
  2557. for ( k = 0, i = j*2; i < 4+(j*2); i++, k++)
  2558. {
  2559. refl += proom->reflect[i];
  2560. proom->refl_walls[k] = proom->reflect[i];
  2561. }
  2562. // assume ceiling is open
  2563. proom->refl_walls[4] = 0.0;
  2564. // get ceiling reflectivity, if any non zero
  2565. for ( i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
  2566. {
  2567. if (proom->reflect[i] == 0.0)
  2568. {
  2569. // if any upward trace hit sky, exit;
  2570. // ceiling reflectivity is 0.0
  2571. proom->refl_walls[4] = 0.0;
  2572. i = IVEC_DOWN; // exit loop
  2573. }
  2574. else
  2575. {
  2576. // upward trace didn't hit sky, keep checking
  2577. proom->refl_walls[4] = proom->reflect[i];
  2578. }
  2579. }
  2580. // add in ceiling reflectivity, if any
  2581. refl += proom->refl_walls[4];
  2582. // get floor reflectivity
  2583. refl += proom->reflect[IVEC_DOWN];
  2584. proom->refl_walls[5] = proom->reflect[IVEC_DOWN];
  2585. proom->refl_avg = refl / 6.0;
  2586. // calculate sky hit percent for this wall
  2587. float sky_pct = 0.0;
  2588. // 0..3 or 4..7
  2589. for ( i = j*2; i < 4+(j*2); i++)
  2590. sky_pct += proom->skyhits[i];
  2591. for ( i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
  2592. {
  2593. if (proom->skyhits[i] > 0.0)
  2594. {
  2595. // if any upward trace hit sky, exit loop
  2596. sky_pct += proom->skyhits[i];
  2597. i = IVEC_DOWN;
  2598. }
  2599. }
  2600. // get floor skyhit
  2601. sky_pct += proom->skyhits[IVEC_DOWN];
  2602. proom->sky_pct = sky_pct;
  2603. // check for sky above
  2604. proom->bskyabove = false;
  2605. for (i = IVEC_DIAG_UP; i < IVEC_DOWN; i++)
  2606. {
  2607. if (proom->skyhits[i] > 0.0)
  2608. proom->bskyabove = true;
  2609. }
  2610. return true;
  2611. }
  2612. // return true if trace hit solid
  2613. // return false if trace hit sky or didn't hit anything
  2614. bool DAS_HitSolid( trace_t *ptr )
  2615. {
  2616. // if hit nothing return false
  2617. if (!ptr->DidHit())
  2618. return false;
  2619. // if hit sky, return false (not solid)
  2620. if (ptr->surface.flags & SURF_SKY)
  2621. return false;
  2622. return true;
  2623. }
  2624. // returns true if trace hit sky
  2625. bool DAS_HitSky( trace_t *ptr )
  2626. {
  2627. if (ptr->DidHit() && (ptr->surface.flags & SURF_SKY))
  2628. return true;
  2629. if (!ptr->DidHit() )
  2630. {
  2631. float dz = ptr->endpos.z - ptr->startpos.z;
  2632. if ( dz > 200*12.0f )
  2633. return true;
  2634. }
  2635. return false;
  2636. }
  2637. bool DAS_ScanningForHeight( das_room_t *proom )
  2638. {
  2639. return (proom->iwall >= IVEC_DIAG_UP);
  2640. }
  2641. bool DAS_ScanningForWidth( das_room_t *proom )
  2642. {
  2643. return (proom->iwall < IVEC_DIAG_UP);
  2644. }
  2645. bool DAS_ScanningForFloor( das_room_t *proom )
  2646. {
  2647. return (proom->iwall == IVEC_DOWN);
  2648. }
  2649. ConVar das_door_height("adsp_door_height", "112"); // standard door height hl2
  2650. ConVar das_wall_height("adsp_wall_height", "128"); // standard wall height hl2
  2651. ConVar das_low_ceiling("adsp_low_ceiling", "108"); // low ceiling height hl2
  2652. // set origin for tracing out to walls to point above player's head
  2653. // allows calculations over walls and floor obstacles, and above door openings
  2654. // WARNING: the current settings are optimal for skipping floor and ceiling clutter,
  2655. // and for detecting rooms without 'looking' through doors or windows. Don't change these cvars for hl2!
  2656. void DAS_SetTraceHeight( das_room_t *proom, trace_t *ptrU, trace_t *ptrD )
  2657. {
  2658. // NOTE: when tracing down through player's box, endpos and startpos are reversed and
  2659. // startsolid and allsolid are true.
  2660. int zup = abs(ptrU->endpos.z - ptrU->startpos.z); // height above player's head
  2661. int zdown = abs(ptrD->endpos.z - ptrD->startpos.z); // distance to floor from player's head
  2662. int h;
  2663. h = zup + zdown;
  2664. int door_height = das_door_height.GetInt();
  2665. int wall_height = das_wall_height.GetInt();
  2666. int low_ceiling = das_low_ceiling.GetInt();
  2667. if (h > low_ceiling && h <= wall_height)
  2668. {
  2669. // low ceiling - trace out just above standard door height @ 112
  2670. if (h > door_height)
  2671. proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + door_height + 1;
  2672. else
  2673. proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + h - 1;
  2674. }
  2675. else if ( h > wall_height )
  2676. {
  2677. // tall ceiling - trace out over standard walls @ 128
  2678. proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + wall_height + 1;
  2679. }
  2680. else
  2681. {
  2682. // very low ceiling, trace out from just below ceiling
  2683. proom->vplayer.z = fpmin(ptrD->endpos.z, ptrD->startpos.z) + h - 1;
  2684. proom->lowceiling = h;
  2685. }
  2686. Assert (proom->vplayer.z <= ptrU->endpos.z);
  2687. if (das_debug.GetInt() > 1)
  2688. {
  2689. // draw line to height, and between floor and ceiling
  2690. CDebugOverlay::AddLineOverlay( ptrD->endpos, ptrU->endpos, 0, 255, 0, 255, false, 20 );
  2691. Vector mins;
  2692. Vector maxs;
  2693. mins.Init(-1,-1,-2.0);
  2694. maxs.Init(1,1,0);
  2695. CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 255, 0, 0, 0, 20 );
  2696. CDebugOverlay::AddBoxOverlay( ptrU->endpos, mins, maxs, vec3_angle, 0, 255, 0, 0, 20 );
  2697. CDebugOverlay::AddBoxOverlay( ptrD->endpos, mins, maxs, vec3_angle, 0, 255, 0, 0, 20 );
  2698. }
  2699. }
  2700. // prepare room struct for new round of checks:
  2701. // clear out struct,
  2702. // init trace height origin by finding space above player's head
  2703. // returns true if player is in valid position to begin checks from
  2704. bool DAS_StartTraceChecks( das_room_t *proom )
  2705. {
  2706. // starting new check: store player position, init maxs, mins
  2707. proom->vplayer_eyes = MainViewOrigin();
  2708. proom->vplayer = MainViewOrigin();
  2709. proom->height_max = 0;
  2710. proom->width_max = 0;
  2711. proom->length_max = 0;
  2712. proom->room_maxs.Init (0.0, 0.0, 0.0);
  2713. proom->room_mins.Init (10000.0, 10000.0, 10000.0);
  2714. proom->lowceiling = 0;
  2715. // find point between player's head and ceiling - trace out to walls from here
  2716. trace_t trU, trD;
  2717. CTraceFilterDAS filterU, filterD;
  2718. Vector v_dir = g_das_vec3[IVEC_DOWN]; // down - find floor
  2719. Vector endpoint = proom->vplayer + v_dir;
  2720. Ray_t ray;
  2721. ray.Init( proom->vplayer, endpoint );
  2722. g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterD, &trD );
  2723. // if player jumping or in air, don't continue
  2724. if (trD.DidHit() && abs(trD.endpos.z - trD.startpos.z) > 72)
  2725. return false;
  2726. v_dir = g_das_vec3[IVEC_UP]; // up - find ceiling
  2727. endpoint = proom->vplayer + v_dir;
  2728. ray.Init( proom->vplayer, endpoint );
  2729. g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterU, &trU );
  2730. // if down trace hits floor, set trace height, otherwise default is player eye location
  2731. if ( DAS_HitSolid( &trD) )
  2732. DAS_SetTraceHeight( proom, &trU, &trD );
  2733. return true;
  2734. }
  2735. void DAS_DebugDrawTrace ( trace_t *ptr, int r, int g, int b, float duration, int imax)
  2736. {
  2737. // das_debug == 3: draw horizontal trace bars for room width/depth detection
  2738. // das_debug == 4: draw upward traces for height detection
  2739. if (das_debug.GetInt() != imax)
  2740. return;
  2741. CDebugOverlay::AddLineOverlay( ptr->startpos, ptr->endpos, r, g, b, 255, false, duration );
  2742. Vector mins;
  2743. Vector maxs;
  2744. mins.Init(-1,-1,-2.0);
  2745. maxs.Init(1,1,0);
  2746. CDebugOverlay::AddBoxOverlay( ptr->endpos, mins, maxs, vec3_angle, r, g, b, 0, duration );
  2747. }
  2748. // wall surface data
  2749. struct das_surfdata_t
  2750. {
  2751. float dist; // distance to player
  2752. float reflectivity; // acoustic reflectivity of material on surface
  2753. Vector hit; // trace hit location
  2754. Vector norm; // wall normal at hit location
  2755. };
  2756. // trace hit wall surface, get info about surface and store in surfdata struct
  2757. // if scanning for height, bounce a second trace off of ceiling and get dist to floor
  2758. void DAS_GetSurfaceData( das_room_t *proom, trace_t *ptr, das_surfdata_t *psurfdata )
  2759. {
  2760. float dist; // distance to player
  2761. float reflectivity; // acoustic reflectivity of material on surface
  2762. Vector hit; // trace hit location
  2763. Vector norm; // wall normal at hit location
  2764. surfacedata_t *psurf;
  2765. psurf = physprop->GetSurfaceData( ptr->surface.surfaceProps );
  2766. reflectivity = psurf ? psurf->audio.reflectivity : DAS_REFLECTIVITY_NORM;
  2767. // keep wall hit location and normal, to calc room bounds and center
  2768. norm = ptr->plane.normal;
  2769. // get length to hit location
  2770. dist = VectorLength(ptr->endpos - ptr->startpos);
  2771. // if started tracing from within player box, startpos & endpos may be flipped
  2772. if (ptr->endpos.z >= ptr->startpos.z)
  2773. hit = ptr->endpos;
  2774. else
  2775. hit = ptr->startpos;
  2776. // if checking for max height by bouncing several vectors off of ceiling:
  2777. // ignore returned normal from 1st bounce, just search straight down from trace hit location
  2778. if ( DAS_ScanningForHeight( proom ) && !DAS_ScanningForFloor( proom ) )
  2779. {
  2780. trace_t tr2;
  2781. CTraceFilterDAS filter2;
  2782. norm.Init(0.0, 0.0, -1.0);
  2783. Vector endpoint = hit + ( norm * DAS_ROOM_TRACE_LEN );
  2784. Ray_t ray;
  2785. ray.Init( hit, endpoint );
  2786. g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filter2, &tr2 );
  2787. //DAS_DebugDrawTrace( &tr2, 255, 255, 0, 10, 1);
  2788. if (tr2.DidHit())
  2789. {
  2790. // get distance between surfaces
  2791. dist = VectorLength(tr2.endpos - tr2.startpos);
  2792. }
  2793. }
  2794. // set up surface struct and return
  2795. psurfdata->dist = dist;
  2796. psurfdata->hit = hit;
  2797. psurfdata->norm = norm;
  2798. psurfdata->reflectivity = reflectivity;
  2799. }
  2800. // algorithm for detecting approximate size of space around player. Handles player in corner & non-axis aligned rooms.
  2801. // also handles player on catwalk or player under small bridge/overhang.
  2802. // The goal is to only change the dsp room description if the the player moves into
  2803. // a space which is SIGNIFICANTLY different from the previously set dsp space.
  2804. // save player position. find a point above player's head and trace out from here.
  2805. // from player position, get max width and max length:
  2806. // from player position,
  2807. // a) trace x,-x, y,-y axes
  2808. // b) trace xy, -xy, x-y, -x-y diagonals
  2809. // c) select largest room size detected from max width, max length
  2810. // from player position, get height
  2811. // a) trace out along front-up (or left-up, back-up, right-up), save hit locations
  2812. // b) trace down -z from hit locations
  2813. // c) save max height
  2814. // when max width, max length, max height all updated, get new player position
  2815. // get average room size & wall materials:
  2816. // update averages with one traceline per frame only
  2817. // returns true if room is fully updated and ready to check
  2818. bool DAS_UpdateRoomSize( das_room_t *proom )
  2819. {
  2820. Vector endpoint;
  2821. Vector startpoint;
  2822. Vector v_dir;
  2823. int iwall;
  2824. bool bskyhit = false;
  2825. das_surfdata_t surfdata;
  2826. // do nothing if room already fully checked
  2827. if ( proom->broomready )
  2828. return true;
  2829. // cycle through all walls, floor, ceiling
  2830. // get wall index
  2831. iwall = proom->iwall;
  2832. // get height above player and init proom for new round of checks
  2833. if (iwall == 0)
  2834. {
  2835. if (!DAS_StartTraceChecks( proom ))
  2836. return false; // bad location to check room - player is jumping etc.
  2837. }
  2838. // get trace vector
  2839. v_dir = g_das_vec3[iwall];
  2840. // trace out from trace origin, in axis-aligned direction or along diagonals
  2841. // if looking for max height, trace from top of player's eyes
  2842. if ( DAS_ScanningForHeight( proom ) )
  2843. {
  2844. startpoint = proom->vplayer_eyes;
  2845. endpoint = proom->vplayer_eyes + v_dir;
  2846. }
  2847. else
  2848. {
  2849. startpoint = proom->vplayer;
  2850. endpoint = proom->vplayer + v_dir;
  2851. }
  2852. // try less expensive world-only trace first (no props, no ents - just try to hit walls)
  2853. trace_t tr;
  2854. CTraceFilterWorldOnly filter;
  2855. Ray_t ray;
  2856. ray.Init( startpoint, endpoint );
  2857. g_pEngineTraceClient->TraceRay( ray, CONTENTS_SOLID, &filter, &tr );
  2858. // if didn't hit world, or we hit sky when looking horizontally,
  2859. // retrace, this time including props
  2860. if ( !DAS_HitSolid( &tr ) && DAS_ScanningForWidth( proom ) )
  2861. {
  2862. CTraceFilterDAS filterDas;
  2863. ray.Init( startpoint, endpoint );
  2864. g_pEngineTraceClient->TraceRay( ray, DAS_TRACE_MASK, &filterDas, &tr );
  2865. }
  2866. if (das_debug.GetInt() > 2)
  2867. {
  2868. // draw trace lines
  2869. if ( DAS_HitSolid( &tr ) )
  2870. DAS_DebugDrawTrace( &tr, 0, 255, 255, 10, DAS_ScanningForHeight( proom ) + 3);
  2871. else
  2872. DAS_DebugDrawTrace( &tr, 255, 0, 0, 10, DAS_ScanningForHeight( proom ) + 3); // red lines if sky hit or no hit
  2873. }
  2874. // init surface data with defaults, in case we didn't hit world
  2875. surfdata.dist = DAS_ROOM_TRACE_LEN;
  2876. surfdata.reflectivity = DAS_REFLECTIVITY_SKY; // assume sky or open area
  2877. surfdata.hit = endpoint; // trace hit location
  2878. surfdata.norm = -v_dir;
  2879. // check for sky hits
  2880. if ( DAS_HitSky( &tr ) )
  2881. {
  2882. bskyhit = true;
  2883. if ( DAS_ScanningForWidth( proom ) )
  2884. // ignore horizontal sky hits for distance calculations
  2885. surfdata.dist = 1.0;
  2886. else
  2887. surfdata.dist = surfdata.dist; // debug
  2888. }
  2889. // get length of trace if it hit world
  2890. // if hit solid and not sky (tr.DidHit() && !bskyhit)
  2891. // get surface information
  2892. if ( DAS_HitSolid( &tr) )
  2893. DAS_GetSurfaceData( proom, &tr, &surfdata );
  2894. // store surface data
  2895. proom->dist[iwall] = surfdata.dist;
  2896. proom->reflect[iwall] = clamp(surfdata.reflectivity, 0.0f, 1.0f);
  2897. proom->skyhits[iwall] = bskyhit ? 0.1 : 0.0;
  2898. proom->hit[iwall] = surfdata.hit;
  2899. proom->norm[iwall] = surfdata.norm;
  2900. // update wall counter
  2901. proom->iwall++;
  2902. if (proom->iwall == DAS_CWALLS)
  2903. {
  2904. bool b_good_node_location;
  2905. // calculate room mins, maxs, reflectivity etc
  2906. b_good_node_location = DAS_CalcRoomProps( proom );
  2907. // reset wall counter
  2908. proom->iwall = 0;
  2909. proom->broomready = b_good_node_location; // room ready to check if good node location
  2910. return b_good_node_location;
  2911. }
  2912. return false; // room not yet fully updated
  2913. }
  2914. // create entity enumerator for counting ents & summing volume of ents in room
  2915. class CDasEntEnum : public IPartitionEnumerator
  2916. {
  2917. public:
  2918. int m_count; // # of ents in space
  2919. float m_volume; // space occupied by ents
  2920. public:
  2921. void Reset()
  2922. {
  2923. m_count = 0;
  2924. m_volume = 0.0;
  2925. }
  2926. // called with each handle...
  2927. IterationRetval_t EnumElement( IHandleEntity *pHandleEntity )
  2928. {
  2929. float vol;
  2930. // get bounding box of entity
  2931. // Generate a collideable
  2932. ICollideable *pCollideable = g_pEngineTraceClient->GetCollideable( pHandleEntity );
  2933. if ( !pCollideable )
  2934. return ITERATION_CONTINUE;
  2935. // Check for solid
  2936. if ( !IsSolid( pCollideable->GetSolid(), pCollideable->GetSolidFlags() ) )
  2937. return ITERATION_CONTINUE;
  2938. m_count++;
  2939. // compute volume of space occupied by entity
  2940. Vector mins = pCollideable->OBBMins();
  2941. Vector maxs = pCollideable->OBBMaxs();
  2942. vol = fabs((maxs.x - mins.x) * (maxs.y - mins.y) * (maxs.z - mins.z));
  2943. m_volume += vol; // add to total vol
  2944. if (das_debug.GetInt() == 5)
  2945. {
  2946. // draw box around all objects detected
  2947. Vector orig = pCollideable->GetCollisionOrigin();
  2948. CDebugOverlay::AddBoxOverlay( orig, mins, maxs, pCollideable->GetCollisionAngles(), 255, 0, 255, 0, 60.0f );
  2949. }
  2950. return ITERATION_CONTINUE;
  2951. }
  2952. };
  2953. // determine # of solid ents/props within detected room boundaries
  2954. // and set diffusion based on count of ents and spatial volume of ents
  2955. void DAS_SetDiffusion( das_room_t *proom )
  2956. {
  2957. // BRJ 7/12/05
  2958. // This was commented out because the y component of proom->room_mins, proom->room_maxs was never
  2959. // being computed, causing a bogus box to be sent to the partition system. The results of
  2960. // this computation (namely the diffusion + ent_count fields of das_room_t) were never being used.
  2961. // Therefore, we'll avoid the enumeration altogether
  2962. proom->diffusion = 0.0f;
  2963. proom->ent_count = 0;
  2964. /*
  2965. CDasEntEnum enumerator;
  2966. SpatialPartitionListMask_t mask = PARTITION_CLIENT_SOLID_EDICTS; // count only solid ents in room
  2967. int count;
  2968. float vol;
  2969. float volroom;
  2970. float dfn;
  2971. enumerator.Reset();
  2972. SpatialPartition()->EnumerateElementsInBox(mask, proom->room_mins, proom->room_maxs, true, &enumerator );
  2973. count = enumerator.m_count;
  2974. vol = enumerator.m_volume;
  2975. // compute diffusion from volume
  2976. // how much space around player is filled with props?
  2977. volroom = (proom->room_maxs.x - proom->room_mins.x) * (proom->room_maxs.y - proom->room_mins.y) * (proom->room_maxs.z - proom->room_mins.z);
  2978. volroom = fabs(volroom);
  2979. if ( !(int)volroom )
  2980. volroom = 1.0;
  2981. dfn = vol / volroom; // % of total volume occupied by props
  2982. dfn = clamp (dfn, 0.0, 1.0);
  2983. proom->diffusion = dfn;
  2984. proom->ent_count = count;
  2985. */
  2986. }
  2987. // debug routine to display current room params
  2988. void DAS_DisplayRoomDEBUG( das_room_t *proom, bool fnew, float preset )
  2989. {
  2990. float dx,dy,dz;
  2991. Vector ctr;
  2992. float count;
  2993. if (das_debug.GetInt() == 0)
  2994. return;
  2995. dx = proom->length_max / 12.0;
  2996. dy = proom->width_max / 12.0;
  2997. dz = proom->height_max / 12.0;
  2998. float refl = proom->refl_avg;
  2999. count = (float)(proom->ent_count);
  3000. float fsky = (proom->bskyabove ? 1.0 : 0.0);
  3001. if (fnew)
  3002. DevMsg( "NEW DSP NODE: size:(%.0f,%.0f) height:(%.0f) dif %.4f : refl %.4f : cobj: %.0f : sky %.0f \n", dx, dy, dz, proom->diffusion, refl, count, fsky);
  3003. if (!fnew && preset < 0.0)
  3004. return;
  3005. if (preset >= 0.0)
  3006. {
  3007. if (proom == NULL)
  3008. return;
  3009. DevMsg( "DSP PRESET: %.0f size:(%.0f,%.0f) height:(%.0f) dif %.4f : refl %.4f : cobj: %.0f : sky %.0f \n", preset, dx, dy, dz, proom->diffusion, refl, count, fsky);
  3010. return;
  3011. }
  3012. // draw box around new node location
  3013. Vector mins;
  3014. Vector maxs;
  3015. mins.Init(-8,-8,-16);
  3016. maxs.Init(8,8,0);
  3017. CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 0, 0, 255, 0, 1000.0f );
  3018. // draw red box around node origin
  3019. mins.Init(-0.5,-0.5,-1.0);
  3020. maxs.Init(0.5,0.5,0);
  3021. CDebugOverlay::AddBoxOverlay( proom->vplayer, mins, maxs, vec3_angle, 255, 0, 0, 0, 1000.0f );
  3022. CDebugOverlay::AddTextOverlay( proom->vplayer, 0, 10, 1.0, "DSP NODE" );
  3023. }
  3024. // check newly calculated room parameters against current stored params.
  3025. // if different, return true.
  3026. // NOTE: only call when all proom params have been calculated.
  3027. // return false if this is not a good location for creating a new node
  3028. bool DAS_CheckNewRoom( das_room_t *proom )
  3029. {
  3030. bool bnewroom;
  3031. float dw,dw2,dr,ds,dh;
  3032. int cchanged = 0;
  3033. das_room_t *proom_prev = NULL;
  3034. Vector2D v2d;
  3035. Vector v3d;
  3036. float dist;
  3037. // player can't see previous node, determine if this is a good place to lay down
  3038. // a new node. Get room at last seen node for comparison
  3039. if (g_pdas_last_node)
  3040. proom_prev = &(g_pdas_last_node->room);
  3041. // no previous room node saw player, go create new room node
  3042. if (!proom_prev)
  3043. {
  3044. bnewroom = true;
  3045. goto check_ret;
  3046. }
  3047. // if player not at least n feet from last node, return false
  3048. v3d = proom->vplayer - proom_prev->vplayer;
  3049. v2d.Init(v3d.x, v3d.y);
  3050. dist = Vector2DLength(v2d);
  3051. if (dist <= DAS_DIST_MIN)
  3052. return false;
  3053. // see if room size has changed significantly since last node
  3054. bnewroom = true;
  3055. dw = 0.0;
  3056. dw2 = 0.0;
  3057. dh = 0.0;
  3058. dr = 0.0;
  3059. if ( proom_prev->width_max != 0 )
  3060. dw = (float)proom->width_max / (float)proom_prev->width_max; // max width delta
  3061. if ( proom_prev->length_max != 0 )
  3062. dw2 = (float)proom->length_max / (float)proom_prev->length_max; // max length delta
  3063. if ( proom_prev->height_max != 0 )
  3064. dh = (float)proom->height_max / (float)proom_prev->height_max; // max height delta
  3065. if ( proom_prev->refl_avg != 0.0 )
  3066. dr = proom->refl_avg / proom_prev->refl_avg; // reflectivity delta
  3067. ds = fabs( proom->sky_pct - proom_prev->sky_pct); // sky hits delta
  3068. if (dw > 1.0) dw = 1.0 / dw;
  3069. if (dw2 > 1.0) dw = 1.0 / dw2;
  3070. if (dh > 1.0) dh = 1.0 / dh;
  3071. if (dr > 1.0) dr = 1.0 / dr;
  3072. if ( (1.0 - dw) >= DAS_WIDTH_MIN )
  3073. cchanged++;
  3074. if ( (1.0 - dw2) >= DAS_WIDTH_MIN )
  3075. cchanged++;
  3076. // if ( (1.0 - dh) >= DAS_WIDTH_MIN ) // don't change room based on height change
  3077. // cchanged++;
  3078. // new room only if at least 1 changed
  3079. if (cchanged >= 1)
  3080. goto check_ret;
  3081. // if ( (1.0 - dr) >= DAS_REFL_MIN ) // don't change room based on reflectivity change
  3082. // goto check_ret;
  3083. // if (ds >= DAS_SKYHIT_MIN )
  3084. // goto check_ret;
  3085. // new room if sky above changes state
  3086. if (proom->bskyabove != proom_prev->bskyabove)
  3087. goto check_ret;
  3088. // room didn't change significantly, return false
  3089. bnewroom = false;
  3090. check_ret:
  3091. if ( bnewroom )
  3092. {
  3093. // if low ceiling detected < 112 units, and max height is > low ceiling height by 20%, discard - no change
  3094. // this detects player in doorway, under pipe or narrow bridge
  3095. if ( proom->lowceiling && (proom->lowceiling < proom->height_max))
  3096. {
  3097. float h = (float)(proom->lowceiling) / (float)proom->height_max;
  3098. if (h < 0.8)
  3099. return false;
  3100. }
  3101. DAS_SetDiffusion( proom );
  3102. }
  3103. DAS_DisplayRoomDEBUG( proom, bnewroom, -1.0 );
  3104. return bnewroom;
  3105. }
  3106. extern int DSP_ConstructPreset( bool bskyabove, int width, int length, int height, float fdiffusion, float freflectivity, float *psurf_refl, int inode, int cnodes );
  3107. // select new dsp_room based on size, wall materials
  3108. // (or modulate params for current dsp)
  3109. // returns new preset # for dsp_automatic
  3110. int DAS_GetRoomDSP( das_room_t *proom, int inode )
  3111. {
  3112. // preset constructor
  3113. // call dsp module with params, get dsp preset back
  3114. bool bskyabove = proom->bskyabove;
  3115. int width = proom->width_max;
  3116. int length = proom->length_max;
  3117. int height = proom->height_max;
  3118. float fdiffusion = proom->diffusion;
  3119. float freflectivity = proom->refl_avg;
  3120. float surf_refl[6];
  3121. // fill array of surface reflectivities - for left,right,front,back,ceiling,floor
  3122. for (int i = 0; i < 6; i++)
  3123. surf_refl[i] = proom->refl_walls[i];
  3124. return DSP_ConstructPreset( bskyabove, width, length, height, fdiffusion, freflectivity, surf_refl, inode, DAS_CNODES );
  3125. }
  3126. // main entry point: call once per frame to update dsp_automatic
  3127. // for automatic room detection. dsp_room must be set to DSP_AUTOMATIC to enable.
  3128. // NOTE: this routine accumulates traceline information over several frames - it
  3129. // never traces more than 3 times per call, and normally just once per call.
  3130. void DAS_CheckNewRoomDSP( )
  3131. {
  3132. VPROF("DAS_CheckNewRoomDSP");
  3133. das_room_t *proom = &g_das_room;
  3134. int dsp_preset;
  3135. bool bRoom_ready = false;
  3136. // if listener has not been updated, do nothing
  3137. if ((listener_origin == vec3_origin) &&
  3138. (listener_forward == vec3_origin) &&
  3139. (listener_right == vec3_origin) &&
  3140. (listener_up == vec3_origin) )
  3141. return;
  3142. if ( !SND_IsInGame() )
  3143. return;
  3144. // make sure we init nodes & vectors first time this is called
  3145. if ( !g_bdas_init_nodes )
  3146. {
  3147. g_bdas_init_nodes = 1;
  3148. DAS_InitNodes();
  3149. }
  3150. if ( !DSP_CheckDspAutoEnabled())
  3151. {
  3152. // make sure room params are reinitialized each time autoroom is selected
  3153. g_bdas_room_init = 0;
  3154. return;
  3155. }
  3156. if ( !g_bdas_room_init )
  3157. {
  3158. g_bdas_room_init = 1;
  3159. DAS_InitAutoRoom( proom );
  3160. }
  3161. // get time
  3162. double dtime = g_pSoundServices->GetHostTime();
  3163. // compare to previous time - don't check for new room until timer expires
  3164. // ie: wait at least DAS_AUTO_WAIT seconds between preset changes
  3165. if ( fabs(dtime - proom->last_dsp_change) < DAS_AUTO_WAIT )
  3166. return;
  3167. // first, update room size parameters, see if room is ready to check - if room is updated, return true right away
  3168. // 3 traces per frame while accumulating room size info
  3169. for (int i = 0 ; i < 3; i++)
  3170. bRoom_ready = DAS_UpdateRoomSize( proom );
  3171. if (!bRoom_ready)
  3172. return;
  3173. if ( !g_bdas_create_new_node )
  3174. {
  3175. // next, check all nodes for line of sight to player - if all checked, return true right away
  3176. if ( !DAS_CheckNextNode( proom ) )
  3177. {
  3178. // check all nodes first
  3179. return;
  3180. }
  3181. // find out if any previously stored nodes can see player,
  3182. // if so, get closest node's dsp preset
  3183. dsp_preset = DAS_GetDspPreset( proom->bskyabove );
  3184. if (dsp_preset != -1)
  3185. {
  3186. // an existing node can see player - just set preset and return
  3187. if (dsp_preset != dsp_room_GetInt())
  3188. {
  3189. // changed preset, so update timestamp
  3190. proom->last_dsp_change = g_pSoundServices->GetHostTime();
  3191. if (g_pdas_last_node)
  3192. DAS_DisplayRoomDEBUG( &(g_pdas_last_node->room), false, (float)dsp_preset );
  3193. }
  3194. DSP_SetDspAuto( dsp_preset );
  3195. goto check_new_room_exit;
  3196. }
  3197. }
  3198. g_bdas_create_new_node = true;
  3199. // no nodes can see player, need to try to create a new one
  3200. // check for 'new' room around player
  3201. if ( DAS_CheckNewRoom( proom ) )
  3202. {
  3203. // new room found - update dsp_automatic
  3204. dsp_preset = DAS_GetRoomDSP( proom, DAS_GetNextNodeIndex() );
  3205. DSP_SetDspAuto( dsp_preset );
  3206. // changed preset, so update timestamp
  3207. proom->last_dsp_change = g_pSoundServices->GetHostTime();
  3208. // save room as new node
  3209. DAS_StoreNode( proom, dsp_preset );
  3210. goto check_new_room_exit;
  3211. }
  3212. check_new_room_exit:
  3213. // reset new node creation flag - start checking for visible nodes again
  3214. g_bdas_create_new_node = false;
  3215. // reset room checking flag - start checking room around player again
  3216. proom->broomready = false;
  3217. // reset node checking flag - start checking nodes around player again
  3218. DAS_ResetNodes();
  3219. return;
  3220. }
  3221. // remap contents of volumes[] arrary if sound originates from player, or is music, and is 100% 'mono'
  3222. // ie: same volume in all channels
  3223. void RemapPlayerOrMusicVols( channel_t *ch, int volumes[CCHANVOLUMES/2], bool fplayersound, bool fmusicsound, float mono )
  3224. {
  3225. VPROF_("RemapPlayerOrMusicVols", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
  3226. if ( !fplayersound && !fmusicsound )
  3227. return; // no remapping
  3228. if ( ch->flags.bSpeaker )
  3229. return; // don't remap speaker sounds rebroadcast on player
  3230. // get total volume
  3231. float vol_total = 0.0;
  3232. int k;
  3233. for (k = 0; k < CCHANVOLUMES/2; k++)
  3234. vol_total += (float)volumes[k];
  3235. if ( !g_AudioDevice->IsSurround() )
  3236. {
  3237. if (mono < 1.0)
  3238. return;
  3239. // remap 2 chan non-spatialized versions of player and music sounds
  3240. // note: this is required to keep volumes same as 4 & 5 ch cases!
  3241. float vol_dist_music[] = {1.0, 1.0}; // FL, FR music volumes
  3242. float vol_dist_player[] = {1.0, 1.0}; // FL, FR player volumes
  3243. float *pvol_dist;
  3244. pvol_dist = (fplayersound ? vol_dist_player : vol_dist_music);
  3245. for (k = 0; k < 2; k++)
  3246. volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
  3247. return;
  3248. }
  3249. // surround sound configuration...
  3250. if ( fplayersound ) // && (ch->bstereowav && ch->wavtype != CHAR_DIRECTIONAL && ch->wavtype != CHAR_DISTVARIANT) )
  3251. {
  3252. // NOTE: player sounds also get n% overall volume boost.
  3253. //float vol_dist5[] = {0.29, 0.29, 0.09, 0.09, 0.63}; // FL, FR, RL, RR, FC - 5 channel (mono source) volume distribution
  3254. //float vol_dist5st[] = {0.29, 0.29, 0.09, 0.09, 0.63}; // FL, FR, RL, RR, FC - 5 channel (stereo source) volume distribution
  3255. float vol_dist5[] = {0.30, 0.30, 0.09, 0.09, 0.59}; // FL, FR, RL, RR, FC - 5 channel (mono source) volume distribution
  3256. float vol_dist5st[] = {0.30, 0.30, 0.09, 0.09, 0.59}; // FL, FR, RL, RR, FC - 5 channel (stereo source) volume distribution
  3257. float vol_dist4[] = {0.50, 0.50, 0.15, 0.15, 0.00}; // FL, FR, RL, RR, 0 - 4 channel (mono source) volume distribution
  3258. float vol_dist4st[] = {0.50, 0.50, 0.15, 0.15, 0.00}; // FL, FR, RL, RR, 0 - 4 channel (stereo source)volume distribution
  3259. float *pvol_dist;
  3260. if ( ch->flags.bstereowav && (ch->wavtype == CHAR_OMNI || ch->wavtype == CHAR_SPATIALSTEREO || ch->wavtype == 0))
  3261. {
  3262. pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5st : vol_dist4st);
  3263. }
  3264. else
  3265. {
  3266. pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5 : vol_dist4);
  3267. }
  3268. for (k = 0; k < 5; k++)
  3269. volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
  3270. return;
  3271. }
  3272. // Special case for music in surround mode
  3273. if ( fmusicsound )
  3274. {
  3275. float vol_dist5[] = {0.5, 0.5, 0.25, 0.25, 0.0}; // FL, FR, RL, RR, FC - 5 channel distribution
  3276. float vol_dist4[] = {0.5, 0.5, 0.25, 0.25, 0.0}; // FL, FR, RL, RR, 0 - 4 channel distribution
  3277. float *pvol_dist;
  3278. pvol_dist = (g_AudioDevice->IsSurroundCenter() ? vol_dist5 : vol_dist4);
  3279. for (k = 0; k < 5; k++)
  3280. volumes[k] = clamp((int)(vol_total * pvol_dist[k]), 0, 255);
  3281. return;
  3282. }
  3283. return;
  3284. }
  3285. static int s_nSoundGuid = 0;
  3286. void SND_ActivateChannel( channel_t *pChannel )
  3287. {
  3288. Q_memset( pChannel, 0, sizeof(*pChannel) );
  3289. g_ActiveChannels.Add( pChannel );
  3290. pChannel->guid = ++s_nSoundGuid;
  3291. }
  3292. /*
  3293. =================
  3294. SND_Spatialize
  3295. =================
  3296. */
  3297. void SND_Spatialize(channel_t *ch)
  3298. {
  3299. VPROF("SND_Spatialize");
  3300. vec_t dist;
  3301. Vector source_vec;
  3302. Vector source_vec_DL;
  3303. Vector source_vec_DR;
  3304. Vector source_doppler_left;
  3305. Vector source_doppler_right;
  3306. bool fdopplerwav = false;
  3307. bool fplaydopplerwav = false;
  3308. bool fvalidentity;
  3309. float gain;
  3310. float scale = 1.0;
  3311. bool fplayersound = false;
  3312. bool fmusicsound = false;
  3313. float mono = 0.0;
  3314. bool bAttenuated = true;
  3315. ch->dspface = 1.0; // default facing direction: always facing player
  3316. ch->dspmix = 0; // default mix 0% dsp_room fx
  3317. ch->distmix = 0; // default 100% left (near) wav
  3318. #if !defined( _X360 )
  3319. if ( ch->sfx &&
  3320. ch->sfx->pSource &&
  3321. ch->sfx->pSource->GetType() == CAudioSource::AUDIO_SOURCE_VOICE )
  3322. {
  3323. Voice_Spatialize( ch );
  3324. }
  3325. #endif
  3326. if ( IsSoundSourceLocalPlayer( ch->soundsource ) && !toolframework->InToolMode() )
  3327. {
  3328. // sounds coming from listener actually come from a short distance directly in front of listener
  3329. // in tool mode however, the view entity is meaningless, since we're viewing from arbitrary locations in space
  3330. fplayersound = true;
  3331. }
  3332. // assume 'dry', playeverwhere sounds are 'music' or 'voiceover'
  3333. if ( ch->flags.bdry && ch->dist_mult <= 0 )
  3334. {
  3335. fmusicsound = true;
  3336. fplayersound = false;
  3337. }
  3338. // update channel's position in case ent that made the sound is moving.
  3339. QAngle source_angles;
  3340. source_angles.Init(0.0, 0.0, 0.0);
  3341. Vector entOrigin = ch->origin;
  3342. bool looping = false;
  3343. CAudioSource *pSource = ch->sfx ? ch->sfx->pSource : NULL;
  3344. if ( pSource )
  3345. {
  3346. looping = pSource->IsLooped();
  3347. }
  3348. SpatializationInfo_t si;
  3349. si.info.Set(
  3350. ch->soundsource,
  3351. ch->entchannel,
  3352. ch->sfx ? ch->sfx->getname() : "",
  3353. ch->origin,
  3354. ch->direction,
  3355. ch->master_vol,
  3356. DIST_MULT_TO_SNDLVL( ch->dist_mult ),
  3357. looping,
  3358. ch->pitch,
  3359. listener_origin,
  3360. ch->speakerentity );
  3361. si.type = SpatializationInfo_t::SI_INSPATIALIZATION;
  3362. si.pOrigin = &entOrigin;
  3363. si.pAngles = &source_angles;
  3364. si.pflRadius = NULL;
  3365. if ( ch->soundsource != 0 && ch->radius == 0 )
  3366. {
  3367. si.pflRadius = &ch->radius;
  3368. }
  3369. {
  3370. VPROF_("SoundServices->GetSoundSpatializtion", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
  3371. fvalidentity = g_pSoundServices->GetSoundSpatialization( ch->soundsource, si );
  3372. }
  3373. if ( ch->flags.bUpdatePositions )
  3374. {
  3375. AngleVectors( source_angles, &ch->direction );
  3376. ch->origin = entOrigin;
  3377. }
  3378. else
  3379. {
  3380. VectorAngles( ch->direction, source_angles );
  3381. }
  3382. if ( ch->userdata != 0 )
  3383. {
  3384. g_pSoundServices->GetToolSpatialization( ch->userdata, ch->guid, si );
  3385. if ( ch->flags.bUpdatePositions )
  3386. {
  3387. AngleVectors( source_angles, &ch->direction );
  3388. ch->origin = entOrigin;
  3389. }
  3390. }
  3391. #if 0
  3392. // !!!UNDONE - above code assumes the ENT hasn't been removed or respawned as another ent!
  3393. // !!!UNDONE - fix this by flagging some entities (ie: glass) as immobile. Don't spatialize them.
  3394. if ( !fvalidendity)
  3395. {
  3396. // Turn off the sound while the entity doesn't exist or is not in the PVS.
  3397. goto ClearAllVolumes;
  3398. }
  3399. #endif // 0
  3400. fdopplerwav = ((ch->wavtype == CHAR_DOPPLER) && !fplayersound);
  3401. if ( fdopplerwav )
  3402. {
  3403. VPROF_("SND_Spatialize doppler", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
  3404. Vector vnearpoint; // point of closest approach to listener,
  3405. // along sound source forward direction (doppler wavs)
  3406. vnearpoint = ch->origin; // default nearest sound approach point
  3407. // calculate point of closest approach for CHAR_DOPPLER wavs, replace source_vec
  3408. fplaydopplerwav = SND_GetClosestPoint( ch, source_angles, vnearpoint );
  3409. // if doppler sound was 'shot' away from listener, don't play it
  3410. if ( !fplaydopplerwav )
  3411. goto ClearAllVolumes;
  3412. // find location of doppler left & doppler right points
  3413. SND_GetDopplerPoints( ch, source_angles, vnearpoint, source_doppler_left, source_doppler_right);
  3414. // source_vec_DL is vector from listener to doppler left point
  3415. // source_vec_DR is vector from listener to doppler right point
  3416. VectorSubtract(source_doppler_left, listener_origin, source_vec_DL );
  3417. VectorSubtract(source_doppler_right, listener_origin, source_vec_DR );
  3418. // normalized vectors to left and right doppler locations
  3419. dist = VectorNormalize( source_vec_DL );
  3420. VectorNormalize( source_vec_DR );
  3421. // don't play doppler if out of range
  3422. // unless recording in the tool, since we may play back in range
  3423. if ( dist > DOPPLER_RANGE_MAX && !toolframework->IsToolRecording() )
  3424. goto ClearAllVolumes;
  3425. }
  3426. else
  3427. {
  3428. // source_vec is vector from listener to sound source
  3429. if ( fplayersound )
  3430. {
  3431. // get 2d forward direction vector, ignoring pitch angle
  3432. Vector listener_forward2d;
  3433. ConvertListenerVectorTo2D( &listener_forward2d, &listener_right );
  3434. // player sounds originate from 1' in front of player, 2d
  3435. VectorMultiply(listener_forward2d, 12.0, source_vec );
  3436. }
  3437. else
  3438. {
  3439. VectorSubtract(ch->origin, listener_origin, source_vec);
  3440. }
  3441. // normalize source_vec and get distance from listener to source
  3442. dist = VectorNormalize( source_vec );
  3443. }
  3444. // calculate dsp mix based on distance to listener & sound level (linear approximation)
  3445. ch->dspmix = SND_GetDspMix( ch, dist );
  3446. // calculate sound source facing direction for CHAR_DIRECTIONAL wavs
  3447. if ( !fplayersound )
  3448. {
  3449. ch->dspface = SND_GetFacingDirection( ch, source_angles );
  3450. // calculate mixing parameter for CHAR_DISTVAR wavs
  3451. ch->distmix = SND_GetDistanceMix( ch, dist );
  3452. }
  3453. // for sounds with a radius, spatialize left/right/front/rear evenly within the radius
  3454. if ( ch->radius > 0 && dist < ch->radius && !fdopplerwav )
  3455. {
  3456. float interval = ch->radius * 0.5;
  3457. mono = dist - interval;
  3458. if ( mono < 0.0 )
  3459. mono = 0.0;
  3460. mono /= interval;
  3461. mono = 1.0 - mono;
  3462. // mono is 0.0 -> 1.0 from radius 100% to radius 50%
  3463. }
  3464. // don't pan sounds with no attenuation
  3465. if ( ch->dist_mult <= 0 && !fdopplerwav )
  3466. {
  3467. // sound is centered left/right/front/back
  3468. mono = 1.0;
  3469. bAttenuated = false;
  3470. }
  3471. if ( ch->wavtype == CHAR_OMNI )
  3472. {
  3473. // omni directional sound sources are mono mix, all speakers
  3474. // ie: they only attenuate by distance, not by source direction.
  3475. mono = 1.0;
  3476. bAttenuated = false;
  3477. }
  3478. // calculate gain based on distance, atmospheric attenuation, interposed objects
  3479. // perform compression as gain approaches 1.0
  3480. gain = SND_GetGain( ch, fplayersound, fmusicsound, looping, dist, bAttenuated );
  3481. // map gain through global mixer by soundtype
  3482. // gain *= SND_GetVolFromSoundtype( ch->soundtype );
  3483. int last_mixgroupid;
  3484. gain *= MXR_GetVolFromMixGroup( ch->mixgroups, &last_mixgroupid );
  3485. // if playing a word, get volume scale of word - scale gain
  3486. scale = VOX_GetChanVol(ch);
  3487. gain *= scale;
  3488. // save spatialized volume and mixgroupid for display later
  3489. ch->last_mixgroupid = last_mixgroupid;
  3490. if ( fdopplerwav )
  3491. {
  3492. VPROF_("SND_Spatialize doppler", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
  3493. // fill out channel volumes for both doppler sound source locations
  3494. int volumes[CCHANVOLUMES/2];
  3495. // left doppler location
  3496. g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec_DL, gain, mono );
  3497. // load volumes into channel as crossfade targets
  3498. ChannelSetVolTargets( ch, volumes, IFRONT_LEFT, CCHANVOLUMES/2 );
  3499. // right doppler location
  3500. g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec_DR, gain, mono );
  3501. // load volumes into channel as crossfade targets
  3502. ChannelSetVolTargets( ch, volumes, IFRONT_LEFTD, CCHANVOLUMES/2 );
  3503. }
  3504. else
  3505. {
  3506. // fill out channel volumes for single sound source location
  3507. int volumes[CCHANVOLUMES/2];
  3508. g_AudioDevice->SpatializeChannel( volumes, ch->master_vol, source_vec, gain, mono );
  3509. // Special case for stereo sounds originating from player in surround mode
  3510. // and special case for musci: remap volumes directly to channels.
  3511. RemapPlayerOrMusicVols( ch, volumes, fplayersound, fmusicsound, mono );
  3512. // load volumes into channel as crossfade volume targets
  3513. ChannelSetVolTargets( ch, volumes, IFRONT_LEFT, CCHANVOLUMES/2 );
  3514. }
  3515. // prevent left/right/front/rear/center volumes from changing too quickly & producing pops
  3516. ChannelUpdateVolXfade( ch );
  3517. // end of first time spatializing sound
  3518. if ( SND_IsInGame() || toolframework->InToolMode() )
  3519. {
  3520. ch->flags.bfirstpass = false;
  3521. }
  3522. // calculate total volume for display later
  3523. ch->last_vol = gain * (ch->master_vol/255.0);
  3524. return;
  3525. ClearAllVolumes:
  3526. // Clear all volumes and return.
  3527. // This shuts the sound off permanently.
  3528. ChannelClearVolumes( ch );
  3529. // end of first time spatializing sound
  3530. ch->flags.bfirstpass = false;
  3531. }
  3532. ConVar snd_defer_trace("snd_defer_trace","1");
  3533. void SND_SpatializeFirstFrameNoTrace( channel_t *pChannel)
  3534. {
  3535. if ( snd_defer_trace.GetBool() )
  3536. {
  3537. // set up tracing state to be non-obstructed
  3538. pChannel->flags.bfirstpass = false;
  3539. pChannel->flags.bTraced = true;
  3540. pChannel->ob_gain = 1.0;
  3541. pChannel->ob_gain_inc = 1.0;
  3542. pChannel->ob_gain_target = 1.0;
  3543. // now spatialize without tracing
  3544. SND_Spatialize(pChannel);
  3545. // now reset tracing state to firstpass so the trace gets done on next spatialize
  3546. pChannel->ob_gain = 0.0;
  3547. pChannel->ob_gain_inc = 0.0;
  3548. pChannel->ob_gain_target = 0.0;
  3549. pChannel->flags.bfirstpass = true;
  3550. pChannel->flags.bTraced = false;
  3551. }
  3552. else
  3553. {
  3554. pChannel->ob_gain = 0.0;
  3555. pChannel->ob_gain_inc = 0.0;
  3556. pChannel->ob_gain_target = 0.0;
  3557. pChannel->flags.bfirstpass = true;
  3558. pChannel->flags.bTraced = false;
  3559. SND_Spatialize(pChannel);
  3560. }
  3561. }
  3562. // search through all channels for a channel that matches this
  3563. // soundsource, entchannel and sfx, and perform alteration on channel
  3564. // as indicated by 'flags' parameter. If shut down request and
  3565. // sfx contains a sentence name, shut off the sentence.
  3566. // returns TRUE if sound was altered,
  3567. // returns FALSE if sound was not found (sound is not playing)
  3568. int S_AlterChannel( int soundsource, int entchannel, CSfxTable *sfx, int vol, int pitch, int flags )
  3569. {
  3570. THREAD_LOCK_SOUND();
  3571. int ch_idx;
  3572. const char *name = sfx->getname();
  3573. if ( name && TestSoundChar( name, CHAR_SENTENCE ) )
  3574. {
  3575. // This is a sentence name.
  3576. // For sentences: assume that the entity is only playing one sentence
  3577. // at a time, so we can just shut off
  3578. // any channel that has ch->isentence >= 0 and matches the
  3579. // soundsource.
  3580. CChannelList list;
  3581. g_ActiveChannels.GetActiveChannels( list );
  3582. for ( int i = 0; i < list.Count(); i++ )
  3583. {
  3584. ch_idx = list.GetChannelIndex(i);
  3585. if (channels[ch_idx].soundsource == soundsource
  3586. && channels[ch_idx].entchannel == entchannel
  3587. && channels[ch_idx].sfx != NULL )
  3588. {
  3589. if (flags & SND_CHANGE_PITCH)
  3590. channels[ch_idx].basePitch = pitch;
  3591. if (flags & SND_CHANGE_VOL)
  3592. channels[ch_idx].master_vol = vol;
  3593. if (flags & SND_STOP)
  3594. {
  3595. S_FreeChannel(&channels[ch_idx]);
  3596. }
  3597. return TRUE;
  3598. }
  3599. }
  3600. // channel not found
  3601. return FALSE;
  3602. }
  3603. // regular sound or streaming sound
  3604. CChannelList list;
  3605. g_ActiveChannels.GetActiveChannels( list );
  3606. bool bSuccess = false;
  3607. for ( int i = 0; i < list.Count(); i++ )
  3608. {
  3609. ch_idx = list.GetChannelIndex(i);
  3610. if ( channels[ch_idx].soundsource == soundsource &&
  3611. ( ( flags & SND_IGNORE_NAME ) ||
  3612. ( channels[ch_idx].entchannel == entchannel && channels[ch_idx].sfx == sfx ) ) )
  3613. {
  3614. if (flags & SND_CHANGE_PITCH)
  3615. channels[ch_idx].basePitch = pitch;
  3616. if (flags & SND_CHANGE_VOL)
  3617. channels[ch_idx].master_vol = vol;
  3618. if (flags & SND_STOP)
  3619. {
  3620. S_FreeChannel(&channels[ch_idx]);
  3621. }
  3622. if ( ( flags & SND_IGNORE_NAME ) == 0 )
  3623. return TRUE;
  3624. else
  3625. bSuccess = true;
  3626. }
  3627. }
  3628. return ( bSuccess ) ? ( TRUE ) : ( FALSE );
  3629. }
  3630. // set channel flags during initialization based on
  3631. // source name
  3632. void S_SetChannelWavtype( channel_t *target_chan, CSfxTable *pSfx )
  3633. {
  3634. // if 1st or 2nd character of name is CHAR_DRYMIX, sound should be mixed dry with no dsp (ie: music)
  3635. if ( TestSoundChar(pSfx->getname(), CHAR_DRYMIX) )
  3636. target_chan->flags.bdry = true;
  3637. else
  3638. target_chan->flags.bdry = false;
  3639. if ( TestSoundChar(pSfx->getname(), CHAR_FAST_PITCH) )
  3640. target_chan->flags.bfast_pitch = true;
  3641. else
  3642. target_chan->flags.bfast_pitch = false;
  3643. // get sound spatialization encoding
  3644. target_chan->wavtype = 0;
  3645. if ( TestSoundChar( pSfx->getname(), CHAR_DOPPLER ))
  3646. target_chan->wavtype = CHAR_DOPPLER;
  3647. if ( TestSoundChar( pSfx->getname(), CHAR_DIRECTIONAL ))
  3648. target_chan->wavtype = CHAR_DIRECTIONAL;
  3649. if ( TestSoundChar( pSfx->getname(), CHAR_DISTVARIANT ))
  3650. target_chan->wavtype = CHAR_DISTVARIANT;
  3651. if ( TestSoundChar( pSfx->getname(), CHAR_OMNI ))
  3652. target_chan->wavtype = CHAR_OMNI;
  3653. if ( TestSoundChar( pSfx->getname(), CHAR_SPATIALSTEREO ))
  3654. target_chan->wavtype = CHAR_SPATIALSTEREO;
  3655. }
  3656. // Sets bstereowav flag in channel if source is true stere wav
  3657. // sets default wavtype for stereo wavs to CHAR_DISTVARIANT -
  3658. // ie: sound varies with distance (left is close, right is far)
  3659. // Must be called after S_SetChannelWavtype
  3660. void S_SetChannelStereo( channel_t *target_chan, CAudioSource *pSource )
  3661. {
  3662. if ( !pSource )
  3663. {
  3664. target_chan->flags.bstereowav = false;
  3665. return;
  3666. }
  3667. // returns true only if source data is a stereo wav file.
  3668. // ie: mp3, voice, sentence are all excluded.
  3669. target_chan->flags.bstereowav = pSource->IsStereoWav();
  3670. // Default stereo wavtype:
  3671. // just player standard stereo wavs on player entity - no override.
  3672. if ( IsSoundSourceLocalPlayer( target_chan->soundsource ) )
  3673. return;
  3674. // default wavtype for stereo wavs is OMNI - except for drymix or sounds with 0 attenuation
  3675. if ( target_chan->flags.bstereowav && !target_chan->wavtype && !target_chan->flags.bdry && target_chan->dist_mult )
  3676. // target_chan->wavtype = CHAR_DISTVARIANT;
  3677. target_chan->wavtype = CHAR_OMNI;
  3678. }
  3679. // =======================================================================
  3680. // Channel volume management routines:
  3681. // channel volumes crossfade between values over time
  3682. // to prevent pops due to rapid spatialization changes
  3683. // =======================================================================
  3684. // return true if all volumes and target volumes for channel are less/equal to 'vol'
  3685. bool BChannelLowVolume( channel_t *pch, int vol_min )
  3686. {
  3687. int max = -1;
  3688. int max_target = -1;
  3689. int vol;
  3690. int vol_target;
  3691. for (int i = 0; i < CCHANVOLUMES; i++)
  3692. {
  3693. vol = (int)(pch->fvolume[i]);
  3694. vol_target = (int)(pch->fvolume_target[i]);
  3695. if (vol > max)
  3696. max = vol;
  3697. if (vol_target > max_target)
  3698. max_target = vol_target;
  3699. }
  3700. return (max <= vol_min && max_target <= vol_min);
  3701. }
  3702. // Get the loudest actual volume for a channel (not counting targets).
  3703. float ChannelLoudestCurVolume( const channel_t * RESTRICT pch )
  3704. {
  3705. float loudest = pch->fvolume[0];
  3706. for (int i = 1; i < CCHANVOLUMES; i++)
  3707. {
  3708. loudest = fpmax(loudest, pch->fvolume[i]);
  3709. }
  3710. return loudest;
  3711. }
  3712. // clear all volumes, targets, crossfade increments
  3713. void ChannelClearVolumes( channel_t *pch )
  3714. {
  3715. for (int i = 0; i < CCHANVOLUMES; i++)
  3716. {
  3717. pch->fvolume[i] = 0.0;
  3718. pch->fvolume_target[i] = 0.0;
  3719. pch->fvolume_inc[i] = 0.0;
  3720. }
  3721. }
  3722. // return current volume as integer
  3723. int ChannelGetVol( channel_t *pch, int ivol )
  3724. {
  3725. Assert(ivol < CCHANVOLUMES);
  3726. return (int)(pch->fvolume[ivol]);
  3727. }
  3728. // return maximum current output volume
  3729. int ChannelGetMaxVol( channel_t *pch )
  3730. {
  3731. float max = 0.0;
  3732. for (int i = 0; i < CCHANVOLUMES; i++)
  3733. {
  3734. if (pch->fvolume[i] > max)
  3735. max = pch->fvolume[i];
  3736. }
  3737. return (int)max;
  3738. }
  3739. // set current volume (clears crossfading - instantaneous value change)
  3740. void ChannelSetVol( channel_t *pch, int ivol, int vol )
  3741. {
  3742. Assert(ivol < CCHANVOLUMES);
  3743. pch->fvolume[ivol] = (float)(clamp(vol, 0, 255));
  3744. pch->fvolume_target[ivol] = pch->fvolume[ivol];
  3745. pch->fvolume_inc[ivol] = 0.0;
  3746. }
  3747. // copy current channel volumes into target array, starting at ivol, copying cvol entries
  3748. void ChannelCopyVolumes( channel_t *pch, int *pvolume_dest, int ivol_start, int cvol )
  3749. {
  3750. Assert (ivol_start < CCHANVOLUMES);
  3751. Assert (ivol_start + cvol <= CCHANVOLUMES);
  3752. for (int i = 0; i < cvol; i++)
  3753. pvolume_dest[i] = (int)(pch->fvolume[i + ivol_start]);
  3754. }
  3755. // volume has hit target, shut off crossfading increment
  3756. inline void ChannelStopVolXfade( channel_t *pch, int ivol )
  3757. {
  3758. pch->fvolume[ivol] = pch->fvolume_target[ivol];
  3759. pch->fvolume_inc[ivol] = 0.0;
  3760. }
  3761. #define VOL_XFADE_TIME 0.070 // channel volume crossfade time in seconds
  3762. #define VOL_INCR_MAX 20.0 // never change volume by more than +/-N units per frame
  3763. // set volume target and volume increment (for crossfade) for channel & speaker
  3764. void ChannelSetVolTarget( channel_t *pch, int ivol, int volume_target )
  3765. {
  3766. float frametime = g_pSoundServices->GetHostFrametime();
  3767. float speed;
  3768. float vol_target = (float)(clamp(volume_target, 0, 255));
  3769. float vol_current;
  3770. Assert(ivol < CCHANVOLUMES);
  3771. // set volume target
  3772. pch->fvolume_target[ivol] = vol_target;
  3773. // current volume
  3774. vol_current = pch->fvolume[ivol];
  3775. // if first time spatializing, set target = volume with no crossfade
  3776. // if current & target volumes are close - don't bother crossfading
  3777. if ( pch->flags.bfirstpass || (fabs(vol_target - vol_current) < 5.0))
  3778. {
  3779. // set current volume = target, no increment
  3780. ChannelStopVolXfade( pch, ivol);
  3781. return;
  3782. }
  3783. // get crossfade increment 'speed' (volume change per frame)
  3784. speed = ( frametime / VOL_XFADE_TIME ) * (vol_target - vol_current);
  3785. // make sure we never increment by more than +/- VOL_INCR_MAX volume units per frame
  3786. speed = clamp(speed, (float) -VOL_INCR_MAX, (float) VOL_INCR_MAX);
  3787. pch->fvolume_inc[ivol] = speed;
  3788. }
  3789. // set volume targets, using array pvolume as source volumes.
  3790. // set into channel volumes starting at ivol_offset index
  3791. // set cvol volumes
  3792. void ChannelSetVolTargets( channel_t *pch, int *pvolumes, int ivol_offset, int cvol )
  3793. {
  3794. int volume_target;
  3795. Assert(ivol_offset + cvol <= CCHANVOLUMES);
  3796. for (int i = 0; i < cvol; i++)
  3797. {
  3798. volume_target = pvolumes[i];
  3799. ChannelSetVolTarget( pch, ivol_offset + i, volume_target );
  3800. }
  3801. }
  3802. // Call once per frame, per channel:
  3803. // update all volume crossfades, from fvolume -> fvolume_target
  3804. // if current volume reaches target, set increment to 0
  3805. void ChannelUpdateVolXfade( channel_t *pch )
  3806. {
  3807. float fincr;
  3808. for (int i = 0; i < CCHANVOLUMES; i++)
  3809. {
  3810. fincr = pch->fvolume_inc[i];
  3811. if (fincr != 0.0)
  3812. {
  3813. pch->fvolume[i] += fincr;
  3814. // test for hit target
  3815. if (fincr > 0.0)
  3816. {
  3817. if (pch->fvolume[i] >= pch->fvolume_target[i])
  3818. ChannelStopVolXfade( pch, i );
  3819. }
  3820. else
  3821. {
  3822. if (pch->fvolume[i] <= pch->fvolume_target[i])
  3823. ChannelStopVolXfade( pch, i );
  3824. }
  3825. }
  3826. }
  3827. }
  3828. // =======================================================================
  3829. // S_StartDynamicSound
  3830. // =======================================================================
  3831. // Start a sound effect for the given entity on the given channel (ie; voice, weapon etc).
  3832. // Try to grab a channel out of the 8 dynamic spots available.
  3833. // Currently used for looping sounds, streaming sounds, sentences, and regular entity sounds.
  3834. // NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.
  3835. // Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100
  3836. // NOTE: it's not a good idea to play looping sounds through StartDynamicSound, because
  3837. // if the looping sound starts out of range, or is bumped from the buffer by another sound
  3838. // it will never be restarted. Use StartStaticSound (pass CHAN_STATIC to EMIT_SOUND or
  3839. // SV_StartSound.
  3840. int S_StartDynamicSound( StartSoundParams_t& params )
  3841. {
  3842. Assert( params.staticsound == false );
  3843. channel_t *target_chan;
  3844. int vol;
  3845. if ( !g_AudioDevice || !g_AudioDevice->IsActive())
  3846. return 0;
  3847. if (!params.pSfx)
  3848. return 0;
  3849. // For debugging to see the actual name of the sound...
  3850. char sndname[ MAX_OSPATH ];
  3851. Q_strncpy( sndname, params.pSfx->getname(), sizeof( sndname ) );
  3852. // Msg("Start sound %s\n", pSfx->getname() );
  3853. // override the entchannel to CHAN_STREAM if this is a
  3854. // non-voice stream sound.
  3855. if ( TestSoundChar(sndname, CHAR_STREAM ) && params.entchannel != CHAN_VOICE && params.entchannel != CHAN_VOICE2 )
  3856. params.entchannel = CHAN_STREAM;
  3857. vol = params.fvol*255;
  3858. if (vol > 255)
  3859. {
  3860. DevMsg("S_StartDynamicSound: %s volume > 255", sndname );
  3861. vol = 255;
  3862. }
  3863. THREAD_LOCK_SOUND();
  3864. if ( params.flags & (SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH) )
  3865. {
  3866. if ( S_AlterChannel( params.soundsource, params.entchannel, params.pSfx, vol, params.pitch, params.flags) )
  3867. return 0;
  3868. if ( params.flags & SND_STOP )
  3869. return 0;
  3870. // fall through - if we're not trying to stop the sound,
  3871. // and we didn't find it (it's not playing), go ahead and start it up
  3872. }
  3873. if (params.pitch == 0)
  3874. {
  3875. DevMsg ("Warning: S_StartDynamicSound (%s) Ignored, called with pitch 0\n", sndname );
  3876. return 0;
  3877. }
  3878. // pick a channel to play on
  3879. target_chan = SND_PickDynamicChannel(params.soundsource, params.entchannel, params.origin, params.pSfx, params.delay, (params.flags & SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL) != 0 );
  3880. if ( !target_chan )
  3881. return 0;
  3882. int channelIndex = (int)( target_chan - channels );
  3883. g_AudioDevice->ChannelReset( params.soundsource, channelIndex, target_chan->dist_mult );
  3884. #ifdef DEBUG_CHANNELS
  3885. {
  3886. char szTmp[128];
  3887. Q_snprintf(szTmp, sizeof( szTmp ), "Sound %s playing on Dynamic game channel %d\n", sndname, IWavstreamOfCh(target_chan));
  3888. Plat_DebugString(szTmp);
  3889. }
  3890. #endif
  3891. bool bIsSentence = TestSoundChar( sndname, CHAR_SENTENCE );
  3892. SND_ActivateChannel( target_chan );
  3893. ChannelClearVolumes( target_chan );
  3894. target_chan->userdata = params.userdata;
  3895. target_chan->initialStreamPosition = params.initialStreamPosition;
  3896. VectorCopy(params.origin, target_chan->origin);
  3897. VectorCopy(params.direction, target_chan->direction);
  3898. // never update positions if source entity is 0
  3899. target_chan->flags.bUpdatePositions = params.bUpdatePositions && (params.soundsource == 0 ? 0 : 1);
  3900. // reference_dist / (reference_power_level / actual_power_level)
  3901. target_chan->flags.m_bCompatibilityAttenuation = SNDLEVEL_IS_COMPATIBILITY_MODE( params.soundlevel );
  3902. if ( target_chan->flags.m_bCompatibilityAttenuation )
  3903. {
  3904. // Translate soundlevel from its 'encoded' value to a real soundlevel that we can use in the sound system.
  3905. params.soundlevel = SNDLEVEL_FROM_COMPATIBILITY_MODE( params.soundlevel );
  3906. }
  3907. target_chan->dist_mult = SNDLVL_TO_DIST_MULT( params.soundlevel );
  3908. S_SetChannelWavtype( target_chan, params.pSfx );
  3909. target_chan->master_vol = vol;
  3910. target_chan->soundsource = params.soundsource;
  3911. target_chan->entchannel = params.entchannel;
  3912. target_chan->basePitch = params.pitch;
  3913. target_chan->flags.isSentence = false;
  3914. target_chan->radius = 0;
  3915. target_chan->sfx = params.pSfx;
  3916. target_chan->special_dsp = params.specialdsp;
  3917. target_chan->flags.fromserver = params.fromserver;
  3918. target_chan->flags.bSpeaker = (params.flags & SND_SPEAKER) ? 1 : 0;
  3919. target_chan->speakerentity = params.speakerentity;
  3920. target_chan->flags.m_bShouldPause = (params.flags & SND_SHOULDPAUSE) ? 1 : 0;
  3921. // initialize dsp room mixing params
  3922. target_chan->dsp_mix_min = -1;
  3923. target_chan->dsp_mix_max = -1;
  3924. CAudioSource *pSource = NULL;
  3925. if ( bIsSentence )
  3926. {
  3927. // this is a sentence
  3928. // link all words and load the first word
  3929. // NOTE: sentence names stored in the cache lookup are
  3930. // prepended with a '!'. Sentence names stored in the
  3931. // sentence file do not have a leading '!'.
  3932. VOX_LoadSound( target_chan, PSkipSoundChars( sndname ) );
  3933. }
  3934. else
  3935. {
  3936. // regular or streamed sound fx
  3937. pSource = S_LoadSound( params.pSfx, target_chan );
  3938. if ( pSource && !IsValidSampleRate( pSource->SampleRate() ) )
  3939. {
  3940. Warning( "*** Invalid sample rate (%d) for sound '%s'.\n", pSource->SampleRate(), sndname );
  3941. }
  3942. if ( !pSource && !params.pSfx->m_bIsLateLoad )
  3943. {
  3944. Warning( "Failed to load sound \"%s\", file probably missing from disk/repository\n", sndname );
  3945. }
  3946. }
  3947. if (!target_chan->pMixer)
  3948. {
  3949. // couldn't load the sound's data, or sentence has 0 words (this is not an error)
  3950. S_FreeChannel( target_chan );
  3951. return 0;
  3952. }
  3953. int nSndShowStart = snd_showstart.GetInt();
  3954. // TODO: Support looping sounds through speakers.
  3955. // If the sound is from a speaker, and it's looping, ignore it.
  3956. if ( target_chan->flags.bSpeaker )
  3957. {
  3958. if ( params.pSfx->pSource && params.pSfx->pSource->IsLooped() )
  3959. {
  3960. if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
  3961. {
  3962. DevMsg("DynamicSound : Speaker ignored looping sound: %s\n", sndname );
  3963. }
  3964. S_FreeChannel( target_chan );
  3965. return 0;
  3966. }
  3967. }
  3968. S_SetChannelStereo( target_chan, pSource );
  3969. if (nSndShowStart == 5)
  3970. {
  3971. snd_showstart.SetValue(6); // debug: show gain for next spatialize only
  3972. nSndShowStart = 6;
  3973. }
  3974. // get sound type before we spatialize
  3975. MXR_GetMixGroupFromSoundsource( target_chan, params.soundsource, params.soundlevel );
  3976. // skip the trace on the first spatialization. This channel may be stolen
  3977. // by another sound played this frame. Defer the trace to the mix loop
  3978. SND_SpatializeFirstFrameNoTrace(target_chan);
  3979. if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
  3980. {
  3981. channel_t *pTargetChan = target_chan;
  3982. DevMsg( "DynamicSound %s : src %d : channel %d : %d dB : vol %.2f : time %.3f\n", sndname, params.soundsource, params.entchannel, params.soundlevel, params.fvol, g_pSoundServices->GetHostTime() );
  3983. if (nSndShowStart == 2 || nSndShowStart == 5)
  3984. DevMsg( "\t dspmix %1.2f : distmix %1.2f : dspface %1.2f : lvol %1.2f : cvol %1.2f : rvol %1.2f : rlvol %1.2f : rrvol %1.2f\n",
  3985. pTargetChan->dspmix, pTargetChan->distmix, pTargetChan->dspface,
  3986. pTargetChan->fvolume[IFRONT_LEFT], pTargetChan->fvolume[IFRONT_CENTER], pTargetChan->fvolume[IFRONT_RIGHT], pTargetChan->fvolume[IREAR_LEFT], pTargetChan->fvolume[IREAR_RIGHT] );
  3987. if (nSndShowStart == 3)
  3988. DevMsg( "\t x: %4f y: %4f z: %4f\n", pTargetChan->origin.x, pTargetChan->origin.y, pTargetChan->origin.z );
  3989. if ( snd_visualize.GetInt() )
  3990. {
  3991. CDebugOverlay::AddTextOverlay( pTargetChan->origin, 2.0f, sndname );
  3992. }
  3993. }
  3994. // If a client can't hear a sound when they FIRST receive the StartSound message,
  3995. // the client will never be able to hear that sound. This is so that out of
  3996. // range sounds don't fill the playback buffer. For streaming sounds, we bypass this optimization.
  3997. if ( BChannelLowVolume( target_chan, 0 ) && !toolframework->IsToolRecording() )
  3998. {
  3999. // Looping sounds don't use this optimization because they should stick around until they're killed.
  4000. // Also bypass for speech (GetSentence)
  4001. if ( !params.pSfx->pSource || (!params.pSfx->pSource->IsLooped() && !params.pSfx->pSource->GetSentence()) )
  4002. {
  4003. // if this is long sound, play the whole thing.
  4004. if (!SND_IsLongWave( target_chan ))
  4005. {
  4006. // DevMsg("S_StartDynamicSound: spatialized to 0 vol & ignored %s", sndname);
  4007. S_FreeChannel( target_chan );
  4008. return 0; // not audible at all
  4009. }
  4010. }
  4011. }
  4012. // Init client entity mouth movement vars
  4013. target_chan->flags.m_bIgnorePhonemes = ( params.flags & SND_IGNORE_PHONEMES ) != 0;
  4014. SND_InitMouth(target_chan);
  4015. if ( IsX360() && params.delay < 0 )
  4016. {
  4017. params.delay = 0;
  4018. target_chan->flags.delayed_start = true;
  4019. }
  4020. // Pre-startup delay. Compute # of samples over which to mix in zeros from data source before
  4021. // actually reading first set of samples
  4022. if ( params.delay != 0.0f )
  4023. {
  4024. Assert( target_chan->sfx );
  4025. Assert( target_chan->sfx->pSource );
  4026. // delay count is computed at the sampling rate of the source because the output rate will
  4027. // match the source rate when the sound is mixed
  4028. float rate = target_chan->sfx->pSource->SampleRate();
  4029. int delaySamples = (int)( params.delay * rate );
  4030. if ( params.delay > 0 )
  4031. {
  4032. target_chan->pMixer->SetStartupDelaySamples( delaySamples );
  4033. target_chan->flags.delayed_start = true;
  4034. }
  4035. else
  4036. {
  4037. int skipSamples = -delaySamples;
  4038. int totalSamples = target_chan->sfx->pSource->SampleCount();
  4039. if ( target_chan->sfx->pSource->IsLooped() )
  4040. {
  4041. skipSamples = skipSamples % totalSamples;
  4042. }
  4043. if ( skipSamples >= totalSamples )
  4044. {
  4045. S_FreeChannel( target_chan );
  4046. return 0;
  4047. }
  4048. target_chan->pitch = target_chan->basePitch * 0.01f;
  4049. target_chan->pMixer->SkipSamples( target_chan, skipSamples, rate, 0 );
  4050. target_chan->ob_gain_target = 1.0f;
  4051. target_chan->ob_gain = 1.0f;
  4052. target_chan->ob_gain_inc = 0.0;
  4053. target_chan->flags.bfirstpass = false;
  4054. target_chan->flags.delayed_start = true;
  4055. }
  4056. }
  4057. g_pSoundServices->OnSoundStarted( target_chan->guid, params, sndname );
  4058. return target_chan->guid;
  4059. }
  4060. //-----------------------------------------------------------------------------
  4061. // Purpose:
  4062. // Input : *name -
  4063. // Output : CSfxTable
  4064. //-----------------------------------------------------------------------------
  4065. CSfxTable *S_DummySfx( const char *name )
  4066. {
  4067. dummySfx.setname( name );
  4068. return &dummySfx;
  4069. }
  4070. /*
  4071. =================
  4072. S_StartStaticSound
  4073. =================
  4074. Start playback of a sound, loaded into the static portion of the channel array.
  4075. Currently, this should be used for looping ambient sounds, looping sounds
  4076. that should not be interrupted until complete, non-creature sentences,
  4077. and one-shot ambient streaming sounds. Can also play 'regular' sounds one-shot,
  4078. in case designers want to trigger regular game sounds.
  4079. Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100
  4080. NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in.
  4081. */
  4082. int S_StartStaticSound( StartSoundParams_t& params )
  4083. {
  4084. Assert( params.staticsound == true );
  4085. channel_t *ch;
  4086. CAudioSource *pSource = NULL;
  4087. if ( !g_AudioDevice->IsActive() )
  4088. return 0;
  4089. if ( !params.pSfx )
  4090. return 0;
  4091. // For debugging to see the actual name of the sound...
  4092. char sndname[ MAX_OSPATH ];
  4093. Q_strncpy( sndname, params.pSfx->getname(), sizeof( sndname ) );
  4094. // Msg("Start static sound %s\n", pSfx->getname() );
  4095. int vol = params.fvol * 255;
  4096. if ( vol > 255 )
  4097. {
  4098. DevMsg( "S_StartStaticSound: %s volume > 255", sndname );
  4099. vol = 255;
  4100. }
  4101. int nSndShowStart = snd_showstart.GetInt();
  4102. if ((params.flags & SND_STOP) && nSndShowStart > 0)
  4103. DevMsg("S_StartStaticSound: %s Stopped.\n", sndname);
  4104. if ((params.flags & SND_STOP) || (params.flags & SND_CHANGE_VOL) || (params.flags & SND_CHANGE_PITCH))
  4105. {
  4106. if (S_AlterChannel(params.soundsource, params.entchannel, params.pSfx, vol, params.pitch, params.flags) || (params.flags & SND_STOP))
  4107. return 0;
  4108. }
  4109. if ( params.pitch == 0 )
  4110. {
  4111. DevMsg( "Warning: S_StartStaticSound Ignored, called with pitch 0\n");
  4112. return 0;
  4113. }
  4114. // First, make sure the sound source entity is even in the PVS.
  4115. float flSoundRadius = 0.0f;
  4116. bool looping = false;
  4117. /*
  4118. CAudioSource *pSource = pSfx ? pSfx->pSource : NULL;
  4119. if ( pSource )
  4120. {
  4121. looping = pSource->IsLooped();
  4122. }
  4123. */
  4124. SpatializationInfo_t si;
  4125. si.info.Set(
  4126. params.soundsource,
  4127. params.entchannel,
  4128. params.pSfx ? sndname : "",
  4129. params.origin,
  4130. params.direction,
  4131. vol,
  4132. params.soundlevel,
  4133. looping,
  4134. params.pitch,
  4135. listener_origin,
  4136. params.speakerentity );
  4137. si.type = SpatializationInfo_t::SI_INCREATION;
  4138. si.pOrigin = NULL;
  4139. si.pAngles = NULL;
  4140. si.pflRadius = &flSoundRadius;
  4141. g_pSoundServices->GetSoundSpatialization( params.soundsource, si );
  4142. // pick a channel to play on from the static area
  4143. THREAD_LOCK_SOUND();
  4144. ch = SND_PickStaticChannel(params.soundsource, params.pSfx); // Autolooping sounds are always fixed origin(?)
  4145. if ( !ch )
  4146. return 0;
  4147. SND_ActivateChannel( ch );
  4148. ChannelClearVolumes( ch );
  4149. ch->userdata = params.userdata;
  4150. ch->initialStreamPosition = params.initialStreamPosition;
  4151. if ( ch->userdata != 0 )
  4152. {
  4153. g_pSoundServices->GetToolSpatialization( ch->userdata, ch->guid, si );
  4154. }
  4155. int channelIndex = ch - channels;
  4156. g_AudioDevice->ChannelReset( params.soundsource, channelIndex, ch->dist_mult );
  4157. #ifdef DEBUG_CHANNELS
  4158. {
  4159. char szTmp[128];
  4160. Q_snprintf(szTmp, sizeof( szTmp ), "Sound %s playing on Static game channel %d\n", sfxin->name, IWavstreamOfCh(ch));
  4161. Plat_DebugString(szTmp);
  4162. }
  4163. #endif
  4164. if ( TestSoundChar(sndname, CHAR_SENTENCE) )
  4165. {
  4166. // this is a sentence. link words to play in sequence.
  4167. // NOTE: sentence names stored in the cache lookup are
  4168. // prepended with a '!'. Sentence names stored in the
  4169. // sentence file do not have a leading '!'.
  4170. // link all words and load the first word
  4171. VOX_LoadSound( ch, PSkipSoundChars(sndname) );
  4172. }
  4173. else
  4174. {
  4175. // load regular or stream sound
  4176. pSource = S_LoadSound( params.pSfx, ch );
  4177. if ( pSource && !IsValidSampleRate( pSource->SampleRate() ) )
  4178. {
  4179. Warning( "*** Invalid sample rate (%d) for sound '%s'.\n", pSource->SampleRate(), sndname );
  4180. }
  4181. if ( !pSource && !params.pSfx->m_bIsLateLoad )
  4182. {
  4183. Warning( "Failed to load sound \"%s\", file probably missing from disk/repository\n", sndname );
  4184. }
  4185. ch->sfx = params.pSfx;
  4186. ch->flags.isSentence = false;
  4187. }
  4188. if ( !ch->pMixer )
  4189. {
  4190. // couldn't load sounds' data, or sentence has 0 words (not an error)
  4191. S_FreeChannel( ch );
  4192. return 0;
  4193. }
  4194. VectorCopy (params.origin, ch->origin);
  4195. VectorCopy (params.direction, ch->direction);
  4196. // never update positions if source entity is 0
  4197. ch->flags.bUpdatePositions = params.bUpdatePositions && (params.soundsource == 0 ? 0 : 1);
  4198. ch->master_vol = vol;
  4199. ch->flags.m_bCompatibilityAttenuation = SNDLEVEL_IS_COMPATIBILITY_MODE( params.soundlevel );
  4200. if ( ch->flags.m_bCompatibilityAttenuation )
  4201. {
  4202. // Translate soundlevel from its 'encoded' value to a real soundlevel that we can use in the sound system.
  4203. params.soundlevel = SNDLEVEL_FROM_COMPATIBILITY_MODE( params.soundlevel );
  4204. }
  4205. ch->dist_mult = SNDLVL_TO_DIST_MULT( params.soundlevel );
  4206. S_SetChannelWavtype( ch, params.pSfx );
  4207. ch->basePitch = params.pitch;
  4208. ch->soundsource = params.soundsource;
  4209. ch->entchannel = params.entchannel;
  4210. ch->special_dsp = params.specialdsp;
  4211. ch->flags.fromserver = params.fromserver;
  4212. ch->flags.bSpeaker = (params.flags & SND_SPEAKER) ? 1 : 0;
  4213. ch->speakerentity = params.speakerentity;
  4214. ch->flags.m_bShouldPause = (params.flags & SND_SHOULDPAUSE) ? 1 : 0;
  4215. // TODO: Support looping sounds through speakers.
  4216. // If the sound is from a speaker, and it's looping, ignore it.
  4217. if ( ch->flags.bSpeaker )
  4218. {
  4219. if ( params.pSfx->pSource && params.pSfx->pSource->IsLooped() )
  4220. {
  4221. if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
  4222. {
  4223. DevMsg("StaticSound : Speaker ignored looping sound: %s\n", sndname);
  4224. }
  4225. S_FreeChannel( ch );
  4226. return 0;
  4227. }
  4228. }
  4229. // set the default radius
  4230. ch->radius = flSoundRadius;
  4231. S_SetChannelStereo( ch, pSource );
  4232. // initialize dsp room mixing params
  4233. ch->dsp_mix_min = -1;
  4234. ch->dsp_mix_max = -1;
  4235. if (nSndShowStart == 5)
  4236. {
  4237. snd_showstart.SetValue(6); // display gain once only
  4238. nSndShowStart = 6;
  4239. }
  4240. // get sound type before we spatialize
  4241. MXR_GetMixGroupFromSoundsource( ch, params.soundsource, params.soundlevel );
  4242. // skip the trace on the first spatialization. This channel may be stolen
  4243. // by another sound played this frame. Defer the trace to the mix loop
  4244. SND_SpatializeFirstFrameNoTrace(ch);
  4245. // Init client entity mouth movement vars
  4246. ch->flags.m_bIgnorePhonemes = ( params.flags & SND_IGNORE_PHONEMES ) != 0;
  4247. SND_InitMouth( ch );
  4248. if ( IsX360() && params.delay < 0 )
  4249. {
  4250. // X360TEMP: Can't support yet, but going to.
  4251. params.delay = 0;
  4252. }
  4253. // Pre-startup delay. Compute # of samples over which to mix in zeros from data source before
  4254. // actually reading first set of samples
  4255. if ( params.delay != 0.0f )
  4256. {
  4257. Assert( ch->sfx );
  4258. Assert( ch->sfx->pSource );
  4259. float rate = ch->sfx->pSource->SampleRate();
  4260. int delaySamples = (int)( params.delay * rate * params.pitch * 0.01f );
  4261. ch->pMixer->SetStartupDelaySamples( delaySamples );
  4262. if ( params.delay > 0 )
  4263. {
  4264. ch->pMixer->SetStartupDelaySamples( delaySamples );
  4265. ch->flags.delayed_start = true;
  4266. }
  4267. else
  4268. {
  4269. int skipSamples = -delaySamples;
  4270. int totalSamples = ch->sfx->pSource->SampleCount();
  4271. if ( ch->sfx->pSource->IsLooped() )
  4272. {
  4273. skipSamples = skipSamples % totalSamples;
  4274. }
  4275. if ( skipSamples >= totalSamples )
  4276. {
  4277. S_FreeChannel( ch );
  4278. return 0;
  4279. }
  4280. ch->pitch = ch->basePitch * 0.01f;
  4281. ch->pMixer->SkipSamples( ch, skipSamples, rate, 0 );
  4282. ch->ob_gain_target = 1.0f;
  4283. ch->ob_gain = 1.0f;
  4284. ch->ob_gain_inc = 0.0f;
  4285. ch->flags.bfirstpass = false;
  4286. }
  4287. }
  4288. if ( S_IsMusic( ch ) )
  4289. {
  4290. // See if we have "music" of same name playing from "world" which means we save/restored this sound already. If so,
  4291. // kill the new version and update the soundsource
  4292. CChannelList list;
  4293. g_ActiveChannels.GetActiveChannels( list );
  4294. for ( int i = 0; i < list.Count(); i++ )
  4295. {
  4296. channel_t *pChannel = list.GetChannel(i);
  4297. // Don't mess with the channel we just created, of course
  4298. if ( ch == pChannel )
  4299. continue;
  4300. if ( ch->sfx != pChannel->sfx )
  4301. continue;
  4302. if ( pChannel->soundsource != SOUND_FROM_WORLD )
  4303. continue;
  4304. if ( !S_IsMusic( pChannel ) )
  4305. continue;
  4306. DevMsg( 1, "Hooking duplicate restored song track %s\n", sndname );
  4307. // the new channel will have an updated soundsource and probably
  4308. // has an updated pitch or volume since we are receiving this sound message
  4309. // after the sound has started playing (usually a volume change)
  4310. // copy that data out of the source
  4311. pChannel->soundsource = ch->soundsource;
  4312. pChannel->master_vol = ch->master_vol;
  4313. pChannel->basePitch = ch->basePitch;
  4314. pChannel->pitch = ch->pitch;
  4315. S_FreeChannel( ch );
  4316. return 0;
  4317. }
  4318. }
  4319. g_pSoundServices->OnSoundStarted( ch->guid, params, sndname );
  4320. if (nSndShowStart > 0 && nSndShowStart < 7 && nSndShowStart != 4)
  4321. {
  4322. DevMsg( "StaticSound %s : src %d : channel %d : %d dB : vol %.2f : radius %.0f : time %.3f\n", sndname, params.soundsource, params.entchannel, params.soundlevel, params.fvol, flSoundRadius, g_pSoundServices->GetHostTime() );
  4323. if (nSndShowStart == 2 || nSndShowStart == 5)
  4324. DevMsg( "\t dspmix %1.2f : distmix %1.2f : dspface %1.2f : lvol %1.2f : cvol %1.2f : rvol %1.2f : rlvol %1.2f : rrvol %1.2f\n",
  4325. ch->dspmix, ch->distmix, ch->dspface,
  4326. ch->fvolume[IFRONT_LEFT], ch->fvolume[IFRONT_CENTER], ch->fvolume[IFRONT_RIGHT], ch->fvolume[IREAR_LEFT], ch->fvolume[IREAR_RIGHT] );
  4327. if (nSndShowStart == 3)
  4328. DevMsg( "\t x: %4f y: %4f z: %4f\n", ch->origin.x, ch->origin.y, ch->origin.z );
  4329. }
  4330. return ch->guid;
  4331. }
  4332. #ifdef STAGING_ONLY
  4333. static ConVar snd_filter( "snd_filter", "", FCVAR_CHEAT );
  4334. #endif // STAGING_ONLY
  4335. int S_StartSound( StartSoundParams_t& params )
  4336. {
  4337. if( ! params.pSfx )
  4338. {
  4339. return 0;
  4340. }
  4341. #ifdef STAGING_ONLY
  4342. if ( snd_filter.GetString()[ 0 ] && !Q_stristr( params.pSfx->getname(), snd_filter.GetString() ) )
  4343. {
  4344. return 0;
  4345. }
  4346. #endif // STAGING_ONLY
  4347. if ( IsX360() && params.delay < 0 && !params.initialStreamPosition && params.pSfx )
  4348. {
  4349. // calculate an initial stream position from the expected sample position
  4350. float rate = params.pSfx->pSource->SampleRate();
  4351. int samplePosition = (int)( -params.delay * rate * params.pitch * 0.01f );
  4352. params.initialStreamPosition = params.pSfx->pSource->SampleToStreamPosition( samplePosition );
  4353. }
  4354. if ( params.staticsound )
  4355. {
  4356. VPROF_( "StartStaticSound", 0, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
  4357. return S_StartStaticSound( params );
  4358. }
  4359. else
  4360. {
  4361. VPROF_( "StartDynamicSound", 0, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
  4362. return S_StartDynamicSound( params );
  4363. }
  4364. }
  4365. // Restart all the sounds on the specified channel
  4366. inline bool IsChannelLooped( int iChannel )
  4367. {
  4368. return (channels[iChannel].sfx &&
  4369. channels[iChannel].sfx->pSource &&
  4370. channels[iChannel].sfx->pSource->IsLooped() );
  4371. }
  4372. int S_GetCurrentStaticSounds( SoundInfo_t *pResult, int nSizeResult, int entchannel )
  4373. {
  4374. int nSpaceRemaining = nSizeResult;
  4375. for (int i = MAX_DYNAMIC_CHANNELS; i < total_channels && nSpaceRemaining; i++)
  4376. {
  4377. if ( channels[i].entchannel == entchannel && channels[i].sfx )
  4378. {
  4379. pResult->Set( channels[i].soundsource,
  4380. channels[i].entchannel,
  4381. channels[i].sfx->getname(),
  4382. channels[i].origin,
  4383. channels[i].direction,
  4384. ( (float)channels[i].master_vol / 255.0 ),
  4385. DIST_MULT_TO_SNDLVL( channels[i].dist_mult ),
  4386. IsChannelLooped( i ),
  4387. channels[i].basePitch,
  4388. listener_origin,
  4389. channels[i].speakerentity );
  4390. pResult++;
  4391. nSpaceRemaining--;
  4392. }
  4393. }
  4394. return (nSizeResult - nSpaceRemaining);
  4395. }
  4396. // Stop all sounds for entity on a channel.
  4397. void S_StopSound(int soundsource, int entchannel)
  4398. {
  4399. THREAD_LOCK_SOUND();
  4400. CChannelList list;
  4401. g_ActiveChannels.GetActiveChannels( list );
  4402. for ( int i = 0; i < list.Count(); i++ )
  4403. {
  4404. channel_t *pChannel = list.GetChannel(i);
  4405. if (pChannel->soundsource == soundsource
  4406. && pChannel->entchannel == entchannel)
  4407. {
  4408. S_FreeChannel( pChannel );
  4409. }
  4410. }
  4411. }
  4412. channel_t *S_FindChannelByGuid( int guid )
  4413. {
  4414. CChannelList list;
  4415. g_ActiveChannels.GetActiveChannels( list );
  4416. for ( int i = 0; i < list.Count(); i++ )
  4417. {
  4418. channel_t *pChannel = list.GetChannel(i);
  4419. if ( pChannel->guid == guid )
  4420. {
  4421. return pChannel;
  4422. }
  4423. }
  4424. return NULL;
  4425. }
  4426. //-----------------------------------------------------------------------------
  4427. // Purpose:
  4428. // Input : guid -
  4429. //-----------------------------------------------------------------------------
  4430. void S_StopSoundByGuid( int guid )
  4431. {
  4432. THREAD_LOCK_SOUND();
  4433. channel_t *pChannel = S_FindChannelByGuid( guid );
  4434. if ( pChannel )
  4435. {
  4436. S_FreeChannel( pChannel );
  4437. }
  4438. }
  4439. //-----------------------------------------------------------------------------
  4440. // Purpose:
  4441. // Input : guid -
  4442. //-----------------------------------------------------------------------------
  4443. float S_SoundDurationByGuid( int guid )
  4444. {
  4445. channel_t *pChannel = S_FindChannelByGuid( guid );
  4446. if ( !pChannel || !pChannel->sfx )
  4447. return 0.0f;
  4448. // NOTE: Looping sounds will return the length of a single loop
  4449. // Use S_IsLoopingSoundByGuid to see if they are looped
  4450. float flRate = pChannel->sfx->pSource->SampleRate() * pChannel->basePitch * 0.01f;
  4451. int nTotalSamples = pChannel->sfx->pSource->SampleCount();
  4452. return (flRate != 0.0f) ? nTotalSamples / flRate : 0.0f;
  4453. }
  4454. //-----------------------------------------------------------------------------
  4455. // Is this sound a looping sound?
  4456. //-----------------------------------------------------------------------------
  4457. bool S_IsLoopingSoundByGuid( int guid )
  4458. {
  4459. channel_t *pChannel = S_FindChannelByGuid( guid );
  4460. if ( !pChannel || !pChannel->sfx )
  4461. return false;
  4462. return( pChannel->sfx->pSource->IsLooped() );
  4463. }
  4464. //-----------------------------------------------------------------------------
  4465. // Purpose: Note that the guid is preincremented, so we can just return the current value as the "last sound" indicator
  4466. // Input : -
  4467. // Output : int
  4468. //-----------------------------------------------------------------------------
  4469. int S_GetGuidForLastSoundEmitted()
  4470. {
  4471. return s_nSoundGuid;
  4472. }
  4473. //-----------------------------------------------------------------------------
  4474. // Purpose:
  4475. // Input : guid -
  4476. // Output : Returns true on success, false on failure.
  4477. //-----------------------------------------------------------------------------
  4478. bool S_IsSoundStillPlaying( int guid )
  4479. {
  4480. channel_t *pChannel = S_FindChannelByGuid( guid );
  4481. return pChannel != NULL ? true : false;
  4482. }
  4483. //-----------------------------------------------------------------------------
  4484. // Purpose:
  4485. // Input : guid -
  4486. // fvol -
  4487. //-----------------------------------------------------------------------------
  4488. void S_SetVolumeByGuid( int guid, float fvol )
  4489. {
  4490. channel_t *pChannel = S_FindChannelByGuid( guid );
  4491. pChannel->master_vol = 255.0f * clamp( fvol, 0.0f, 1.0f );
  4492. }
  4493. //-----------------------------------------------------------------------------
  4494. // Purpose:
  4495. // Input : guid -
  4496. // Output : float
  4497. //-----------------------------------------------------------------------------
  4498. float S_GetElapsedTimeByGuid( int guid )
  4499. {
  4500. channel_t *pChannel = S_FindChannelByGuid( guid );
  4501. if ( !pChannel )
  4502. return 0.0f;
  4503. CAudioMixer *mixer = pChannel->pMixer;
  4504. if ( !mixer )
  4505. return 0.0f;
  4506. float elapsed = mixer->GetSamplePosition() / ( mixer->GetSource()->SampleRate() * pChannel->pitch * 0.01f );
  4507. return elapsed;
  4508. }
  4509. //-----------------------------------------------------------------------------
  4510. // Purpose:
  4511. // Input : sndlist -
  4512. //-----------------------------------------------------------------------------
  4513. void S_GetActiveSounds( CUtlVector< SndInfo_t >& sndlist )
  4514. {
  4515. CChannelList list;
  4516. g_ActiveChannels.GetActiveChannels( list );
  4517. for ( int i = 0; i < list.Count(); i++ )
  4518. {
  4519. channel_t *ch = list.GetChannel(i);
  4520. SndInfo_t info;
  4521. info.m_nGuid = ch->guid;
  4522. info.m_filenameHandle = ch->sfx ? ch->sfx->GetFileNameHandle() : NULL;
  4523. info.m_nSoundSource = ch->soundsource;
  4524. info.m_nChannel = ch->entchannel;
  4525. // If a sound is being played through a speaker entity (e.g., on a monitor,), this is the
  4526. // entity upon which to show the lips moving, if the sound has sentence data
  4527. info.m_nSpeakerEntity = ch->speakerentity;
  4528. info.m_flVolume = (float)ch->master_vol / 255.0f;
  4529. info.m_flLastSpatializedVolume = ch->last_vol;
  4530. // Radius of this sound effect (spatialization is different within the radius)
  4531. info.m_flRadius = ch->radius;
  4532. info.m_nPitch = ch->basePitch;
  4533. info.m_pOrigin = &ch->origin;
  4534. info.m_pDirection = &ch->direction;
  4535. // if true, assume sound source can move and update according to entity
  4536. info.m_bUpdatePositions = ch->flags.bUpdatePositions;
  4537. // true if playing linked sentence
  4538. info.m_bIsSentence = ch->flags.isSentence;
  4539. // if true, bypass all dsp processing for this sound (ie: music)
  4540. info.m_bDryMix = ch->flags.bdry;
  4541. // true if sound is playing through in-game speaker entity.
  4542. info.m_bSpeaker = ch->flags.bSpeaker;
  4543. // true if sound is using special DSP effect
  4544. info.m_bSpecialDSP = ( ch->special_dsp != 0 );
  4545. // for snd_show, networked sounds get colored differently than local sounds
  4546. info.m_bFromServer = ch->flags.fromserver;
  4547. sndlist.AddToTail( info );
  4548. }
  4549. }
  4550. void S_StopAllSounds( bool bClear )
  4551. {
  4552. THREAD_LOCK_SOUND();
  4553. int i;
  4554. if ( !g_AudioDevice )
  4555. return;
  4556. if ( !g_AudioDevice->IsActive() )
  4557. return;
  4558. total_channels = MAX_DYNAMIC_CHANNELS; // no statics
  4559. CChannelList list;
  4560. g_ActiveChannels.GetActiveChannels( list );
  4561. for ( i = 0; i < list.Count(); i++ )
  4562. {
  4563. channel_t *pChannel = list.GetChannel(i);
  4564. if ( channels[i].sfx )
  4565. {
  4566. DevMsg( 1, "%2d:Stopped sound %s\n", i, channels[i].sfx->getname() );
  4567. }
  4568. S_FreeChannel( pChannel );
  4569. }
  4570. Q_memset( channels, 0, MAX_CHANNELS * sizeof(channel_t) );
  4571. if ( bClear )
  4572. {
  4573. S_ClearBuffer();
  4574. }
  4575. // Clear any remaining soundfade
  4576. memset( &soundfade, 0, sizeof( soundfade ) );
  4577. g_AudioDevice->StopAllSounds();
  4578. Assert( g_ActiveChannels.GetActiveCount() == 0 );
  4579. }
  4580. void S_StopAllSoundsC( void )
  4581. {
  4582. S_StopAllSounds( true );
  4583. }
  4584. void S_OnLoadScreen( bool value )
  4585. {
  4586. s_bOnLoadScreen = value;
  4587. }
  4588. void S_ClearBuffer( void )
  4589. {
  4590. if ( !g_AudioDevice )
  4591. return;
  4592. g_AudioDevice->ClearBuffer();
  4593. DSP_ClearState();
  4594. MIX_ClearAllPaintBuffers( PAINTBUFFER_SIZE, true );
  4595. }
  4596. //-----------------------------------------------------------------------------
  4597. // Purpose:
  4598. // Input : percent -
  4599. // holdtime -
  4600. // intime -
  4601. // outtime -
  4602. //-----------------------------------------------------------------------------
  4603. void S_SoundFade( float percent, float holdtime, float intime, float outtime )
  4604. {
  4605. soundfade.starttime = g_pSoundServices->GetHostTime();
  4606. soundfade.initial_percent = percent;
  4607. soundfade.fadeouttime = outtime;
  4608. soundfade.holdtime = holdtime;
  4609. soundfade.fadeintime = intime;
  4610. }
  4611. //-----------------------------------------------------------------------------
  4612. // Purpose: Modulates sound volume on the client.
  4613. //-----------------------------------------------------------------------------
  4614. void S_UpdateSoundFade(void)
  4615. {
  4616. float totaltime;
  4617. float f;
  4618. // Determine current fade value.
  4619. // Assume no fading remains
  4620. soundfade.percent = 0;
  4621. totaltime = soundfade.fadeouttime + soundfade.fadeintime + soundfade.holdtime;
  4622. float elapsed = g_pSoundServices->GetHostTime() - soundfade.starttime;
  4623. // Clock wrapped or reset (BUG) or we've gone far enough
  4624. if ( elapsed < 0.0f || elapsed >= totaltime || totaltime <= 0.0f )
  4625. {
  4626. return;
  4627. }
  4628. // We are in the fade time, so determine amount of fade.
  4629. if ( soundfade.fadeouttime > 0.0f && ( elapsed < soundfade.fadeouttime ) )
  4630. {
  4631. // Ramp up
  4632. f = elapsed / soundfade.fadeouttime;
  4633. }
  4634. // Inside the hold time
  4635. else if ( elapsed <= ( soundfade.fadeouttime + soundfade.holdtime ) )
  4636. {
  4637. // Stay
  4638. f = 1.0f;
  4639. }
  4640. else
  4641. {
  4642. // Ramp down
  4643. f = ( elapsed - ( soundfade.fadeouttime + soundfade.holdtime ) ) / soundfade.fadeintime;
  4644. // backward interpolated...
  4645. f = 1.0f - f;
  4646. }
  4647. // Spline it.
  4648. f = SimpleSpline( f );
  4649. f = clamp( f, 0.0f, 1.0f );
  4650. soundfade.percent = soundfade.initial_percent * f;
  4651. }
  4652. //=============================================================================
  4653. // Global Voice Ducker - enabled in vcd scripts, when characters deliver important dialog. Overrides all
  4654. // other mixer ducking, and ducks all other sounds except dialog.
  4655. ConVar snd_ducktovolume( "snd_ducktovolume", "0.55", FCVAR_ARCHIVE );
  4656. ConVar snd_duckerattacktime( "snd_duckerattacktime", "0.5", FCVAR_ARCHIVE );
  4657. ConVar snd_duckerreleasetime( "snd_duckerreleasetime", "2.5", FCVAR_ARCHIVE );
  4658. ConVar snd_duckerthreshold("snd_duckerthreshold", "0.15", FCVAR_ARCHIVE );
  4659. static void S_UpdateVoiceDuck( int voiceChannelCount, int voiceChannelMaxVolume, float frametime )
  4660. {
  4661. float volume_when_ducked = snd_ducktovolume.GetFloat();
  4662. int volume_threshold = (int)(snd_duckerthreshold.GetFloat() * 255.0);
  4663. float duckTarget = 1.0;
  4664. if ( voiceChannelCount > 0 )
  4665. {
  4666. voiceChannelMaxVolume = clamp(voiceChannelMaxVolume, 0, 255);
  4667. // duckTarget = RemapVal( voiceChannelMaxVolume, 0, 255, 1.0, volume_when_ducked );
  4668. // KB: Change: ducker now active if any character is speaking above threshold volume.
  4669. // KB: Active ducker drops all volumes to volumes * snd_duckvolume
  4670. if ( voiceChannelMaxVolume > volume_threshold )
  4671. duckTarget = volume_when_ducked;
  4672. }
  4673. float rate = ( duckTarget < g_DuckScale ) ? snd_duckerattacktime.GetFloat() : snd_duckerreleasetime.GetFloat();
  4674. g_DuckScale = Approach( duckTarget, g_DuckScale, frametime * ((1-volume_when_ducked) / rate) );
  4675. }
  4676. // set 2d forward vector, given 3d right vector.
  4677. // NOTE: this should only be used for a listener forward
  4678. // vector from a listener right vector. It is not a general use routine.
  4679. void ConvertListenerVectorTo2D( Vector *pvforward, Vector *pvright )
  4680. {
  4681. // get 2d forward direction vector, ignoring pitch angle
  4682. QAngle angles2d;
  4683. Vector source2d;
  4684. Vector listener_forward2d;
  4685. source2d = *pvright;
  4686. source2d.z = 0.0;
  4687. VectorNormalize(source2d);
  4688. // convert right vector to euler angles (yaw & pitch)
  4689. VectorAngles(source2d, angles2d);
  4690. // get forward angle of listener
  4691. angles2d[PITCH] = 0;
  4692. angles2d[YAW] += 90; // rotate 90 ccw
  4693. angles2d[ROLL] = 0;
  4694. if (angles2d[YAW] >= 360)
  4695. angles2d[YAW] -= 360;
  4696. AngleVectors(angles2d, &listener_forward2d);
  4697. VectorNormalize(listener_forward2d);
  4698. *pvforward = listener_forward2d;
  4699. }
  4700. // If this is nonzero, we will only spatialize some of the static
  4701. // channels each frame. The round robin will spatialize 1 / (2 ^ x)
  4702. // of the spatial channels each frame.
  4703. ConVar snd_spatialize_roundrobin( "snd_spatialize_roundrobin", "0", FCVAR_ALLOWED_IN_COMPETITIVE, "Lowend optimization: if nonzero, spatialize only a fraction of sound channels each frame. 1/2^x of channels will be spatialized per frame." );
  4704. /*
  4705. ============
  4706. S_Update
  4707. Called once each time through the main loop
  4708. ============
  4709. */
  4710. void S_Update( const AudioState_t *pAudioState )
  4711. {
  4712. VPROF("S_Update");
  4713. channel_t *ch;
  4714. channel_t *combine;
  4715. static unsigned int s_roundrobin = 0 ; ///< number of times this function is called.
  4716. ///< used instead of host_frame because that number
  4717. ///< isn't necessarily available here (sez Yahn).
  4718. if ( !g_AudioDevice->IsActive() )
  4719. return;
  4720. g_SndMutex.Lock();
  4721. // Update any client side sound fade
  4722. S_UpdateSoundFade();
  4723. if ( pAudioState )
  4724. {
  4725. VectorCopy( pAudioState->m_Origin, listener_origin );
  4726. AngleVectors( pAudioState->m_Angles, &listener_forward, &listener_right, &listener_up );
  4727. s_bIsListenerUnderwater = pAudioState->m_bIsUnderwater;
  4728. }
  4729. else
  4730. {
  4731. VectorCopy( vec3_origin, listener_origin );
  4732. VectorCopy( vec3_origin, listener_forward );
  4733. VectorCopy( vec3_origin, listener_right );
  4734. VectorCopy( vec3_origin, listener_up );
  4735. s_bIsListenerUnderwater = false;
  4736. }
  4737. g_AudioDevice->UpdateListener( listener_origin, listener_forward, listener_right, listener_up );
  4738. combine = NULL;
  4739. int voiceChannelCount = 0;
  4740. int voiceChannelMaxVolume = 0;
  4741. // reset traceline counter for this frame
  4742. g_snd_trace_count = 0;
  4743. // calculate distance to nearest walls, update dsp_spatial
  4744. // updates one wall only per frame (one trace per frame)
  4745. SND_SetSpatialDelays();
  4746. // updates dsp_room if automatic room detection enabled
  4747. DAS_CheckNewRoomDSP();
  4748. // update spatialization for static and dynamic sounds
  4749. CChannelList list;
  4750. g_ActiveChannels.GetActiveChannels( list );
  4751. if (snd_spatialize_roundrobin.GetInt() == 0)
  4752. {
  4753. // spatialize each channel each time
  4754. for ( int i = 0; i < list.Count(); i++ )
  4755. {
  4756. ch = list.GetChannel(i);
  4757. Assert(ch->sfx);
  4758. Assert(ch->activeIndex > 0);
  4759. SND_Spatialize(ch); // respatialize channel
  4760. if ( ch->sfx->pSource && ch->sfx->pSource->IsVoiceSource() )
  4761. {
  4762. voiceChannelCount++;
  4763. voiceChannelMaxVolume = max(voiceChannelMaxVolume, ChannelGetMaxVol( ch) );
  4764. }
  4765. }
  4766. }
  4767. else // lowend performance improvement: spatialize only some channels each frame.
  4768. {
  4769. unsigned int robinmask = (1 << snd_spatialize_roundrobin.GetInt()) - 1;
  4770. // now do static channels
  4771. for ( int i = 0 ; i < list.Count() ; ++i )
  4772. {
  4773. ch = list.GetChannel(i);
  4774. Assert(ch->sfx);
  4775. Assert(ch->activeIndex > 0);
  4776. // need to check bfirstpass because sound tracing may have been deferred
  4777. if ( ch->flags.bfirstpass || (robinmask & s_roundrobin) == ( i & robinmask ) )
  4778. {
  4779. SND_Spatialize(ch); // respatialize channel
  4780. }
  4781. if ( ch->sfx->pSource && ch->sfx->pSource->IsVoiceSource() )
  4782. {
  4783. voiceChannelCount++;
  4784. voiceChannelMaxVolume = max( voiceChannelMaxVolume, ChannelGetMaxVol( ch) );
  4785. }
  4786. }
  4787. ++s_roundrobin;
  4788. }
  4789. SND_ChannelTraceReset();
  4790. // set new target for voice ducking
  4791. float frametime = g_pSoundServices->GetHostFrametime();
  4792. S_UpdateVoiceDuck( voiceChannelCount, voiceChannelMaxVolume, frametime );
  4793. // update x360 music volume
  4794. g_DashboardMusicMixValue = Approach( g_DashboardMusicMixTarget, g_DashboardMusicMixValue, g_DashboardMusicFadeRate * frametime );
  4795. //
  4796. // debugging output
  4797. //
  4798. if (snd_show.GetInt())
  4799. {
  4800. con_nprint_t np;
  4801. np.time_to_live = 2.0f;
  4802. np.fixed_width_font = true;
  4803. int total = 0;
  4804. CChannelList activeChannels;
  4805. g_ActiveChannels.GetActiveChannels( activeChannels );
  4806. for ( int i = 0; i < activeChannels.Count(); i++ )
  4807. {
  4808. channel_t *channel = activeChannels.GetChannel(i);
  4809. if ( !channel->sfx )
  4810. continue;
  4811. np.index = total + 2;
  4812. if ( channel->flags.fromserver )
  4813. {
  4814. np.color[0] = 1.0;
  4815. np.color[1] = 0.8;
  4816. np.color[2] = 0.1;
  4817. }
  4818. else
  4819. {
  4820. np.color[0] = 0.1;
  4821. np.color[1] = 0.9;
  4822. np.color[2] = 1.0;
  4823. }
  4824. unsigned int sampleCount = RemainingSamples( channel );
  4825. float timeleft = (float)sampleCount / (float)channel->sfx->pSource->SampleRate();
  4826. bool bLooping = channel->sfx->pSource->IsLooped();
  4827. if (snd_surround.GetInt() < 4)
  4828. {
  4829. Con_NXPrintf ( &np, "%02i l(%03d) r(%03d) vol(%03d) ent(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s",
  4830. total+ 1,
  4831. (int)channel->fvolume[IFRONT_LEFT],
  4832. (int)channel->fvolume[IFRONT_RIGHT],
  4833. channel->master_vol,
  4834. channel->soundsource,
  4835. (int)channel->origin[0],
  4836. (int)channel->origin[1],
  4837. (int)channel->origin[2],
  4838. timeleft,
  4839. bLooping,
  4840. channel->sfx->getname());
  4841. }
  4842. else
  4843. {
  4844. Con_NXPrintf ( &np, "%02i l(%03d) c(%03d) r(%03d) rl(%03d) rr(%03d) vol(%03d) ent(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s",
  4845. total+ 1,
  4846. (int)channel->fvolume[IFRONT_LEFT],
  4847. (int)channel->fvolume[IFRONT_CENTER],
  4848. (int)channel->fvolume[IFRONT_RIGHT],
  4849. (int)channel->fvolume[IREAR_LEFT],
  4850. (int)channel->fvolume[IREAR_RIGHT],
  4851. channel->master_vol,
  4852. channel->soundsource,
  4853. (int)channel->origin[0],
  4854. (int)channel->origin[1],
  4855. (int)channel->origin[2],
  4856. timeleft,
  4857. bLooping,
  4858. channel->sfx->getname());
  4859. }
  4860. if ( snd_visualize.GetInt() )
  4861. {
  4862. CDebugOverlay::AddTextOverlay( channel->origin, 0.05f, channel->sfx->getname() );
  4863. }
  4864. total++;
  4865. }
  4866. while ( total <= 128 )
  4867. {
  4868. Con_NPrintf( total + 2, "" );
  4869. total++;
  4870. }
  4871. }
  4872. g_SndMutex.Unlock();
  4873. if ( s_bOnLoadScreen )
  4874. return;
  4875. // not time to update yet?
  4876. double tNow = Plat_FloatTime();
  4877. // this is the last time we ran a sound frame
  4878. g_LastSoundFrame = tNow;
  4879. // this is the last time we did mixing (extraupdate also advances this if it mixes)
  4880. g_LastMixTime = tNow;
  4881. // mix some sound
  4882. // try to stay at least one frame + mixahead ahead in the mix.
  4883. g_EstFrameTime = (g_EstFrameTime * 0.9f) + (g_pSoundServices->GetHostFrametime() * 0.1f);
  4884. S_Update_( g_EstFrameTime + snd_mixahead.GetFloat() );
  4885. }
  4886. CON_COMMAND( snd_dumpclientsounds, "Dump sounds to VXConsole" )
  4887. {
  4888. con_nprint_t np;
  4889. np.time_to_live = 2.0f;
  4890. np.fixed_width_font = true;
  4891. int total = 0;
  4892. CChannelList list;
  4893. g_ActiveChannels.GetActiveChannels( list );
  4894. for ( int i = 0; i < list.Count(); i++ )
  4895. {
  4896. channel_t *ch = list.GetChannel(i);
  4897. if ( !ch->sfx )
  4898. continue;
  4899. unsigned int sampleCount = RemainingSamples( ch );
  4900. float timeleft = (float)sampleCount / (float)ch->sfx->pSource->SampleRate();
  4901. bool bLooping = ch->sfx->pSource->IsLooped();
  4902. const char *pszclassname = GetClientClassname(ch->soundsource);
  4903. Msg( "%02i %s l(%03d) c(%03d) r(%03d) rl(%03d) rr(%03d) vol(%03d) pos(%6d %6d %6d) timeleft(%f) looped(%d) %50s chan:%d ent(%03d):%s\n",
  4904. total+ 1,
  4905. ch->flags.fromserver ? "SERVER" : "CLIENT",
  4906. (int)ch->fvolume[IFRONT_LEFT],
  4907. (int)ch->fvolume[IFRONT_CENTER],
  4908. (int)ch->fvolume[IFRONT_RIGHT],
  4909. (int)ch->fvolume[IREAR_LEFT],
  4910. (int)ch->fvolume[IREAR_RIGHT],
  4911. ch->master_vol,
  4912. (int)ch->origin[0],
  4913. (int)ch->origin[1],
  4914. (int)ch->origin[2],
  4915. timeleft,
  4916. bLooping,
  4917. ch->sfx->getname(),
  4918. ch->entchannel,
  4919. ch->soundsource,
  4920. pszclassname ? pszclassname : "NULL" );
  4921. total++;
  4922. }
  4923. }
  4924. //-----------------------------------------------------------------------------
  4925. // Set g_soundtime to number of full samples that have been transfered out to hardware
  4926. // since start.
  4927. //-----------------------------------------------------------------------------
  4928. void GetSoundTime(void)
  4929. {
  4930. int fullsamples;
  4931. int sampleOutCount;
  4932. // size of output buffer in *full* 16 bit samples
  4933. // A 2 channel device has a *full* sample consisting of a 16 bit LR pair.
  4934. // A 1 channel device has a *full* sample consiting of a 16 bit single sample.
  4935. fullsamples = g_AudioDevice->DeviceSampleCount() / g_AudioDevice->DeviceChannels();
  4936. // NOTE: it is possible to miscount buffers if it has wrapped twice between
  4937. // calls to S_Update. However, since the output buffer size is > 1 second of sound,
  4938. // this should only occur for framerates lower than 1hz
  4939. // sampleOutCount is counted in 16 bit *full* samples, of number of samples output to hardware
  4940. // for current output buffer
  4941. sampleOutCount = g_AudioDevice->GetOutputPosition();
  4942. if ( sampleOutCount < s_oldsampleOutCount )
  4943. {
  4944. // buffer wrapped
  4945. s_buffers++;
  4946. if ( g_paintedtime > 0x70000000 )
  4947. {
  4948. // time to chop things off to avoid 32 bit limits
  4949. s_buffers = 0;
  4950. g_paintedtime = fullsamples;
  4951. S_StopAllSounds( true );
  4952. }
  4953. }
  4954. s_oldsampleOutCount = sampleOutCount;
  4955. if ( cl_movieinfo.IsRecording() || IsReplayRendering() )
  4956. {
  4957. // when recording a replay, we look at the record frame rate, not the engine frame rate
  4958. #if defined( REPLAY_ENABLED )
  4959. extern IClientReplayContext *g_pClientReplayContext;
  4960. if ( IsReplayRendering() )
  4961. {
  4962. IReplayMovieRenderer *pMovieRenderer = (g_pClientReplayContext != NULL) ? g_pClientReplayContext->GetMovieRenderer() : NULL;
  4963. if ( pMovieRenderer && pMovieRenderer->IsAudioSyncFrame() )
  4964. {
  4965. float t = g_pSoundServices->GetHostTime();
  4966. if ( s_lastsoundtime != t )
  4967. {
  4968. float frameTime = pMovieRenderer->GetRecordingFrameDuration();
  4969. float fSamples = frameTime * (float) g_AudioDevice->DeviceDmaSpeed() + g_ReplaySoundTimeFracAccumulator;
  4970. float intPart = (float) floor( fSamples );
  4971. g_ReplaySoundTimeFracAccumulator = fSamples - intPart;
  4972. g_soundtime += (int) intPart;
  4973. s_lastsoundtime = t;
  4974. }
  4975. }
  4976. }
  4977. else // cl_movieinfo.IsRecording()
  4978. // in movie, just mix one frame worth of sound
  4979. #endif
  4980. {
  4981. float t = g_pSoundServices->GetHostTime();
  4982. if ( s_lastsoundtime != t )
  4983. {
  4984. g_soundtime += g_pSoundServices->GetHostFrametime() * g_AudioDevice->DeviceDmaSpeed();
  4985. s_lastsoundtime = t;
  4986. }
  4987. }
  4988. }
  4989. else
  4990. {
  4991. // g_soundtime indicates how many *full* samples have actually been
  4992. // played out to dma
  4993. g_soundtime = s_buffers*fullsamples + sampleOutCount;
  4994. }
  4995. }
  4996. void S_ExtraUpdate( void )
  4997. {
  4998. if ( !g_AudioDevice || !g_pSoundServices )
  4999. return;
  5000. if ( !g_AudioDevice->IsActive() )
  5001. return;
  5002. if ( s_bOnLoadScreen )
  5003. return;
  5004. if ( snd_noextraupdate.GetInt() || cl_movieinfo.IsRecording() || IsReplayRendering() )
  5005. return; // don't pollute timings
  5006. // If listener position and orientation has not yet been updated (ie: no call to S_Update since level load)
  5007. // then don't mix. Important - mixing with listener at 'false' origin causes
  5008. // some sounds to incorrectly spatialize to 0 volume, killing them before they can play.
  5009. if ((listener_origin == vec3_origin) &&
  5010. (listener_forward == vec3_origin) &&
  5011. (listener_right == vec3_origin) &&
  5012. (listener_up == vec3_origin) )
  5013. return;
  5014. // Only mix if you have used up 90% of the mixahead buffer
  5015. double tNow = Plat_FloatTime();
  5016. float delta = (tNow - g_LastMixTime);
  5017. // we know we were at least snd_mixahead seconds ahead of the output the last time we did mixing
  5018. // if we're not close to running out just exit to avoid small mix batches
  5019. if ( delta > 0 && delta < (snd_mixahead.GetFloat() * 0.9f) )
  5020. return;
  5021. g_LastMixTime = tNow;
  5022. g_pSoundServices->OnExtraUpdate();
  5023. // Shouldn't have to do any work here if your framerate hasn't dropped
  5024. S_Update_( snd_mixahead.GetFloat() );
  5025. }
  5026. extern void DEBUG_StartSoundMeasure(int type, int samplecount );
  5027. extern void DEBUG_StopSoundMeasure(int type, int samplecount );
  5028. void S_Update_Guts( float mixAheadTime )
  5029. {
  5030. VPROF( "S_Update_Guts" );
  5031. tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
  5032. DEBUG_StartSoundMeasure(4, 0);
  5033. // Update our perception of audio time.
  5034. // 'g_soundtime' tells how many samples have
  5035. // been played out of the dma buffer since sound system startup.
  5036. // 'g_paintedtime' indicates how many samples we've actually mixed
  5037. // and sent to the dma buffer since sound system startup.
  5038. GetSoundTime();
  5039. // if ( g_soundtime > g_paintedtime )
  5040. // {
  5041. // // if soundtime > paintedtime, then the dma buffer
  5042. // // has played out more sound than we've actually
  5043. // // mixed. We need to call S_Update_ more often.
  5044. //
  5045. // DevMsg ("S_Update_ : Underflow\n");
  5046. // paintedtime = g_soundtime;
  5047. // }
  5048. // (kdb) above code doesn't handle underflow correctly
  5049. // should actually zero out the paintbuffer to advance to the new
  5050. // time.
  5051. // mix ahead of current position
  5052. unsigned endtime = g_AudioDevice->PaintBegin( mixAheadTime, g_soundtime, g_paintedtime );
  5053. int samples = endtime - g_paintedtime;
  5054. samples = samples < 0 ? 0 : samples;
  5055. if ( samples )
  5056. {
  5057. THREAD_LOCK_SOUND();
  5058. DEBUG_StartSoundMeasure( 2, samples );
  5059. MIX_PaintChannels( endtime, s_bIsListenerUnderwater );
  5060. MXR_DebugShowMixVolumes();
  5061. MXR_UpdateAllDuckerVolumes();
  5062. DEBUG_StopSoundMeasure( 2, 0 );
  5063. }
  5064. g_AudioDevice->PaintEnd();
  5065. DEBUG_StopSoundMeasure( 4, samples );
  5066. }
  5067. #if !defined( _X360 )
  5068. #define THREADED_MIX_TIME 33
  5069. #else
  5070. #define THREADED_MIX_TIME XMA_POLL_RATE
  5071. #endif
  5072. ConVar snd_ShowThreadFrameTime( "snd_ShowThreadFrameTime", "0" );
  5073. bool g_bMixThreadExit;
  5074. ThreadHandle_t g_hMixThread;
  5075. void S_Update_Thread()
  5076. {
  5077. float frameTime = THREADED_MIX_TIME * 0.001f;
  5078. double lastFrameTime = Plat_FloatTime();
  5079. while ( !g_bMixThreadExit )
  5080. {
  5081. // mixing (for 360) needs to be updated at a steady rate
  5082. // large update times causes the mixer to demand more audio data
  5083. // the 360 decoder has finite latency and cannot fulfill spike requests
  5084. float t0 = Plat_FloatTime();
  5085. S_Update_Guts( frameTime + snd_mixahead.GetFloat() );
  5086. int updateTime = ( Plat_FloatTime() - t0 ) * 1000.0f;
  5087. // try to maintain a steadier rate by compensating for fluctuating mix times
  5088. int sleepTime = THREADED_MIX_TIME - updateTime;
  5089. if ( sleepTime > 0 )
  5090. {
  5091. ThreadSleep( sleepTime );
  5092. }
  5093. // mimic a frametime needed for sound update
  5094. double t1 = Plat_FloatTime();
  5095. frameTime = t1 - lastFrameTime;
  5096. lastFrameTime = t1;
  5097. if ( snd_ShowThreadFrameTime.GetBool() )
  5098. {
  5099. Msg( "S_Update_Thread: frameTime: %d ms\n", (int)( frameTime * 1000.0f ) );
  5100. }
  5101. }
  5102. }
  5103. void S_ShutdownMixThread()
  5104. {
  5105. if ( g_hMixThread )
  5106. {
  5107. g_bMixThreadExit = true;
  5108. ThreadJoin( g_hMixThread );
  5109. ReleaseThreadHandle( g_hMixThread );
  5110. g_hMixThread = NULL;
  5111. }
  5112. }
  5113. void S_Update_( float mixAheadTime )
  5114. {
  5115. if ( !IsConsole() || !snd_mix_async.GetBool() )
  5116. {
  5117. S_ShutdownMixThread();
  5118. S_Update_Guts( mixAheadTime );
  5119. }
  5120. else
  5121. {
  5122. if ( !g_hMixThread )
  5123. {
  5124. g_bMixThreadExit = false;
  5125. g_hMixThread = ThreadExecuteSolo( "SndMix", S_Update_Thread );
  5126. if ( IsX360() )
  5127. {
  5128. ThreadSetAffinity( g_hMixThread, XBOX_PROCESSOR_5 );
  5129. }
  5130. }
  5131. }
  5132. }
  5133. //-----------------------------------------------------------------------------
  5134. // Threaded mixing enable. Purposely hiding enable/disable details.
  5135. //-----------------------------------------------------------------------------
  5136. void S_EnableThreadedMixing( bool bEnable )
  5137. {
  5138. if ( snd_mix_async.GetBool() != bEnable )
  5139. {
  5140. snd_mix_async.SetValue( bEnable );
  5141. }
  5142. }
  5143. /*
  5144. ===============================================================================
  5145. console functions
  5146. ===============================================================================
  5147. */
  5148. extern void DSP_DEBUGSetParams(int ipreset, int iproc, float *pvalues, int cparams);
  5149. extern void DSP_DEBUGReloadPresetFile( void );
  5150. void S_DspParms( const CCommand &args )
  5151. {
  5152. if ( args.ArgC() == 1)
  5153. {
  5154. // if dsp_parms with no arguments, reload entire preset file
  5155. DSP_DEBUGReloadPresetFile();
  5156. return;
  5157. }
  5158. if ( args.ArgC() < 4 )
  5159. {
  5160. Msg( "Usage: dsp_parms PRESET# PROC# param0 param1 ...up to param15 \n" );
  5161. return;
  5162. }
  5163. int cparam = min( args.ArgC() - 4, 16);
  5164. float params[16];
  5165. Q_memset( params, 0, sizeof(float) * 16 );
  5166. // get preset & proc
  5167. int idsp, iproc;
  5168. idsp = Q_atof( args[1] );
  5169. iproc = Q_atof( args[2] );
  5170. // get params
  5171. for (int i = 0; i < cparam; i++)
  5172. {
  5173. params[i] = Q_atof( args[i+4] );
  5174. }
  5175. // set up params & switch preset
  5176. DSP_DEBUGSetParams(idsp, iproc, params, cparam);
  5177. }
  5178. static ConCommand dsp_parm("dsp_reload", S_DspParms );
  5179. void S_Play( const char *pszName, bool flush = false )
  5180. {
  5181. int inCache;
  5182. char szName[256];
  5183. CSfxTable *pSfx;
  5184. Q_strncpy( szName, pszName, sizeof( szName ) );
  5185. if ( !Q_strrchr( pszName, '.' ) )
  5186. {
  5187. Q_strncat( szName, ".wav", sizeof( szName ), COPY_ALL_CHARACTERS );
  5188. }
  5189. pSfx = S_FindName( szName, &inCache );
  5190. if ( inCache && flush )
  5191. {
  5192. pSfx->pSource->CacheUnload();
  5193. }
  5194. StartSoundParams_t params;
  5195. params.staticsound = false;
  5196. params.soundsource = g_pSoundServices->GetViewEntity();
  5197. params.entchannel = CHAN_REPLACE;
  5198. params.pSfx = pSfx;
  5199. params.origin = listener_origin;
  5200. params.fvol = 1.0f;
  5201. params.soundlevel = SNDLVL_NONE;
  5202. params.flags = 0;
  5203. params.pitch = PITCH_NORM;
  5204. S_StartSound( params );
  5205. }
  5206. static void S_Play( const CCommand &args )
  5207. {
  5208. bool bFlush = !Q_stricmp( args[0], "playflush" );
  5209. for ( int i = 1; i < args.ArgC(); ++i )
  5210. {
  5211. S_Play( args[i], bFlush );
  5212. }
  5213. }
  5214. static void S_PlayVol( const CCommand &args )
  5215. {
  5216. static int hash=543;
  5217. float vol;
  5218. char name[256];
  5219. CSfxTable *pSfx;
  5220. for ( int i = 1; i<args.ArgC(); i += 2 )
  5221. {
  5222. if ( !Q_strrchr( args[i], '.') )
  5223. {
  5224. Q_strncpy( name, args[i], sizeof( name ) );
  5225. Q_strncat( name, ".wav", sizeof( name ), COPY_ALL_CHARACTERS );
  5226. }
  5227. else
  5228. {
  5229. Q_strncpy( name, args[i], sizeof( name ) );
  5230. }
  5231. pSfx = S_PrecacheSound( name );
  5232. vol = Q_atof( args[i+1] );
  5233. StartSoundParams_t params;
  5234. params.staticsound = false;
  5235. params.soundsource = hash++;
  5236. params.entchannel = CHAN_AUTO;
  5237. params.pSfx = pSfx;
  5238. params.origin = listener_origin;
  5239. params.fvol = vol;
  5240. params.soundlevel = SNDLVL_NONE;
  5241. params.flags = 0;
  5242. params.pitch = PITCH_NORM;
  5243. S_StartDynamicSound( params );
  5244. }
  5245. }
  5246. static void S_PlayDelay( const CCommand &args )
  5247. {
  5248. if ( args.ArgC() != 3 )
  5249. {
  5250. Msg( "Usage: sndplaydelay delay_in_sec (negative to skip ahead) soundname\n" );
  5251. return;
  5252. }
  5253. char szName[256];
  5254. CSfxTable *pSfx;
  5255. float delay = Q_atof( args[ 1 ] );
  5256. Q_strncpy(szName, args[ 2 ], sizeof( szName ) );
  5257. if ( !Q_strrchr( args[ 2 ], '.' ) )
  5258. {
  5259. Q_strncat( szName, ".wav", sizeof( szName ), COPY_ALL_CHARACTERS );
  5260. }
  5261. pSfx = S_FindName( szName, NULL );
  5262. StartSoundParams_t params;
  5263. params.staticsound = false;
  5264. params.soundsource = g_pSoundServices->GetViewEntity();
  5265. params.entchannel = CHAN_REPLACE;
  5266. params.pSfx = pSfx;
  5267. params.origin = listener_origin;
  5268. params.fvol = 1.0f;
  5269. params.soundlevel = SNDLVL_NONE;
  5270. params.flags = 0;
  5271. params.pitch = PITCH_NORM;
  5272. params.delay = delay;
  5273. S_StartSound( params );
  5274. }
  5275. static ConCommand sndplaydelay( "sndplaydelay", S_PlayDelay, "Usage: sndplaydelay delay_in_sec (negative to skip ahead) soundname", FCVAR_SERVER_CAN_EXECUTE );
  5276. static bool SortByNameLessFunc( const int &lhs, const int &rhs )
  5277. {
  5278. CSfxTable *pSfx1 = s_Sounds[lhs].pSfx;
  5279. CSfxTable *pSfx2 = s_Sounds[rhs].pSfx;
  5280. return CaselessStringLessThan( pSfx1->getname(), pSfx2->getname() );
  5281. }
  5282. void S_SoundList(void)
  5283. {
  5284. CSfxTable *sfx;
  5285. CAudioSource *pSource;
  5286. int size, total;
  5287. total = 0;
  5288. for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) )
  5289. {
  5290. sfx = s_Sounds[i].pSfx;
  5291. pSource = sfx->pSource;
  5292. if ( !pSource || !pSource->IsCached() )
  5293. continue;
  5294. size = pSource->SampleSize() * pSource->SampleCount();
  5295. total += size;
  5296. if ( pSource->IsLooped() )
  5297. Msg ("L");
  5298. else
  5299. Msg (" ");
  5300. Msg("(%2db) %6i : %s\n", pSource->SampleSize(), size, sfx->getname());
  5301. }
  5302. Msg( "Total resident: %i\n", total );
  5303. }
  5304. #if defined( _X360 )
  5305. CON_COMMAND( vx_soundlist, "Dump sounds to VXConsole" )
  5306. {
  5307. CSfxTable *sfx;
  5308. CAudioSource *pSource;
  5309. int dataSize;
  5310. char *pFormatStr;
  5311. int sampleRate;
  5312. int sampleBits;
  5313. int streamed;
  5314. int looped;
  5315. int channels;
  5316. int numSamples;
  5317. int numSounds = s_Sounds.Count();
  5318. xSoundList_t* pSoundList = new xSoundList_t[numSounds];
  5319. int i = 0;
  5320. for ( int iSrcSound=s_Sounds.FirstInorder(); iSrcSound != s_Sounds.InvalidIndex(); iSrcSound = s_Sounds.NextInorder( iSrcSound ) )
  5321. {
  5322. dataSize = -1;
  5323. sampleRate = -1;
  5324. sampleBits = -1;
  5325. pFormatStr = "???";
  5326. streamed = -1;
  5327. looped = -1;
  5328. channels = -1;
  5329. numSamples = -1;
  5330. sfx = s_Sounds[iSrcSound].pSfx;
  5331. pSource = sfx->pSource;
  5332. if ( pSource && pSource->IsCached() )
  5333. {
  5334. numSamples = pSource->SampleCount();
  5335. dataSize = pSource->DataSize();
  5336. sampleRate = pSource->SampleRate();
  5337. streamed = pSource->IsStreaming();
  5338. looped = pSource->IsLooped();
  5339. channels = pSource->IsStereoWav() ? 2 : 1;
  5340. if ( pSource->Format() == WAVE_FORMAT_ADPCM )
  5341. {
  5342. pFormatStr = "ADPCM";
  5343. sampleBits = 16;
  5344. }
  5345. else if ( pSource->Format() == WAVE_FORMAT_PCM )
  5346. {
  5347. pFormatStr = "PCM";
  5348. sampleBits = (pSource->SampleSize() * 8)/channels;
  5349. }
  5350. else if ( pSource->Format() == WAVE_FORMAT_XMA )
  5351. {
  5352. pFormatStr = "XMA";
  5353. sampleBits = 16;
  5354. }
  5355. }
  5356. V_strncpy( pSoundList[i].name, sfx->getname(), sizeof( pSoundList[i].name ) );
  5357. V_strncpy( pSoundList[i].formatName, pFormatStr, sizeof( pSoundList[i].formatName ) );
  5358. pSoundList[i].rate = sampleRate;
  5359. pSoundList[i].bits = sampleBits;
  5360. pSoundList[i].channels = channels;
  5361. pSoundList[i].looped = looped;
  5362. pSoundList[i].dataSize = dataSize;
  5363. pSoundList[i].numSamples = numSamples;
  5364. pSoundList[i].streamed = streamed;
  5365. ++i;
  5366. }
  5367. XBX_rSoundList( numSounds, pSoundList );
  5368. delete [] pSoundList;
  5369. }
  5370. #endif
  5371. extern unsigned g_snd_time_debug;
  5372. extern unsigned g_snd_call_time_debug;
  5373. extern unsigned g_snd_count_debug;
  5374. extern unsigned g_snd_samplecount;
  5375. extern unsigned g_snd_frametime;
  5376. extern unsigned g_snd_frametime_total;
  5377. extern int g_snd_profile_type;
  5378. // start measuring sound perf, 100 reps
  5379. // type 1 - dsp, 2 - mix, 3 - load sound, 4 - all sound
  5380. // set type via ConVar snd_profile
  5381. void DEBUG_StartSoundMeasure(int type, int samplecount )
  5382. {
  5383. if (type != g_snd_profile_type)
  5384. return;
  5385. if (samplecount)
  5386. g_snd_samplecount += samplecount;
  5387. g_snd_call_time_debug = Plat_MSTime();
  5388. }
  5389. // show sound measurement after 25 reps - show as % of total frame
  5390. // type 1 - dsp, 2 - mix, 3 - load sound, 4 - all sound
  5391. // BUGBUG: snd_profile 4 reports a lower average because it's average cost
  5392. // PER CALL and most calls (via SoundExtraUpdate()) don't do any work and
  5393. // bring the average down. If you want an average PER FRAME instead, it's generally higher.
  5394. void DEBUG_StopSoundMeasure(int type, int samplecount )
  5395. {
  5396. if (type != g_snd_profile_type)
  5397. return;
  5398. if (samplecount)
  5399. g_snd_samplecount += samplecount;
  5400. // add total time since last frame
  5401. g_snd_frametime_total += Plat_MSTime() - g_snd_frametime;
  5402. // performance timing
  5403. g_snd_time_debug += Plat_MSTime() - g_snd_call_time_debug;
  5404. if (++g_snd_count_debug >= 100)
  5405. {
  5406. switch (g_snd_profile_type)
  5407. {
  5408. case 1:
  5409. Msg("dsp: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
  5410. Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
  5411. break;
  5412. case 2:
  5413. Msg("mix+dsp:(%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
  5414. Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
  5415. break;
  5416. case 3:
  5417. //if ( (((float)g_snd_time_debug) / 100.0) < 0.01 )
  5418. // break;
  5419. Msg("snd load: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
  5420. Msg("(%2.2f) pct of frame \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total));
  5421. break;
  5422. case 4:
  5423. Msg("sound: (%2.2f) millisec ", ((float)g_snd_time_debug) / 100.0);
  5424. Msg("(%2.2f) pct of frame (%d samples) \n", 100.0 * ((float)g_snd_time_debug) / ((float)g_snd_frametime_total), g_snd_samplecount);
  5425. break;
  5426. }
  5427. g_snd_count_debug = 0;
  5428. g_snd_time_debug = 0;
  5429. g_snd_samplecount = 0;
  5430. g_snd_frametime_total = 0;
  5431. }
  5432. g_snd_frametime = Plat_MSTime();
  5433. }
  5434. // speak a sentence from console; works by passing in "!sentencename"
  5435. // or "sentence"
  5436. extern ConVar dsp_room;
  5437. static void S_Say( const CCommand &args )
  5438. {
  5439. CSfxTable *pSfx;
  5440. if ( !g_AudioDevice->IsActive() )
  5441. return;
  5442. char sound[256];
  5443. Q_strncpy( sound, args[1], sizeof( sound ) );
  5444. // DEBUG - test performance of dsp code
  5445. if ( !Q_stricmp( sound, "dsp" ) )
  5446. {
  5447. unsigned time;
  5448. int i;
  5449. int count = 10000;
  5450. int idsp;
  5451. for (i = 0; i < PAINTBUFFER_SIZE; i++)
  5452. {
  5453. g_paintbuffer[i].left = RandomInt(0,2999);
  5454. g_paintbuffer[i].right = RandomInt(0,2999);
  5455. }
  5456. Msg ("Start profiling 10,000 calls to DSP\n");
  5457. idsp = dsp_room.GetInt();
  5458. // get system time
  5459. time = Plat_MSTime();
  5460. for (i = 0; i < count; i++)
  5461. {
  5462. // SX_RoomFX(PAINTBUFFER_SIZE, TRUE, TRUE);
  5463. DSP_Process(idsp, g_paintbuffer, NULL, NULL, PAINTBUFFER_SIZE);
  5464. }
  5465. // display system time delta
  5466. Msg("%d milliseconds \n", Plat_MSTime() - time);
  5467. return;
  5468. }
  5469. if ( !Q_stricmp(sound, "paint") )
  5470. {
  5471. unsigned time;
  5472. int count = 10000;
  5473. static int hash=543;
  5474. int psav = g_paintedtime;
  5475. Msg ("Start profiling MIX_PaintChannels\n");
  5476. pSfx = S_PrecacheSound("ambience/labdrone1.wav");
  5477. StartSoundParams_t params;
  5478. params.staticsound = false;
  5479. params.soundsource = hash++;
  5480. params.entchannel = CHAN_AUTO;
  5481. params.pSfx = pSfx;
  5482. params.origin = listener_origin;
  5483. params.fvol = 1.0f;
  5484. params.soundlevel = SNDLVL_NONE;
  5485. params.flags = 0;
  5486. params.pitch = PITCH_NORM;
  5487. S_StartDynamicSound( params );
  5488. // get system time
  5489. time = Plat_MSTime();
  5490. // paint a boatload of sound
  5491. MIX_PaintChannels( g_paintedtime + 512*count, s_bIsListenerUnderwater );
  5492. // display system time delta
  5493. Msg("%d milliseconds \n", Plat_MSTime() - time);
  5494. g_paintedtime = psav;
  5495. return;
  5496. }
  5497. // DEBUG
  5498. if ( !TestSoundChar( sound, CHAR_SENTENCE ) )
  5499. {
  5500. // build a fake sentence name, then play the sentence text
  5501. Q_strncpy(sound, "xxtestxx ", sizeof( sound ) );
  5502. Q_strncat(sound, args[1], sizeof( sound ), COPY_ALL_CHARACTERS );
  5503. int addIndex = g_Sentences.AddToTail();
  5504. sentence_t *pSentence = &g_Sentences[addIndex];
  5505. pSentence->pName = sound;
  5506. pSentence->length = 0;
  5507. // insert null terminator after sentence name
  5508. sound[8] = 0;
  5509. pSfx = S_PrecacheSound ("!xxtestxx");
  5510. if (!pSfx)
  5511. {
  5512. Msg ("S_Say: can't cache %s\n", sound);
  5513. return;
  5514. }
  5515. StartSoundParams_t params;
  5516. params.staticsound = false;
  5517. params.soundsource = g_pSoundServices->GetViewEntity();
  5518. params.entchannel = CHAN_REPLACE;
  5519. params.pSfx = pSfx;
  5520. params.origin = vec3_origin;
  5521. params.fvol = 1.0f;
  5522. params.soundlevel = SNDLVL_NONE;
  5523. params.flags = 0;
  5524. params.pitch = PITCH_NORM;
  5525. S_StartDynamicSound ( params );
  5526. // remove last
  5527. g_Sentences.Remove( g_Sentences.Size() - 1 );
  5528. }
  5529. else
  5530. {
  5531. pSfx = S_FindName(sound, NULL);
  5532. if (!pSfx)
  5533. {
  5534. Msg ("S_Say: can't find sentence name %s\n", sound);
  5535. return;
  5536. }
  5537. StartSoundParams_t params;
  5538. params.staticsound = false;
  5539. params.soundsource = g_pSoundServices->GetViewEntity();
  5540. params.entchannel = CHAN_REPLACE;
  5541. params.pSfx = pSfx;
  5542. params.origin = vec3_origin;
  5543. params.fvol = 1.0f;
  5544. params.soundlevel = SNDLVL_NONE;
  5545. params.flags = 0;
  5546. params.pitch = PITCH_NORM;
  5547. S_StartDynamicSound( params );
  5548. }
  5549. }
  5550. //------------------------------------------------------------------------------
  5551. //
  5552. // Sound Mixers
  5553. //
  5554. // Sound mixers are referenced by name from Soundscapes, and are used to provide
  5555. // custom volume control over various sound categories, called 'mix groups'
  5556. //
  5557. // see scripts/soundmixers.txt for data format
  5558. //------------------------------------------------------------------------------
  5559. #define CMXRGROUPMAX 64 // up to n mixgroups
  5560. #define CMXRGROUPRULESMAX (CMXRGROUPMAX + 16) // max number of group rules
  5561. #define CMXRSOUNDMIXERSMAX 32 // up to n sound mixers per project
  5562. // mix groups - these equivalent to submixes on an audio mixer
  5563. // list of rules for determining sound membership in mix groups.
  5564. // All conditions which are not null are ANDed together
  5565. #define CMXRCLASSMAX 16
  5566. #define CMXRNAMEMAX 32
  5567. struct classlistelem_t
  5568. {
  5569. char szclassname[CMXRNAMEMAX]; // name of entities' class, such as CAI_BaseNPC or CHL2_Player
  5570. };
  5571. struct grouprule_t
  5572. {
  5573. char szmixgroup[CMXRNAMEMAX]; // mix group name
  5574. int mixgroupid; // mix group unique id
  5575. char szdir[CMXRNAMEMAX]; // substring to search for in ch->sfx
  5576. int classId; // index of classname
  5577. int chantype; // channel type (CHAN_WEAPON, etc)
  5578. int soundlevel_min; // min soundlevel
  5579. int soundlevel_max; // max soundlevel
  5580. int priority; // 0..100 higher priority sound groups duck all lower pri groups if enabled
  5581. int is_ducked; // if 1, sound group is ducked by all higher priority 'causes_duck" sounds
  5582. int causes_ducking; // if 1, sound group ducks other 'is_ducked' sounds of lower priority
  5583. float duck_target_pct; // if sound group is ducked, target percent of original volume
  5584. float total_vol; // total volume of all sounds in this group, if group can cause ducking
  5585. float ducker_threshold; // ducking is caused by this group if total_vol > ducker_threshold
  5586. // and causes_ducking is enabled.
  5587. float duck_target_vol; // target volume while ducking
  5588. float duck_ramp_val; // current value of ramp - moves towards duck_target_vol
  5589. };
  5590. // sound mixer
  5591. struct soundmixer_t
  5592. {
  5593. char szsoundmixer[CMXRNAMEMAX]; // name of this soundmixer
  5594. float mapMixgroupidToValue[CMXRGROUPMAX]; // sparse array of mix group values for this soundmixer
  5595. };
  5596. int g_mapMixgroupidToGrouprulesid[CMXRGROUPMAX]; // map mixgroupid (one per unique group name)
  5597. // back to 1st entry of this name in g_grouprules
  5598. // sound mixer globals
  5599. classlistelem_t g_groupclasslist[CMXRCLASSMAX];
  5600. soundmixer_t g_soundmixers[CMXRSOUNDMIXERSMAX]; // all sound mixers
  5601. grouprule_t g_grouprules[CMXRGROUPRULESMAX]; // all rules for determining mix group membership
  5602. // set current soundmixer index g_isoundmixer, search for match in soundmixers
  5603. // Only change current soundmixer if new name is different from current name.
  5604. int g_isoundmixer = -1; // index of current sound mixer
  5605. char g_szsoundmixer_cur[64]; // current soundmixer name
  5606. ConVar snd_soundmixer("snd_soundmixer", "Default_Mix"); // current soundmixer name
  5607. void MXR_SetCurrentSoundMixer( const char *szsoundmixer )
  5608. {
  5609. // if soundmixer name is not different from current name, return
  5610. if ( !Q_stricmp(szsoundmixer, g_szsoundmixer_cur) )
  5611. {
  5612. return;
  5613. }
  5614. for (int i = 0; i < g_csoundmixers; i++)
  5615. {
  5616. if ( !Q_stricmp(g_soundmixers[i].szsoundmixer, szsoundmixer) )
  5617. {
  5618. g_isoundmixer = i;
  5619. // save new current sound mixer name
  5620. V_strcpy_safe(g_szsoundmixer_cur, szsoundmixer);
  5621. return;
  5622. }
  5623. }
  5624. }
  5625. ConVar snd_showclassname("snd_showclassname", "0"); // if 1, show classname of ent making sound
  5626. // if 2, show all mixgroup matches
  5627. // if 3, show all mixgroup matches with current soundmixer for ent
  5628. // get the client class name if an entity was specified
  5629. const char *GetClientClassname( SoundSource soundsource )
  5630. {
  5631. IClientEntity *pClientEntity = NULL;
  5632. if ( entitylist )
  5633. {
  5634. pClientEntity = entitylist->GetClientEntity( soundsource );
  5635. if ( pClientEntity )
  5636. {
  5637. ClientClass *pClientClass = pClientEntity->GetClientClass();
  5638. // check npc sounds
  5639. if ( pClientClass )
  5640. {
  5641. return pClientClass->GetName();
  5642. }
  5643. }
  5644. }
  5645. return NULL;
  5646. }
  5647. // builds a cached list of rules that match the directory name on the sound
  5648. int MXR_GetMixGroupListFromDirName( const char *pDirname, byte *pList, int listMax )
  5649. {
  5650. // if we call this before the groups are parsed we'll get bad data
  5651. Assert(g_cgrouprules>0);
  5652. int count = 0;
  5653. for ( int i = 0; i < listMax; i++ )
  5654. {
  5655. pList[i] = 255;
  5656. }
  5657. for ( int i = 0; i < g_cgrouprules; i++ )
  5658. {
  5659. grouprule_t *prule = &g_grouprules[i];
  5660. if ( prule->szdir[ 0 ] && Q_stristr( pDirname, prule->szdir ) )
  5661. {
  5662. pList[count] = i;
  5663. count++;
  5664. if ( count >= listMax )
  5665. return count;
  5666. }
  5667. }
  5668. return count;
  5669. }
  5670. // determine which mixgroups sound is in, and save those mixgroupids in sound.
  5671. // use current soundmixer indicated with g_isoundmixer, and contents of g_rgpgrouprules.
  5672. // Algorithm:
  5673. // 1. all conditions in a row are AND conditions,
  5674. // 2. all rows sharing the same groupname are OR conditions.
  5675. // so - if a sound matches all conditions of a row, it is given that row's mixgroup id
  5676. // if a sound doesn't match all conditions of a row, the next row is checked.
  5677. // returns 0, default mixgroup if no match
  5678. void MXR_GetMixGroupFromSoundsource( channel_t *pchan, SoundSource soundsource, soundlevel_t soundlevel)
  5679. {
  5680. int i;
  5681. grouprule_t *prule;
  5682. bool fmatch;
  5683. bool classMatch[CMXRCLASSMAX];
  5684. // init all mixgroups for channel
  5685. for ( i = 0; i < 8; i++ )
  5686. {
  5687. pchan->mixgroups[i] = -1;
  5688. }
  5689. char sndname[MAX_OSPATH];
  5690. Q_strncpy( sndname, pchan->sfx->getname(), sizeof( sndname ) );
  5691. // Use forward slashes here
  5692. Q_FixSlashes( sndname, '/' );
  5693. const char *pszclassname = GetClientClassname(soundsource);
  5694. for ( i = 0; i < g_cgroupclass; i++ )
  5695. {
  5696. classMatch[i] = false;
  5697. if ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[i].szclassname ) )
  5698. {
  5699. classMatch[i] = true;
  5700. }
  5701. }
  5702. if ( snd_showclassname.GetInt() == 1)
  5703. {
  5704. // utility: show classname of ent making sound
  5705. if (pszclassname)
  5706. {
  5707. DevMsg("(%s:%s) \n", pszclassname, sndname);
  5708. }
  5709. }
  5710. // check all group rules for a match, save
  5711. // up to 8 matches in channel mixgroup.
  5712. int cmixgroups = 0;
  5713. if (!pchan->sfx->m_bMixGroupsCached)
  5714. {
  5715. pchan->sfx->OnNameChanged( pchan->sfx->getname() );
  5716. }
  5717. // since this is a sorted list (in group rule order) we only need to test against the next matching rule
  5718. // this avoids a search inside the loop
  5719. int currentDirRuleIndex = 0;
  5720. int currentDirRule = pchan->sfx->m_mixGroupList[0];
  5721. for (i = 0; i < g_cgrouprules; i++)
  5722. {
  5723. prule = &g_grouprules[i];
  5724. fmatch = true;
  5725. // check directory or name substring
  5726. #if _DEBUG
  5727. // check dir table is correct in CSfxTable cache
  5728. if ( prule->szdir[ 0 ] && Q_stristr( sndname, prule->szdir ) )
  5729. {
  5730. Assert(currentDirRule == i);
  5731. }
  5732. else
  5733. {
  5734. Assert(currentDirRule != i);
  5735. }
  5736. if ( prule->classId >= 0 )
  5737. {
  5738. // rule has a valid class id and table is correct
  5739. Assert(prule->classId < g_cgroupclass);
  5740. if ( pszclassname && Q_stristr(pszclassname, g_groupclasslist[prule->classId].szclassname) )
  5741. {
  5742. Assert(classMatch[prule->classId] == true);
  5743. }
  5744. else
  5745. {
  5746. Assert(classMatch[prule->classId] == false);
  5747. }
  5748. }
  5749. #endif
  5750. // this is the next matching dir for this sound, no need to search
  5751. // becuse the list is sorted and we visit all elements
  5752. if ( currentDirRule == i )
  5753. {
  5754. Assert(prule->szdir[0]);
  5755. currentDirRuleIndex++;
  5756. currentDirRule = 255;
  5757. if ( currentDirRuleIndex < pchan->sfx->m_mixGroupCount )
  5758. {
  5759. currentDirRule = pchan->sfx->m_mixGroupList[currentDirRuleIndex];
  5760. }
  5761. }
  5762. else if ( prule->szdir[ 0 ] )
  5763. {
  5764. fmatch = false; // substring doesn't match, keep looking
  5765. }
  5766. // check class name
  5767. if ( fmatch && prule->classId >= 0 )
  5768. {
  5769. fmatch = classMatch[prule->classId];
  5770. }
  5771. // check channel type
  5772. if ( fmatch && prule->chantype >= 0)
  5773. {
  5774. if ( pchan->entchannel != prule->chantype )
  5775. fmatch = false; // channel type doesn't match, keep looking
  5776. }
  5777. // check sndlvlmin/max
  5778. if ( fmatch && prule->soundlevel_min >= 0)
  5779. {
  5780. if ( soundlevel < prule->soundlevel_min )
  5781. fmatch = false; // soundlevel is less than min, keep looking
  5782. }
  5783. if ( fmatch && prule->soundlevel_max >= 0)
  5784. {
  5785. if ( soundlevel > prule->soundlevel_max )
  5786. fmatch = false; // soundlevel is greater than max, keep looking
  5787. }
  5788. if ( fmatch )
  5789. {
  5790. pchan->mixgroups[cmixgroups] = prule->mixgroupid;
  5791. cmixgroups++;
  5792. if (cmixgroups >= 8)
  5793. return; // too many matches, stop looking
  5794. }
  5795. if (fmatch && snd_showclassname.GetInt() >= 2)
  5796. {
  5797. // show all mixgroups for this sound
  5798. if (cmixgroups == 1)
  5799. {
  5800. DevMsg("\n%s:%s: ", g_szsoundmixer_cur, sndname);
  5801. }
  5802. if (prule->szmixgroup[0])
  5803. {
  5804. // int rgmixgroupid[8];
  5805. // for (int i = 0; i < 8; i++)
  5806. // rgmixgroupid[i] = -1;
  5807. // rgmixgroupid[0] = prule->mixgroupid;
  5808. // float vol = MXR_GetVolFromMixGroup( rgmixgroupid );
  5809. // DevMsg("%s(%1.2f) ", prule->szmixgroup, vol);
  5810. DevMsg("%s ", prule->szmixgroup);
  5811. }
  5812. }
  5813. }
  5814. }
  5815. struct debug_showvols_t
  5816. {
  5817. char *psz; // group name
  5818. int mixgroupid; // groupid
  5819. float vol; // group volume
  5820. float totalvol; // total volume of all sounds playing in this group
  5821. };
  5822. // display routine for MXR_DebugShowMixVolumes
  5823. #define MXR_DEBUG_INCY (1.0/40.0) // vertical text spacing
  5824. #define MXR_DEBUG_GREENSTART 0.3 // start position on screen of bar
  5825. #define MXR_DEBUG_MAXVOL 1.0 // max volume scale
  5826. #define MXR_DEBUG_REDLIMIT 1.0 // volume limit into yellow
  5827. #define MXR_DEBUG_YELLOWLIMIT 0.7 // volume limit into red
  5828. #define MXR_DEBUG_VOLSCALE 48 // length of graph in characters
  5829. #define MXR_DEBUG_CHAR '-' // bar character
  5830. extern ConVar dsp_volume;
  5831. int g_debug_mxr_displaycount = 0;
  5832. void MXR_DebugGraphMixVolumes( debug_showvols_t *groupvols, int cgroups)
  5833. {
  5834. float flXpos, flYpos, flXposBar, duration;
  5835. int r,g,b,a;
  5836. int rb, gb, bb, ab;
  5837. flXpos = 0;
  5838. flYpos = 0;
  5839. char text[128];
  5840. char bartext[MXR_DEBUG_VOLSCALE*3];
  5841. duration = 0.01;
  5842. g_debug_mxr_displaycount++;
  5843. if (!(g_debug_mxr_displaycount % 10))
  5844. return; // only display every 10 frames
  5845. r = 96; g = 86; b = 226; a = 255; ab = 255;
  5846. // show volume, dsp_volume
  5847. Q_snprintf( text, 128, "Game Volume: %1.2f", volume.GetFloat());
  5848. CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
  5849. flYpos += MXR_DEBUG_INCY;
  5850. Q_snprintf( text, 128, "DSP Volume: %1.2f", dsp_volume.GetFloat());
  5851. CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
  5852. flYpos += MXR_DEBUG_INCY;
  5853. for (int i = 0; i < cgroups; i++)
  5854. {
  5855. // r += 64; g += 64; b += 16;
  5856. r = r % 255; g = g % 255; b = b % 255;
  5857. Q_snprintf( text, 128, "%s: %1.2f (%1.2f)", groupvols[i].psz,
  5858. groupvols[i].vol * g_DuckScale, groupvols[i].totalvol * g_DuckScale);
  5859. CDebugOverlay::AddScreenTextOverlay(flXpos, flYpos, duration, r, g, b,a, text);
  5860. // draw volume bar graph
  5861. float vol = (groupvols[i].totalvol * g_DuckScale) / MXR_DEBUG_MAXVOL;
  5862. // draw first 70% green
  5863. float vol1 = 0.0;
  5864. float vol2 = 0.0;
  5865. float vol3 = 0.0;
  5866. int cbars;
  5867. vol1 = clamp(vol, 0.0f, 0.7f);
  5868. vol2 = clamp(vol, 0.0f, 0.95f);
  5869. vol3 = vol;
  5870. flXposBar = flXpos + MXR_DEBUG_GREENSTART;
  5871. if (vol1 > 0.0)
  5872. {
  5873. //flXposBar = flXpos + MXR_DEBUG_GREENSTART;
  5874. rb = 0; gb= 255; bb = 0; // green bar
  5875. Q_memset(bartext, 0, sizeof(bartext));
  5876. cbars = (int)((float)vol1 * (float)MXR_DEBUG_VOLSCALE);
  5877. cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
  5878. Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
  5879. CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
  5880. }
  5881. // yellow bar
  5882. if (vol2 > MXR_DEBUG_YELLOWLIMIT)
  5883. {
  5884. rb = 255; gb = 255; bb = 0;
  5885. Q_memset(bartext, 0, sizeof(bartext));
  5886. cbars = (int)((float)vol2 * (float)MXR_DEBUG_VOLSCALE);
  5887. cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
  5888. Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
  5889. CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
  5890. }
  5891. // red bar
  5892. if (vol3 > MXR_DEBUG_REDLIMIT)
  5893. {
  5894. //flXposBar = flXpos + MXR_DEBUG_REDSTART;
  5895. rb = 255; gb = 0; bb = 0;
  5896. Q_memset(bartext, 0, sizeof(bartext));
  5897. cbars = (int)((float)vol3 * (float)MXR_DEBUG_VOLSCALE);
  5898. cbars = clamp(cbars, 0, MXR_DEBUG_VOLSCALE*3-1);
  5899. Q_memset(bartext, MXR_DEBUG_CHAR, cbars);
  5900. CDebugOverlay::AddScreenTextOverlay(flXposBar, flYpos, duration, rb, gb, bb,ab, bartext);
  5901. }
  5902. flYpos += MXR_DEBUG_INCY;
  5903. }
  5904. }
  5905. ConVar snd_disable_mixer_duck("snd_disable_mixer_duck", "0"); // if 1, soundmixer ducking is disabled
  5906. // given mix group id, return current duck volume
  5907. float MXR_GetDuckVolume( int mixgroupid )
  5908. {
  5909. if ( snd_disable_mixer_duck.GetInt() )
  5910. return 1.0;
  5911. Assert ( mixgroupid < g_cgrouprules );
  5912. int grouprulesid = g_mapMixgroupidToGrouprulesid[mixgroupid];
  5913. // if this mixgroup is not ducked, return 1.0
  5914. if ( !g_grouprules[grouprulesid].is_ducked )
  5915. return 1.0;
  5916. // return current duck value for this group, scaled by current fade in/out ramp
  5917. return g_grouprules[grouprulesid].duck_ramp_val;
  5918. }
  5919. #define SND_DUCKER_UPDATETIME 0.1 // seconds to wait between ducker updates
  5920. double g_mxr_ducktime = 0.0; // time of last update to ducker
  5921. // Get total volume currently playing in all groups,
  5922. // process duck volumes for all groups
  5923. // Call once per frame - updates occur at 10hz
  5924. void MXR_UpdateAllDuckerVolumes( void )
  5925. {
  5926. if ( snd_disable_mixer_duck.GetInt() )
  5927. return;
  5928. // check timer since last update, only update at 10hz
  5929. int i;
  5930. double dtime = g_pSoundServices->GetHostTime();
  5931. // don't update until timer expires
  5932. if (fabs(dtime - g_mxr_ducktime) < SND_DUCKER_UPDATETIME)
  5933. return;
  5934. g_mxr_ducktime = dtime;
  5935. // clear out all total volume values for groups
  5936. for ( i = 0; i < g_cgrouprules; i++)
  5937. g_grouprules[i].total_vol = 0.0;
  5938. // for every channel in a mix group which can cause ducking:
  5939. // get total volume, store total in grouprule:
  5940. CChannelList list;
  5941. int ch_idx;
  5942. channel_t *pchan;
  5943. bool b_found_ducked_channel = false;
  5944. g_ActiveChannels.GetActiveChannels( list );
  5945. for ( i = 0; i < list.Count(); i++ )
  5946. {
  5947. ch_idx = list.GetChannelIndex(i);
  5948. pchan = &channels[ch_idx];
  5949. if (pchan->last_vol > 0.0)
  5950. {
  5951. // account for all mix groups this channel belongs to...
  5952. for (int j = 0; j < 8; j++)
  5953. {
  5954. int imixgroup = pchan->mixgroups[j];
  5955. if (imixgroup < 0)
  5956. continue;
  5957. int grouprulesid = g_mapMixgroupidToGrouprulesid[imixgroup];
  5958. if (g_grouprules[grouprulesid].causes_ducking)
  5959. g_grouprules[grouprulesid].total_vol += pchan->last_vol;
  5960. if (g_grouprules[grouprulesid].is_ducked)
  5961. b_found_ducked_channel = true;
  5962. }
  5963. }
  5964. }
  5965. // if no channels playing which may be ducked, do nothing
  5966. if ( !b_found_ducked_channel )
  5967. return;
  5968. // for all groups that can be ducked:
  5969. // see if a higher priority sound group has a volume > threshold,
  5970. // if so, then duck this group by setting duck_target_vol to duck_target_pct.
  5971. // if no sound group is causing ducking in this group, reset duck_target_vol to 1.0
  5972. for (i = 0; i < g_cgrouprules; i++)
  5973. {
  5974. if (g_grouprules[i].is_ducked)
  5975. {
  5976. int priority = g_grouprules[i].priority;
  5977. float duck_volume = 1.0; // clear to 1.0 if no channel causing ducking
  5978. // make sure we interact appropriately with global voice ducking...
  5979. // if global voice ducking is active, skip sound group ducking and just set duck_volume target to 1.0
  5980. if ( g_DuckScale >= 1.0 )
  5981. {
  5982. // check all sound groups for higher priority duck trigger
  5983. for (int j = 0; j < g_cgrouprules; j++)
  5984. {
  5985. if (g_grouprules[j].priority > priority &&
  5986. g_grouprules[j].causes_ducking &&
  5987. g_grouprules[j].total_vol > g_grouprules[j].ducker_threshold)
  5988. {
  5989. // a higher priority group is causing this group to be ducked
  5990. // set duck volume target to the ducked group's duck target percent
  5991. // and break
  5992. duck_volume = g_grouprules[i].duck_target_pct;
  5993. // UNDONE: to prevent edge condition caused by crossing threshold, may need to have secondary
  5994. // UNDONE: timer which allows ducking at 0.2 hz
  5995. break;
  5996. }
  5997. }
  5998. }
  5999. g_grouprules[i].duck_target_vol = duck_volume;
  6000. }
  6001. }
  6002. // update all ducker ramps if current duck value is not target
  6003. // if ramp is greater than duck_volume, approach at 'attack rate'
  6004. // if ramp is less than duck_volume, approach at 'decay rate'
  6005. for (i = 0; i < g_cgrouprules; i++)
  6006. {
  6007. float target = g_grouprules[i].duck_target_vol;
  6008. float current = g_grouprules[i].duck_ramp_val;
  6009. if (g_grouprules[i].is_ducked && (current != target))
  6010. {
  6011. float ramptime = target < current ? snd_duckerattacktime.GetFloat() : snd_duckerreleasetime.GetFloat();
  6012. // delta is volume change per update (we can do this
  6013. // since we run at an approximate fixed update rate of 10hz)
  6014. float delta = (1.0 - g_grouprules[i].duck_target_pct);
  6015. delta *= ( SND_DUCKER_UPDATETIME / ramptime );
  6016. if (current > target)
  6017. delta = -delta;
  6018. // update ramps
  6019. current += delta;
  6020. if (current < target && delta < 0)
  6021. current = target;
  6022. if (current > target && delta > 0)
  6023. current = target;
  6024. g_grouprules[i].duck_ramp_val = current;
  6025. }
  6026. }
  6027. }
  6028. ConVar snd_showmixer("snd_showmixer", "0"); // set to 1 to show mixer every frame
  6029. // show the current soundmixer output
  6030. void MXR_DebugShowMixVolumes( void )
  6031. {
  6032. if (snd_showmixer.GetInt() == 0)
  6033. return;
  6034. // for the current soundmixer:
  6035. // make a totalvolume bucket for each mixgroup type in the soundmixer.
  6036. // for every active channel, add its spatialized volume to
  6037. // totalvolume bucket for that channel's selected mixgroup
  6038. // display all mixgroup/volume/totalvolume values as horizontal bars
  6039. debug_showvols_t groupvols[CMXRGROUPMAX];
  6040. int i;
  6041. int cgroups = 0;
  6042. if (g_isoundmixer < 0)
  6043. {
  6044. DevMsg("No sound mixer selected!");
  6045. return;
  6046. }
  6047. soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
  6048. // for every entry in mapMixgroupidToValue which is not -1,
  6049. // set up groupvols
  6050. for (i = 0; i < CMXRGROUPMAX; i++)
  6051. {
  6052. if (pmixer->mapMixgroupidToValue[i] >= 0)
  6053. {
  6054. groupvols[cgroups].mixgroupid = i;
  6055. groupvols[cgroups].psz = MXR_GetGroupnameFromId( i );
  6056. groupvols[cgroups].totalvol = 0.0;
  6057. groupvols[cgroups].vol = pmixer->mapMixgroupidToValue[i];
  6058. cgroups++;
  6059. }
  6060. }
  6061. // for every active channel, get its volume and
  6062. // the selected mixgroupid, add to groupvols totalvol
  6063. CChannelList list;
  6064. int ch_idx;
  6065. channel_t *pchan;
  6066. g_ActiveChannels.GetActiveChannels( list );
  6067. for ( i = 0; i < list.Count(); i++ )
  6068. {
  6069. ch_idx = list.GetChannelIndex(i);
  6070. pchan = &channels[ch_idx];
  6071. if (pchan->last_vol > 0.0)
  6072. {
  6073. // find entry in groupvols
  6074. for (int j = 0; j < CMXRGROUPMAX; j++)
  6075. {
  6076. if (pchan->last_mixgroupid == groupvols[j].mixgroupid)
  6077. {
  6078. groupvols[j].totalvol += pchan->last_vol;
  6079. break;
  6080. }
  6081. }
  6082. }
  6083. }
  6084. // groupvols is now fully initialized - just display it
  6085. MXR_DebugGraphMixVolumes( groupvols, cgroups);
  6086. }
  6087. #ifdef _DEBUG
  6088. // set the named mixgroup volume to vol for the current soundmixer
  6089. static void MXR_DebugSetMixGroupVolume( const CCommand &args )
  6090. {
  6091. if ( args.ArgC() != 3 )
  6092. {
  6093. DevMsg("Parameters: mix group name, volume");
  6094. return;
  6095. }
  6096. const char *szgroupname = args[1];
  6097. float vol = atof( args[2] );
  6098. int imixgroup = MXR_GetMixgroupFromName( szgroupname );
  6099. if ( g_isoundmixer < 0 )
  6100. return;
  6101. soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
  6102. pmixer->mapMixgroupidToValue[imixgroup] = vol;
  6103. }
  6104. #endif //_DEBUG
  6105. // given array of groupids (ie: the sound is in these groups),
  6106. // return a mix volume.
  6107. // return first mixgroup id in the provided array
  6108. // which maps to a non -1 volume value for this
  6109. // sound mixer
  6110. float MXR_GetVolFromMixGroup( int rgmixgroupid[8], int *plast_mixgroupid )
  6111. {
  6112. // if no soundmixer currently set, return 1.0 volume
  6113. if (g_isoundmixer < 0)
  6114. {
  6115. *plast_mixgroupid = 0;
  6116. return 1.0;
  6117. }
  6118. float duckgain = 1.0;
  6119. if (g_csoundmixers)
  6120. {
  6121. soundmixer_t *pmixer = &g_soundmixers[g_isoundmixer];
  6122. if (pmixer)
  6123. {
  6124. // search mixgroupid array, return first match (non -1)
  6125. for (int i = 0; i < 8; i++)
  6126. {
  6127. int imixgroup = rgmixgroupid[i];
  6128. if (imixgroup < 0)
  6129. continue;
  6130. // save lowest duck gain value for any of the mix groups this sound is in
  6131. float duckgain_new = MXR_GetDuckVolume( imixgroup );
  6132. if ( duckgain_new < duckgain)
  6133. duckgain = duckgain_new;
  6134. Assert(imixgroup < CMXRGROUPMAX);
  6135. // return first mixgroup id in the passed in array
  6136. // that maps to a non -1 volume value for this
  6137. // sound mixer
  6138. if ( pmixer->mapMixgroupidToValue[imixgroup] >= 0)
  6139. {
  6140. *plast_mixgroupid = imixgroup;
  6141. // get gain due to mixer settings
  6142. float gain = pmixer->mapMixgroupidToValue[imixgroup];
  6143. // modify gain with ducker settings for this group
  6144. return gain * duckgain;
  6145. }
  6146. }
  6147. }
  6148. }
  6149. *plast_mixgroupid = 0;
  6150. return duckgain;
  6151. }
  6152. // get id of mixgroup name
  6153. int MXR_GetMixgroupFromName( const char *pszgroupname )
  6154. {
  6155. // scan group rules for mapping from name to id
  6156. if ( !pszgroupname )
  6157. return -1;
  6158. if ( Q_strlen(pszgroupname) == 0 )
  6159. return -1;
  6160. for (int i = 0; i < g_cgrouprules; i++)
  6161. {
  6162. if ( !Q_stricmp(g_grouprules[i].szmixgroup, pszgroupname ) )
  6163. return g_grouprules[i].mixgroupid;
  6164. }
  6165. return -1;
  6166. }
  6167. // get mixgroup name from id
  6168. char *MXR_GetGroupnameFromId( int mixgroupid)
  6169. {
  6170. // scan group rules for mapping from name to id
  6171. if (mixgroupid < 0)
  6172. return NULL;
  6173. for (int i = 0; i < g_cgrouprules; i++)
  6174. {
  6175. if ( g_grouprules[i].mixgroupid == mixgroupid)
  6176. return g_grouprules[i].szmixgroup;
  6177. }
  6178. return NULL;
  6179. }
  6180. // assign a unique mixgroup id to each unique named mix group
  6181. // within grouprules. Note: all mixgroupids in grouprules must be -1
  6182. // when this routine starts.
  6183. void MXR_AssignGroupIds( void )
  6184. {
  6185. int cmixgroupid = 0;
  6186. for (int i = 0; i < g_cgrouprules; i++)
  6187. {
  6188. int mixgroupid = MXR_GetMixgroupFromName( g_grouprules[i].szmixgroup );
  6189. if (mixgroupid == -1)
  6190. {
  6191. // groupname is not yet assigned, provide a unique mixgroupid.
  6192. g_grouprules[i].mixgroupid = cmixgroupid;
  6193. // save reverse mapping, from mixgroupid to the first grouprules entry for this name
  6194. g_mapMixgroupidToGrouprulesid[cmixgroupid] = i;
  6195. cmixgroupid++;
  6196. }
  6197. }
  6198. }
  6199. int MXR_AddClassname( const char *pName )
  6200. {
  6201. char szclassname[CMXRNAMEMAX];
  6202. Q_strncpy( szclassname, pName, CMXRNAMEMAX );
  6203. for ( int i = 0; i < g_cgroupclass; i++ )
  6204. {
  6205. if ( !Q_stricmp( szclassname, g_groupclasslist[i].szclassname ) )
  6206. return i;
  6207. }
  6208. if ( g_cgroupclass >= CMXRCLASSMAX )
  6209. {
  6210. Assert(g_cgroupclass < CMXRCLASSMAX);
  6211. return -1;
  6212. }
  6213. Q_memcpy(g_groupclasslist[g_cgroupclass].szclassname, pName, min((size_t)CMXRNAMEMAX-1, strlen(pName)));
  6214. g_cgroupclass++;
  6215. return g_cgroupclass-1;
  6216. }
  6217. #define CHAR_LEFT_PAREN '{'
  6218. #define CHAR_RIGHT_PAREN '}'
  6219. // load group rules and sound mixers from file
  6220. bool MXR_LoadAllSoundMixers( void )
  6221. {
  6222. // init soundmixer globals
  6223. g_isoundmixer = -1;
  6224. g_szsoundmixer_cur[0] = 0;
  6225. g_csoundmixers = 0; // total number of soundmixers found
  6226. g_cgrouprules = 0; // total number of group rules found
  6227. Q_memset(g_soundmixers, 0, sizeof(g_soundmixers));
  6228. Q_memset(g_grouprules, 0, sizeof(g_grouprules));
  6229. // load file
  6230. // build rules
  6231. // build array of sound mixers
  6232. char szFile[MAX_OSPATH];
  6233. const char *pstart;
  6234. bool bResult = false;
  6235. char *pbuffer;
  6236. Q_snprintf( szFile, sizeof( szFile ), "scripts/soundmixers.txt" );
  6237. pbuffer = (char *)COM_LoadFile( szFile, 5, NULL ); // Use malloc - free at end of this routine
  6238. if ( !pbuffer )
  6239. {
  6240. Error( "MXR_LoadAllSoundMixers: unable to open '%s'\n", szFile );
  6241. return bResult;
  6242. }
  6243. pstart = pbuffer;
  6244. // first pass: load g_grouprules[]
  6245. // starting at top of file,
  6246. // scan for first '{', skipping all comment lines
  6247. // get strings for: groupname, directory, classname, chan, sndlvl_min, sndlvl_max
  6248. // convert chan to CHAN_ lookup
  6249. // convert sndlvl_min, sndl_max to ints
  6250. // store all in g_grouprules, update g_cgrouprules;
  6251. // get next line
  6252. // when hit '}' we're done with grouprules
  6253. // check for first CHAR_LEFT_PAREN
  6254. while (1)
  6255. {
  6256. pstart = COM_Parse( pstart );
  6257. if ( strlen(com_token) <= 0)
  6258. break; // eof
  6259. if ( com_token[0] != CHAR_LEFT_PAREN )
  6260. continue;
  6261. break;
  6262. }
  6263. while (1)
  6264. {
  6265. pstart = COM_Parse( pstart );
  6266. if (com_token[0] == CHAR_RIGHT_PAREN)
  6267. break;
  6268. grouprule_t *pgroup = &g_grouprules[g_cgrouprules];
  6269. // copy mixgroup name, directory, classname
  6270. // if no value specified, set to 0 length string
  6271. if (com_token[0])
  6272. Q_memcpy(pgroup->szmixgroup, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
  6273. pstart = COM_Parse( pstart );
  6274. if (com_token[0])
  6275. Q_memcpy(pgroup->szdir, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
  6276. pgroup->classId = -1;
  6277. pstart = COM_Parse( pstart );
  6278. if (com_token[0])
  6279. {
  6280. pgroup->classId = MXR_AddClassname( com_token );
  6281. }
  6282. // make sure all copied strings are null terminated
  6283. pgroup->szmixgroup[CMXRNAMEMAX-1] = 0;
  6284. pgroup->szdir[CMXRNAMEMAX-1] = 0;
  6285. // lookup chan
  6286. pstart = COM_Parse( pstart );
  6287. if (com_token[0])
  6288. {
  6289. if (!Q_stricmp(com_token, "CHAN_STATIC"))
  6290. pgroup->chantype = CHAN_STATIC;
  6291. else if (!Q_stricmp(com_token, "CHAN_WEAPON"))
  6292. pgroup->chantype = CHAN_WEAPON;
  6293. else if (!Q_stricmp(com_token, "CHAN_VOICE"))
  6294. pgroup->chantype = CHAN_VOICE;
  6295. else if (!Q_stricmp(com_token, "CHAN_VOICE2"))
  6296. pgroup->chantype = CHAN_VOICE2;
  6297. else if (!Q_stricmp(com_token, "CHAN_BODY"))
  6298. pgroup->chantype = CHAN_BODY;
  6299. else if (!Q_stricmp(com_token, "CHAN_ITEM"))
  6300. pgroup->chantype = CHAN_ITEM;
  6301. }
  6302. else
  6303. pgroup->chantype = -1;
  6304. // get sndlvls
  6305. pstart = COM_Parse( pstart );
  6306. if (com_token[0])
  6307. pgroup->soundlevel_min = atoi(com_token);
  6308. else
  6309. pgroup->soundlevel_min = -1;
  6310. pstart = COM_Parse( pstart );
  6311. if (com_token[0])
  6312. pgroup->soundlevel_max = atoi(com_token);
  6313. else
  6314. pgroup->soundlevel_max = -1;
  6315. // get duck priority, IsDucked, Causes_ducking, duck_target_pct
  6316. pstart = COM_Parse( pstart );
  6317. if (com_token[0])
  6318. pgroup->priority = atoi(com_token);
  6319. else
  6320. pgroup->priority = 50;
  6321. pstart = COM_Parse( pstart );
  6322. if (com_token[0])
  6323. pgroup->is_ducked = atoi(com_token);
  6324. else
  6325. pgroup->is_ducked = 0;
  6326. pstart = COM_Parse( pstart );
  6327. if (com_token[0])
  6328. pgroup->causes_ducking = atoi(com_token);
  6329. else
  6330. pgroup->causes_ducking = 0;
  6331. pstart = COM_Parse( pstart );
  6332. if (com_token[0])
  6333. pgroup->duck_target_pct = ((float)(atoi(com_token))) / 100.0f;
  6334. else
  6335. pgroup->duck_target_pct = 0.5f;
  6336. pstart = COM_Parse( pstart );
  6337. if (com_token[0])
  6338. pgroup->ducker_threshold = ((float)(atoi(com_token))) / 100.0f;
  6339. else
  6340. pgroup->ducker_threshold = 0.5f;
  6341. pgroup->duck_ramp_val = 1.0;
  6342. pgroup->duck_target_vol = 1.0;
  6343. pgroup->total_vol = 0.0;
  6344. // set mixgroup id to -1
  6345. pgroup->mixgroupid = -1;
  6346. // update rule count
  6347. g_cgrouprules++;
  6348. if (g_cgrouprules >= CMXRGROUPRULESMAX)
  6349. {
  6350. // UNDONE: error! too many rules
  6351. break;
  6352. }
  6353. }
  6354. // now process all groupids in groups, such that
  6355. // each mixgroup gets a unique id.
  6356. MXR_AssignGroupIds();
  6357. // now load g_soundmixers
  6358. // while not at end of file...
  6359. // scan for "<name>", if found save as new soundmixer name
  6360. // while not '}'
  6361. // scan for "<name>", save as groupname
  6362. // scan for "<num>", save as mix value
  6363. while(1)
  6364. {
  6365. pstart = COM_Parse( pstart );
  6366. if ( strlen(com_token) <= 0)
  6367. break; // eof
  6368. // save name in soundmixer
  6369. soundmixer_t *pmixer = &g_soundmixers[g_csoundmixers];
  6370. Q_memcpy(pmixer->szsoundmixer, com_token, min((size_t)CMXRNAMEMAX-1, strlen(com_token)));
  6371. // init all mixer values to -1.
  6372. for (int j = 0; j < CMXRGROUPMAX; j++)
  6373. {
  6374. pmixer->mapMixgroupidToValue[j] = -1.0;
  6375. }
  6376. // load all groupnames for this soundmixer
  6377. while (1)
  6378. {
  6379. pstart = COM_Parse( pstart );
  6380. if (com_token[0] == CHAR_LEFT_PAREN)
  6381. continue; // skip {
  6382. if (com_token[0] == CHAR_RIGHT_PAREN)
  6383. break; // finished with this sounmixer
  6384. // lookup mixgroupid for groupname
  6385. int mixgroupid = MXR_GetMixgroupFromName( com_token );
  6386. float value;
  6387. // get mix value
  6388. pstart = COM_Parse( pstart );
  6389. value = atof( com_token );
  6390. // store value for mixgroupid
  6391. Assert(mixgroupid <= CMXRGROUPMAX);
  6392. pmixer->mapMixgroupidToValue[mixgroupid] = value;
  6393. }
  6394. g_csoundmixers++;
  6395. if (g_csoundmixers >= CMXRSOUNDMIXERSMAX)
  6396. {
  6397. // UNDONE: error! to many sound mixers
  6398. break;
  6399. }
  6400. }
  6401. bResult = true;
  6402. // loadmxr_exit:
  6403. free( pbuffer );
  6404. return bResult;
  6405. }
  6406. void MXR_ReleaseMemory( void )
  6407. {
  6408. // free all resources
  6409. }
  6410. float S_GetMono16Samples( const char *pszName, CUtlVector< short >& sampleList )
  6411. {
  6412. CSfxTable *pSfx = S_PrecacheSound( PSkipSoundChars( pszName ) );
  6413. if ( !pSfx )
  6414. return 0.0f;
  6415. CAudioSource *pWave = pSfx->pSource;
  6416. if ( !pWave )
  6417. return 0.0f;
  6418. int nType = pWave->GetType();
  6419. if ( nType != CAudioSource::AUDIO_SOURCE_WAV )
  6420. return 0.0f;
  6421. CAudioMixer *pMixer = pWave->CreateMixer();
  6422. if ( !pMixer )
  6423. return 0.0f;
  6424. float duration = AudioSource_GetSoundDuration( pSfx );
  6425. // Determine start/stop positions
  6426. int totalsamples = (int)( duration * pWave->SampleRate() );
  6427. if ( totalsamples <= 0 )
  6428. return 0;
  6429. bool bStereo = pWave->IsStereoWav();
  6430. int mix_sample_size = pMixer->GetMixSampleSize();
  6431. int nNumChannels = bStereo ? 2 : 1;
  6432. char *pData = NULL;
  6433. int pos = 0;
  6434. int remaining = totalsamples;
  6435. while ( remaining > 0 )
  6436. {
  6437. int blockSize = min( remaining, 1000 );
  6438. char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
  6439. int copied = pWave->GetOutputData( (void **)&pData, pos, blockSize, copyBuf );
  6440. if ( !copied )
  6441. {
  6442. break;
  6443. }
  6444. remaining -= copied;
  6445. pos += copied;
  6446. // Now get samples out of output data
  6447. switch ( nNumChannels )
  6448. {
  6449. default:
  6450. case 1:
  6451. {
  6452. for ( int i = 0; i < copied; ++i )
  6453. {
  6454. int offset = i * mix_sample_size;
  6455. short sample = 0;
  6456. if ( mix_sample_size == 1 )
  6457. {
  6458. char s = *( char * )( pData + offset );
  6459. // Upscale it to fit into a short
  6460. sample = s << 8;
  6461. }
  6462. else if ( mix_sample_size == 2 )
  6463. {
  6464. sample = *( short * )( pData + offset );
  6465. }
  6466. else if ( mix_sample_size == 4 )
  6467. {
  6468. // Not likely to have 4 bytes mono!!!
  6469. Assert( 0 );
  6470. int s = *( int * )( pData + offset );
  6471. sample = s >> 16;
  6472. }
  6473. else
  6474. {
  6475. Assert( 0 );
  6476. }
  6477. sampleList.AddToTail( sample );
  6478. }
  6479. }
  6480. break;
  6481. case 2:
  6482. {
  6483. for ( int i = 0; i < copied; ++i )
  6484. {
  6485. int offset = i * mix_sample_size;
  6486. short left = 0;
  6487. short right = 0;
  6488. if ( mix_sample_size == 1 )
  6489. {
  6490. // Not possible!!!, must be at least 2 bytes!!!
  6491. Assert( 0 );
  6492. char v = *( char * )( pData + offset );
  6493. left = right = ( v << 8 );
  6494. }
  6495. else if ( mix_sample_size == 2 )
  6496. {
  6497. // One byte per channel
  6498. left = (short)( ( *(char *)( pData + offset ) ) << 8 );
  6499. right = (short)( ( *(char *)( pData + offset + 1 ) ) << 8 );
  6500. }
  6501. else if ( mix_sample_size == 4 )
  6502. {
  6503. // 2 bytes per channel
  6504. left = *( short * )( pData + offset );
  6505. right = *( short * )( pData + offset + 2 );
  6506. }
  6507. else
  6508. {
  6509. Assert( 0 );
  6510. }
  6511. short sample = ( left + right ) >> 1;
  6512. sampleList.AddToTail( sample );
  6513. }
  6514. }
  6515. break;
  6516. }
  6517. }
  6518. delete pMixer;
  6519. return duration;
  6520. }