sfxVoice.cpp
Engine/source/sfx/sfxVoice.cpp
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