sfxVoice.cpp

Engine/source/sfx/sfxVoice.cpp

More...

Detailed Description

  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/sfxVoice.h"
 25#include "sfx/sfxBuffer.h"
 26#include "sfx/sfxInternal.h"
 27#include "console/console.h"
 28
 29
 30// [rene, 07-May-11] The interplay between SFXBuffer and SFXVoice here isn't good.
 31//    Too complex, and while it works reliably in most cases, when doing seeks
 32//    on streaming sources, it is prone to subtle timing dependencies.
 33
 34
 35//#define DEBUG_SPEW
 36
 37
 38Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceCreatedSignal;
 39Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceDestroyedSignal;
 40
 41
 42//-----------------------------------------------------------------------------
 43
 44SFXVoice::SFXVoice( SFXBuffer* buffer )
 45   : mStatus( SFXStatusNull ),
 46     mBuffer( buffer ),
 47     mOffset( 0 )
 48{
 49}
 50
 51//-----------------------------------------------------------------------------
 52
 53SFXVoice::~SFXVoice()
 54{
 55   smVoiceDestroyedSignal.trigger( this );
 56   
 57   if( mBuffer )
 58      mBuffer->mOnStatusChange.remove( this, &SFXVoice::_onBufferStatusChange );
 59}
 60
 61//-----------------------------------------------------------------------------
 62
 63void SFXVoice::_attachToBuffer()
 64{
 65   using namespace SFXInternal;
 66
 67   // If the buffer is unique, attach us as its unique voice.
 68
 69   if( mBuffer->isUnique() )
 70   {
 71      AssertFatal( !mBuffer->mUniqueVoice,
 72         "SFXVoice::SFXVoice - streaming buffer already is assigned a voice" );
 73
 74      mBuffer->mUniqueVoice = this;
 75
 76      // The buffer can start its queuing now so give it a chance
 77      // to run an update.
 78      SFXInternal::TriggerUpdate();
 79   }
 80
 81   mBuffer->mOnStatusChange.notify( this, &SFXVoice::_onBufferStatusChange );
 82   
 83   smVoiceCreatedSignal.trigger( this );
 84}
 85
 86//-----------------------------------------------------------------------------
 87
 88void SFXVoice::_onBufferStatusChange( SFXBuffer* buffer, SFXBuffer::Status newStatus )
 89{
 90   AssertFatal( buffer == mBuffer, "SFXVoice::_onBufferStatusChange() - got an invalid buffer" );
 91
 92   #ifdef DEBUG_SPEW
 93   Platform::outputDebugString( "[SFXVoice] Buffer changes status to: %s",
 94      newStatus == SFXBuffer::STATUS_Null ? "STATUS_Null" :
 95      newStatus == SFXBuffer::STATUS_Loading ? "STATUS_Loading" :
 96      newStatus == SFXBuffer::STATUS_Ready ? "STATUS_Ready" :
 97      newStatus == SFXBuffer::STATUS_Blocked ? "STATUS_Blocked" :
 98      newStatus == SFXBuffer::STATUS_AtEnd ? "STATUS_AtEnd" : "unknown" );
 99   #endif
100
101   // This is called concurrently!
102
103   switch( newStatus )
104   {
105      case SFXBuffer::STATUS_Loading:
106         // Can ignore this.  Buffer simply lets us know it has started
107         // its initial stream load.
108         break;
109
110      case SFXBuffer::STATUS_AtEnd:
111         
112         // Streaming voice has played to end of stream.
113
114         if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) )
115         {
116            _stop();
117            mOffset = 0;
118
119            dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusStopped );
120         }
121         #ifdef DEBUG_SPEW
122         Platform::outputDebugString( "[SFXVoice] Voice stopped as end of stream reached" );
123         #endif
124         break;
125
126      case SFXBuffer::STATUS_Blocked:
127
128         // Streaming has fallen behind.
129
130         if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) )
131         {
132            _pause();
133            dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusBlocked );
134         }
135         #ifdef DEBUG_SPEW
136         Platform::outputDebugString( "[SFXVoice] Voice waiting for buffer to catch up" );
137         #endif
138         break;
139
140      case SFXBuffer::STATUS_Ready:
141         if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
142         {
143            // Get the playback going again.
144
145            _play();
146            dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPlaying );
147
148            #ifdef DEBUG_SPEW
149            Platform::outputDebugString( "[SFXVoice] Buffer caught up with voice" );
150            #endif
151         }
152         break;
153      
154      case SFXBuffer::STATUS_Null:
155         AssertFatal( false, "SFXVoice::_onBufferStatusChange - Buffer changed to invalid NULL status" );
156         break;
157   }
158}
159
160//-----------------------------------------------------------------------------
161
162SFXStatus SFXVoice::getStatus() const
163{
164   // Detect when the device has finished playback.  Only for
165   // non-streaming voices.  For streaming voices, we rely on
166   // the buffer to send us a STATUS_AtEnd signal when it is
167   // done playing.
168
169   if( mStatus == SFXStatusPlaying &&
170       !mBuffer->isStreaming() &&
171       _status() == SFXStatusStopped )
172      mStatus = SFXStatusStopped;
173
174   return mStatus;
175}
176
177//-----------------------------------------------------------------------------
178
179void SFXVoice::play( bool looping )
180{
181   AssertFatal( mBuffer != NULL, "SFXVoice::play() - no buffer" );
182   using namespace SFXInternal;
183
184   // For streaming, check whether we have played previously.
185   // If so, reset the buffer's stream.
186
187   if( mBuffer->isStreaming() &&
188       mStatus == SFXStatusStopped )
189      _resetStream( 0 );
190
191   // Now switch state.
192
193   while( mStatus != SFXStatusPlaying &&
194          mStatus != SFXStatusBlocked )
195   {
196      if( !mBuffer->isReady() &&
197          ( dCompareAndSwap( ( U32& ) mStatus, SFXStatusNull, SFXStatusBlocked ) ||
198            dCompareAndSwap( ( U32& ) mStatus, SFXStatusStopped, SFXStatusBlocked ) ||
199            dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusBlocked ) ) )
200      {
201         #ifdef DEBUG_SPEW
202         Platform::outputDebugString( "[SFXVoice] Wanted to start playback but buffer isn't ready" );
203         #endif
204
205         break;
206      }
207
208      else if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusNull, SFXStatusTransition ) ||
209               dCompareAndSwap( ( U32& ) mStatus, SFXStatusStopped, SFXStatusTransition ) ||
210               dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusTransition ) )
211      {
212         _play();
213         dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPlaying );
214
215         #ifdef DEBUG_SPEW
216         Platform::outputDebugString( "[SFXVoice] Started playback" );
217         #endif
218
219         break;
220      }
221   }
222}
223
224//-----------------------------------------------------------------------------
225
226void SFXVoice::pause()
227{
228   while( mStatus != SFXStatusPaused &&
229          mStatus != SFXStatusNull &&
230          mStatus != SFXStatusStopped )
231   {
232      if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) ||
233          dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
234      {
235         _pause();
236         dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPaused );
237
238         break;
239      }
240   }
241}
242
243//-----------------------------------------------------------------------------
244
245void SFXVoice::stop()
246{
247   while( mStatus != SFXStatusStopped &&
248          mStatus != SFXStatusNull )
249   {
250      if( dCompareAndSwap( ( U32& ) mStatus, ( U32 ) SFXStatusPlaying, ( U32 ) SFXStatusTransition ) ||
251          dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusTransition ) ||
252          dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
253      {
254         _stop();
255         dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusStopped );
256
257         break;
258      }
259   }
260}
261
262//-----------------------------------------------------------------------------
263
264U32 SFXVoice::getPosition() const
265{
266   // When stopped, always return 0.
267
268   if( getStatus() == SFXStatusStopped )
269      return 0;
270
271   // It depends on the device if and when it will return a count of the total samples
272   // played so far.  With streaming buffers, all devices will do that.  With non-streaming
273   // buffers, some may do for looping voices thus returning a number that exceeds the actual
274   // source stream size.  So, clamp things into range here and also take care of any offsetting
275   // resulting from a setPosition() call.
276
277   U32 pos = _tell() + mOffset;
278   const U32 numStreamSamples = mBuffer->getFormat().getSampleCount( mBuffer->getDuration() );
279
280   if( mBuffer->mIsLooping )
281      pos %= numStreamSamples;
282   else if( pos > numStreamSamples )
283   {
284      // Ensure we never report out-of-range positions even if the device does.
285
286      pos = numStreamSamples;
287   }
288
289   return pos;
290}
291
292//-----------------------------------------------------------------------------
293
294void SFXVoice::setPosition( U32 inSample )
295{
296   // Clamp to sample range.
297   const U32 sample = inSample % ( mBuffer->getFormat().getSampleCount( mBuffer->getDuration() ) - 1 );
298   
299   // Don't perform a seek when we already are at the
300   // given position.  Especially avoids a costly stream
301   // clone when seeking on a streamed voice.
302   
303   if( getPosition() == sample )
304      return;
305
306   if( !mBuffer->isStreaming() )
307   {
308      // Non-streaming sound.  Just seek in the device buffer.
309
310      _seek( sample );
311   }
312   else
313   {
314      // Streaming sound.  Reset the stream and playback.
315      //
316      // Unfortunately, the logic here is still prone to subtle timing dependencies
317      // in relation to the buffer updates.  In retrospect, I feel that solving all issues
318      // of asynchronous operation on a per-voice/buffer level has greatly complicated
319      // the system.  It seems now that it would have been a lot simpler to have a single
320      // asynchronous buffer/voice manager that manages the updates of all voices and buffers
321      // currently in the system in one spot.  Packet reads could still be pushed out to
322      // the thread pool but queue updates would all be handled centrally in one spot.  This
323      // would do away with problems like those (mostly) solved by the multi-step procedure
324      // here.
325
326      // Go into transition.
327
328      SFXStatus oldStatus;
329      while( true )
330      {
331         oldStatus = mStatus;
332         if( oldStatus != SFXStatusTransition &&
333             dCompareAndSwap( ( U32& ) mStatus, oldStatus, SFXStatusTransition ) )
334            break;
335      }
336
337      // Switch the stream.
338
339      _resetStream( sample, false );
340
341      // Come out of transition.
342
343      SFXStatus newStatus = oldStatus;
344      if( oldStatus == SFXStatusPlaying )
345         newStatus = SFXStatusBlocked;
346
347      dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, newStatus );
348
349      // Trigger an update.
350
351      SFXInternal::TriggerUpdate();
352   }
353}
354
355//-----------------------------------------------------------------------------
356
357void SFXVoice::_resetStream( U32 sampleStartPos, bool triggerUpdate )
358{
359   AssertFatal( mBuffer->isStreaming(), "SFXVoice::_resetStream - Not a streaming voice!" );
360
361   ThreadSafeRef< SFXBuffer::AsyncState> oldState = mBuffer->mAsyncState;
362   AssertFatal( oldState != NULL,
363      "SFXVoice::_resetStream() - streaming buffer must have valid async state" );
364
365   #ifdef DEBUG_SPEW
366   Platform::outputDebugString( "[SFXVoice] Resetting stream to %i", sampleStartPos );
367   #endif
368
369   // Rather than messing up the async code by adding repositioning (which
370   // further complicates synchronizing the various parts), just construct
371   // a complete new AsyncState and discard the old one.  The only problem
372   // here is the stateful sound streams.  We can't issue a new packet as long
373   // as we aren't sure there's no request pending, so we just clone the stream
374   // and leave the old one to the old AsyncState.
375
376   ThreadSafeRef< SFXStream> sfxStream = oldState->mStream->getSourceStream()->clone();
377   if( sfxStream == NULL )
378   {
379      Con::errorf( "SFXVoice::_resetStream - could not clone SFXStream" );
380      return;
381   }
382
383   IPositionable< U32>* sfxPositionable = dynamic_cast< IPositionable< U32>* >( sfxStream.ptr() );
384   if( !sfxPositionable )
385   {
386      Con::errorf( "SFXVoice::_resetStream - could not seek in SFXStream" );
387      return;
388   }
389
390   sfxPositionable->setPosition( sampleStartPos * sfxStream->getFormat().getBytesPerSample() );
391   
392   ThreadSafeRef< SFXInternal::SFXAsyncStream> newStream =
393      new SFXInternal::SFXAsyncStream
394         ( sfxStream,
395           true,
396           oldState->mStream->getPacketDuration() / 1000,
397           oldState->mStream->getReadAhead(),
398           oldState->mStream->isLooping() );
399   newStream->setReadSilenceAtEnd( oldState->mStream->getReadSilenceAtEnd() );
400
401   AssertFatal( newStream->getPacketSize() == oldState->mStream->getPacketSize(),
402      "SFXVoice::setPosition() - packet size mismatch with new stream" );
403
404   ThreadSafeRef< SFXBuffer::AsyncState> newState =
405      new SFXBuffer::AsyncState( newStream );
406   newStream->start();
407
408   // Switch the states.
409
410   mOffset = sampleStartPos;
411   mBuffer->mAsyncState = newState;
412
413   // Stop the old state from reading more data.
414
415   oldState->mStream->stop();
416
417   // Trigger update.
418
419   if( triggerUpdate )
420      SFXInternal::TriggerUpdate();
421}
422