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