sfxSound.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "sfx/sfxSound.h"
  23. #include "sfx/sfxDevice.h"
  24. #include "sfx/sfxVoice.h"
  25. #include "sfx/sfxSystem.h"
  26. #include "sfx/sfxBuffer.h"
  27. #include "sfx/sfxStream.h"
  28. #include "sfx/sfxDescription.h"
  29. #include "core/util/safeDelete.h"
  30. #include "console/engineAPI.h"
  31. //#define DEBUG_SPEW
  32. IMPLEMENT_CONOBJECT( SFXSound );
  33. ConsoleDocClass( SFXSound,
  34. "@brief A sound controller that directly plays a single sound file.\n\n"
  35. "When playing individual audio files, SFXSounds are implicitly created by the sound system.\n\n"
  36. "Each sound source has an associated play cursor that can be queried and explicitly positioned "
  37. "by the user. The cursor is a floating-point value measured in seconds.\n\n"
  38. "For streamed sources, playback may not be continuous in case the streaming queue is interrupted.\n\n"
  39. "@note This class cannot be instantiated directly by the user but rather is implicitly created by the sound "
  40. "system when sfxCreateSource() or sfxPlayOnce() is called on a SFXProfile instance.\n\n"
  41. "@section SFXSound_virtualization Sounds and Voices\n\n"
  42. "To actually emit an audible signal, a sound must allocate a resource on the sound device through "
  43. "which the sound data is being played back. This resource is called 'voice'.\n\n"
  44. "As with other types of resources, the availability of these resources may be restricted, i.e. a given "
  45. "sound device will usually only support a fixed number of voices that are playing at the same time. Since, "
  46. "however, there may be arbitrary many SFXSounds instantiated and playing at the same time, this needs to be "
  47. "solved. \n\n"
  48. "@see SFXDescription::priority\n"
  49. "@ingroup SFX"
  50. );
  51. //-----------------------------------------------------------------------------
  52. SFXSound::SFXSound()
  53. : mVoice( NULL ), mDuration(0), mSetPositionValue(0)
  54. {
  55. // NOTE: This should never be used directly
  56. // and is only here to satisfy satisfy the
  57. // construction needs of IMPLEMENT_CONOBJECT.
  58. }
  59. //-----------------------------------------------------------------------------
  60. SFXSound::SFXSound( SFXProfile *profile, SFXDescription* desc )
  61. : Parent( profile, desc ),
  62. mVoice( NULL ), mDuration(0)
  63. {
  64. mSetPositionValue = 0;
  65. }
  66. //-----------------------------------------------------------------------------
  67. SFXSound* SFXSound::_create( SFXDevice *device, SFXProfile *profile )
  68. {
  69. AssertFatal( profile, "SFXSound::_create() - Got a null profile!" );
  70. SFXDescription* desc = profile->getDescription();
  71. if ( !desc )
  72. {
  73. Con::errorf( "SFXSound::_create() - Profile has null description!" );
  74. return NULL;
  75. }
  76. // Create the sound and register it.
  77. SFXSound* sound = new SFXSound( profile, desc );
  78. sound->registerObject();
  79. // Initialize the buffer.
  80. SFXBuffer* buffer = profile->getBuffer();
  81. if( !buffer )
  82. {
  83. sound->deleteObject();
  84. Con::errorf( "SFXSound::_create() - Could not create device buffer!" );
  85. return NULL;
  86. }
  87. sound->_setBuffer( buffer );
  88. // The sound is a console object... register it.
  89. #ifdef DEBUG_SPEW
  90. Platform::outputDebugString( "[SFXSound] new sound '%i' with profile '%i' (\"%s\")",
  91. sound->getId(), profile->getId(), profile->getName() );
  92. #endif
  93. // Hook up reloading.
  94. profile->getChangedSignal().notify( sound, &SFXSound::_onProfileChanged );
  95. return sound;
  96. }
  97. //-----------------------------------------------------------------------------
  98. SFXSound* SFXSound::_create( SFXDevice* device,
  99. const ThreadSafeRef< SFXStream >& stream,
  100. SFXDescription* description )
  101. {
  102. AssertFatal( stream.ptr() != NULL, "SFXSound::_create() - Got a null stream!" );
  103. AssertFatal( description, "SFXSound::_create() - Got a null description!" );
  104. // Create the source and register it.
  105. SFXSound* source = new SFXSound( NULL, description );
  106. source->registerObject();
  107. // Create the buffer.
  108. SFXBuffer* buffer = SFX->_createBuffer( stream, description );
  109. if( !buffer )
  110. {
  111. SAFE_DELETE_OBJECT(source);
  112. Con::errorf( "SFXSound::_create() - Could not create device buffer!" );
  113. return NULL;
  114. }
  115. source->_setBuffer( buffer );
  116. #ifdef DEBUG_SPEW
  117. Platform::outputDebugString( "[SFXSound] new source '%i' for stream", source->getId() );
  118. #endif
  119. return source;
  120. }
  121. //-----------------------------------------------------------------------------
  122. void SFXSound::_reloadBuffer()
  123. {
  124. SFXProfile* profile = getProfile();
  125. if( profile != NULL && _releaseVoice() )
  126. {
  127. SFXBuffer* buffer = profile->getBuffer();
  128. if( !buffer )
  129. {
  130. Con::errorf( "SFXSound::_reloadBuffer() - Could not create device buffer!" );
  131. return;
  132. }
  133. _setBuffer( buffer );
  134. if( getLastStatus() == SFXStatusPlaying )
  135. SFX->_assignVoice( this );
  136. }
  137. }
  138. //-----------------------------------------------------------------------------
  139. void SFXSound::_setBuffer( SFXBuffer* buffer )
  140. {
  141. mBuffer = buffer;
  142. // There is no telling when the device will be
  143. // destroyed and the buffers deleted.
  144. //
  145. // By caching the duration now we can allow sources
  146. // to continue virtual playback until the device
  147. // is restored.
  148. mDuration = mBuffer->getDuration();
  149. }
  150. //-----------------------------------------------------------------------------
  151. bool SFXSound::_allocVoice( SFXDevice* device )
  152. {
  153. // We shouldn't have any existing voice!
  154. AssertFatal( !mVoice, "SFXSound::_allocVoice() - Already had a voice!" );
  155. // Must not assign voice to source that isn't playing.
  156. AssertFatal( getLastStatus() == SFXStatusPlaying,
  157. "SFXSound::_allocVoice() - Source is not playing!" );
  158. // The buffer can be lost when the device is reset
  159. // or changed, so initialize it if we have to. If
  160. // that fails then we cannot create the voice.
  161. if( mBuffer.isNull() )
  162. {
  163. SFXProfile* profile = getProfile();
  164. if( profile != NULL )
  165. {
  166. SFXBuffer* buffer = profile->getBuffer();
  167. if( buffer )
  168. _setBuffer( buffer );
  169. }
  170. if( mBuffer.isNull() )
  171. return false;
  172. }
  173. // Ask the device for a voice based on this buffer.
  174. mVoice = device->createVoice( is3d(), mBuffer );
  175. if( !mVoice )
  176. return false;
  177. // Set initial properties.
  178. mVoice->setVolume( mPreAttenuatedVolume );
  179. mVoice->setPitch( mEffectivePitch );
  180. mVoice->setPriority( mEffectivePriority );
  181. if( mDescription->mRolloffFactor != -1.f )
  182. mVoice->setRolloffFactor( mDescription->mRolloffFactor );
  183. // Set 3D parameters.
  184. if( is3d() )
  185. {
  186. // Scatter the position, if requested. Do this only once so
  187. // we don't change position when resuming from virtualized
  188. // playback.
  189. if( !mTransformScattered )
  190. _scatterTransform();
  191. // Set the 3D attributes.
  192. setTransform( mTransform );
  193. setVelocity( mVelocity );
  194. _setMinMaxDistance( mMinDistance, mMaxDistance );
  195. _setCone( mConeInsideAngle, mConeOutsideAngle, mConeOutsideVolume );
  196. }
  197. // Set reverb, if enabled.
  198. if( mDescription->mUseReverb )
  199. mVoice->setReverb( mDescription->mReverb );
  200. // Update the duration... it shouldn't have changed, but
  201. // its probably better that we're accurate if it did.
  202. mDuration = mBuffer->getDuration();
  203. // If virtualized playback has been started, we transfer its position to the
  204. // voice and stop virtualization.
  205. const U32 playTime = mPlayTimer.getPosition();
  206. if( playTime > 0 )
  207. {
  208. const U32 pos = mBuffer->getFormat().getSampleCount( playTime );
  209. mVoice->setPosition( pos);
  210. }
  211. mVoice->play( isLooping() );
  212. #ifdef DEBUG_SPEW
  213. Platform::outputDebugString( "[SFXSound] allocated voice for source '%i' (pos=%i, 3d=%i, vol=%f)",
  214. getId(), playTime, is3d(), mPreAttenuatedVolume );
  215. #endif
  216. return true;
  217. }
  218. //-----------------------------------------------------------------------------
  219. void SFXSound::_onParameterEvent( SFXParameter* parameter, SFXParameterEvent event )
  220. {
  221. Parent::_onParameterEvent( parameter, event );
  222. switch( event )
  223. {
  224. case SFXParameterEvent_ValueChanged:
  225. switch( parameter->getChannel() )
  226. {
  227. case SFXChannelCursor:
  228. setPosition( parameter->getValue() * 1000.f );
  229. break;
  230. default:
  231. break;
  232. }
  233. break;
  234. default:
  235. break;
  236. }
  237. }
  238. //-----------------------------------------------------------------------------
  239. void SFXSound::onRemove()
  240. {
  241. SFXProfile* profile = getProfile();
  242. if( profile != NULL )
  243. profile->getChangedSignal().remove( this, &SFXSound::_onProfileChanged );
  244. Parent::onRemove();
  245. }
  246. //-----------------------------------------------------------------------------
  247. void SFXSound::onDeleteNotify( SimObject* object )
  248. {
  249. if( object == mDescription )
  250. {
  251. deleteObject();
  252. return;
  253. }
  254. Parent::onDeleteNotify( object );
  255. }
  256. //-----------------------------------------------------------------------------
  257. bool SFXSound::_releaseVoice()
  258. {
  259. if( !mVoice )
  260. return true;
  261. // Refuse to release a voice for a streaming buffer that
  262. // is not coming from a profile. For streaming buffers, we will
  263. // have to release the buffer, too, and without a profile we don't
  264. // know how to recreate the stream.
  265. if( isStreaming() && !mTrack )
  266. return false;
  267. // If we're currently playing, transfer our playback position
  268. // to the playtimer so we can virtualize playback while not
  269. // having a voice.
  270. SFXStatus status = getLastStatus();
  271. if( status == SFXStatusPlaying || status == SFXStatusBlocked )
  272. {
  273. // Sync up the play timer with the voice's current position to make
  274. // sure we handle any lag that's cropped up.
  275. mPlayTimer.setPosition( mVoice->getPosition() );
  276. if( status == SFXStatusBlocked )
  277. status = SFXStatusPlaying;
  278. }
  279. mVoice = NULL;
  280. // If this is a streaming source, release our buffer, too.
  281. // Otherwise the voice will stick around as it is uniquely assigned to
  282. // the buffer. When we get reassigned a voice, we will have to do
  283. // a full stream seek anyway, so it's no real loss here.
  284. if( isStreaming() )
  285. mBuffer = NULL;
  286. #ifdef DEBUG_SPEW
  287. Platform::outputDebugString( "[SFXSound] release voice for source '%i' (status: %s)",
  288. getId(), SFXStatusToString( status ) );
  289. #endif
  290. return true;
  291. }
  292. //-----------------------------------------------------------------------------
  293. void SFXSound::_play()
  294. {
  295. Parent::_play();
  296. if( mVoice )
  297. mVoice->play( isLooping() );
  298. else
  299. {
  300. // To ensure the fastest possible reaction
  301. // to this playback let the system reassign
  302. // voices immediately.
  303. SFX->_assignVoice( this );
  304. // If we did not get assigned a voice, we'll be
  305. // running virtualized.
  306. #ifdef DEBUG_SPEW
  307. if( !mVoice )
  308. Platform::outputDebugString( "[SFXSound] virtualizing playback of source '%i'", getId() );
  309. #endif
  310. }
  311. if(getPosition() != mSetPositionValue)
  312. setPosition(mSetPositionValue);
  313. mSetPositionValue = 0; //Non looping sounds need this to reset.
  314. }
  315. //-----------------------------------------------------------------------------
  316. void SFXSound::_stop()
  317. {
  318. Parent::_stop();
  319. if( mVoice )
  320. mVoice->stop();
  321. mSetPositionValue = 0;
  322. }
  323. //-----------------------------------------------------------------------------
  324. void SFXSound::_pause()
  325. {
  326. Parent::_pause();
  327. if( mVoice )
  328. mVoice->pause();
  329. mSetPositionValue = getPosition();
  330. }
  331. //-----------------------------------------------------------------------------
  332. void SFXSound::_updateStatus()
  333. {
  334. // If we have a voice, use its status.
  335. if( mVoice )
  336. {
  337. SFXStatus voiceStatus = mVoice->getStatus();
  338. // Filter out SFXStatusBlocked.
  339. if( voiceStatus == SFXStatusBlocked )
  340. _setStatus( SFXStatusPlaying );
  341. else
  342. _setStatus( voiceStatus );
  343. return;
  344. }
  345. // If we're not in a playing state or we're a looping
  346. // sound then we don't need to calculate the status.
  347. if( isLooping() || mStatus != SFXStatusPlaying )
  348. return;
  349. // If we're playing and don't have a voice we
  350. // need to decide if the sound is done playing
  351. // to ensure proper virtualization of the sound.
  352. if( mPlayTimer.getPosition() > mDuration )
  353. {
  354. _stop();
  355. _setStatus( SFXStatusStopped );
  356. }
  357. }
  358. //-----------------------------------------------------------------------------
  359. void SFXSound::_updateVolume( const MatrixF& listener )
  360. {
  361. F32 oldPreAttenuatedVolume = mPreAttenuatedVolume;
  362. Parent::_updateVolume( listener );
  363. // If we have a voice and the pre-attenuated volume has
  364. // changed, pass it on to the voice. Attenuation itself will
  365. // happen on the device.
  366. if( mVoice != NULL && oldPreAttenuatedVolume != mPreAttenuatedVolume )
  367. mVoice->setVolume( mPreAttenuatedVolume );
  368. }
  369. //-----------------------------------------------------------------------------
  370. void SFXSound::_updatePitch()
  371. {
  372. F32 oldEffectivePitch = mEffectivePitch;
  373. Parent::_updatePitch();
  374. if( mVoice != NULL && oldEffectivePitch != mEffectivePitch )
  375. mVoice->setPitch( mEffectivePitch );
  376. }
  377. //-----------------------------------------------------------------------------
  378. void SFXSound::_updatePriority()
  379. {
  380. F32 oldEffectivePriority = mEffectivePriority;
  381. Parent::_updatePriority();
  382. if( mVoice != NULL && oldEffectivePriority != mEffectivePriority )
  383. mVoice->setPriority( mEffectivePriority );
  384. }
  385. //-----------------------------------------------------------------------------
  386. U32 SFXSound::getPosition() const
  387. {
  388. if( getLastStatus() == SFXStatusStopped)
  389. return mSetPositionValue;
  390. if( mVoice )
  391. return mVoice->getFormat().getDuration( mVoice->getPosition() );
  392. else
  393. return ( mPlayTimer.getPosition() % mDuration ); // Clamp for looped sounds.
  394. }
  395. //-----------------------------------------------------------------------------
  396. void SFXSound::setPosition( U32 ms )
  397. {
  398. AssertFatal( ms < getDuration(), "SFXSound::setPosition() - position out of range" );
  399. mSetPositionValue = ms;
  400. if( mVoice )
  401. mVoice->setPosition( mVoice->getFormat().getSampleCount( ms ) );
  402. else
  403. mPlayTimer.setPosition( ms );
  404. }
  405. //-----------------------------------------------------------------------------
  406. void SFXSound::setVelocity( const VectorF& velocity )
  407. {
  408. Parent::setVelocity( velocity );
  409. if( mVoice && is3d() )
  410. mVoice->setVelocity( velocity );
  411. }
  412. //-----------------------------------------------------------------------------
  413. void SFXSound::setTransform( const MatrixF& transform )
  414. {
  415. Parent::setTransform( transform );
  416. if( mVoice && is3d() )
  417. mVoice->setTransform( mTransform );
  418. }
  419. //-----------------------------------------------------------------------------
  420. void SFXSound::_setMinMaxDistance( F32 min, F32 max )
  421. {
  422. Parent::_setMinMaxDistance( min, max );
  423. if( mVoice && is3d() )
  424. mVoice->setMinMaxDistance( mMinDistance, mMaxDistance );
  425. }
  426. //-----------------------------------------------------------------------------
  427. void SFXSound::_setCone( F32 innerAngle,
  428. F32 outerAngle,
  429. F32 outerVolume )
  430. {
  431. Parent::_setCone( innerAngle, outerAngle, outerVolume );
  432. if( mVoice && is3d() )
  433. mVoice->setCone( mConeInsideAngle,
  434. mConeOutsideAngle,
  435. mConeOutsideVolume );
  436. }
  437. //-----------------------------------------------------------------------------
  438. bool SFXSound::isReady() const
  439. {
  440. return ( mBuffer != NULL && mBuffer->isReady() );
  441. }
  442. //-----------------------------------------------------------------------------
  443. bool SFXSound::isVirtualized() const
  444. {
  445. return ( ( mVoice == NULL && isPlaying() ) ||
  446. ( mVoice != NULL && mVoice->isVirtual() ) );
  447. }
  448. //-----------------------------------------------------------------------------
  449. SFXProfile* SFXSound::getProfile() const
  450. {
  451. return dynamic_cast< SFXProfile* >( mTrack.getPointer() );
  452. }
  453. //-----------------------------------------------------------------------------
  454. F32 SFXSound::getElapsedPlayTimeCurrentCycle() const
  455. {
  456. return F32( getPosition() ) / 1000.f;
  457. }
  458. //-----------------------------------------------------------------------------
  459. F32 SFXSound::getTotalPlayTime() const
  460. {
  461. return F32( mDuration ) / 1000.f;
  462. }
  463. //-----------------------------------------------------------------------------
  464. // Let the user define a priority value for each channel
  465. // in script. We assign it in the system init and use
  466. // it when doleing out hardware handles.
  467. S32 QSORT_CALLBACK SFXSound::qsortCompare( const void* item1, const void* item2 )
  468. {
  469. const SFXSound* source1 = *( ( SFXSound** ) item1 );
  470. const SFXSound* source2 = *( ( SFXSound** ) item2 );
  471. // Sounds that are playing are always sorted
  472. // closer than non-playing sounds.
  473. const bool source1IsPlaying = source1->isPlaying();
  474. const bool source2IsPlaying = source2->isPlaying();
  475. if( !source1IsPlaying && !source2IsPlaying )
  476. return 0;
  477. else if( !source1IsPlaying && source2IsPlaying )
  478. return 1;
  479. else if( source1IsPlaying && !source2IsPlaying )
  480. return -1;
  481. // Louder attenuated volumes take precedence but adjust them
  482. // by priority so that less audible sounds with higher priority
  483. // become more important.
  484. F32 volume1 = source1->getAttenuatedVolume();
  485. F32 volume2 = source2->getAttenuatedVolume();
  486. volume1 += volume1 * source1->mEffectivePriority;
  487. volume2 += volume2 * source2->mEffectivePriority;
  488. if( volume1 < volume2 )
  489. return 1;
  490. if( volume1 > volume2 )
  491. return -1;
  492. // If we got this far then the source that was
  493. // played last has the higher priority.
  494. if( source1->mPlayStartTick > source2->mPlayStartTick )
  495. return -1;
  496. if( source1->mPlayStartTick < source2->mPlayStartTick )
  497. return 1;
  498. // These are sorted the same!
  499. return 0;
  500. }
  501. //=============================================================================
  502. // Console Methods.
  503. //=============================================================================
  504. // MARK: ---- Console Methods ----
  505. //-----------------------------------------------------------------------------
  506. DefineEngineMethod( SFXSound, isReady, bool, (),,
  507. "Test whether the sound data associated with the sound has been fully loaded and is ready for playback.\n"
  508. "For streamed sounds, this will be false during playback when the stream queue for the sound is starved and "
  509. "waiting for data. For buffered sounds, only an initial loading phase will potentially cause isReady to "
  510. "return false.\n\n"
  511. "@return True if the sound is ready for playback." )
  512. {
  513. return object->isReady();
  514. }
  515. //-----------------------------------------------------------------------------
  516. DefineEngineMethod( SFXSound, getPosition, F32, (),,
  517. "Get the current playback position in seconds.\n"
  518. "@return The current play cursor offset." )
  519. {
  520. return F32( object->getPosition() ) * 0.001f;
  521. }
  522. //-----------------------------------------------------------------------------
  523. DefineEngineMethod( SFXSound, setPosition, void, ( F32 position ),,
  524. "Set the current playback position in seconds.\n"
  525. "If the source is currently playing, playback will jump to the new position. If playback is stopped or paused, "
  526. "playback will resume at the given position when play() is called.\n\n"
  527. "@param position The new position of the play cursor (in seconds).\n" )
  528. {
  529. position *= 1000.0f;
  530. if( position >= 0 && position < object->getDuration() )
  531. object->setPosition( position );
  532. }
  533. //-----------------------------------------------------------------------------
  534. DefineEngineMethod( SFXSound, getDuration, F32, (),,
  535. "Get the total play time (in seconds) of the sound data attached to the sound.\n"
  536. "@return \n\n"
  537. "@note Be aware that for looped sounds, this will not return the total playback time of the sound.\n" )
  538. {
  539. return F32( object->getDuration() ) * 0.001f;
  540. }