sfxSound.cpp

Engine/source/sfx/sfxSound.cpp

More...

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