Torque3D/Engine/source/sfx/sfxVoice.cpp
Azaezel fbfd3ed8ed clang: constructor initialization order
while not a major issue per-se, the sheer number of times the engine has to jump back in memory and backfill data in a given class can add up. First run of... many.,
2016-10-14 18:16:55 -05:00

421 lines
14 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "sfx/sfxVoice.h"
#include "sfx/sfxBuffer.h"
#include "sfx/sfxInternal.h"
#include "console/console.h"
// [rene, 07-May-11] The interplay between SFXBuffer and SFXVoice here isn't good.
// Too complex, and while it works reliably in most cases, when doing seeks
// on streaming sources, it is prone to subtle timing dependencies.
//#define DEBUG_SPEW
Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceCreatedSignal;
Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceDestroyedSignal;
//-----------------------------------------------------------------------------
SFXVoice::SFXVoice( SFXBuffer* buffer )
: mStatus( SFXStatusNull ),
mBuffer( buffer ),
mOffset( 0 )
{
}
//-----------------------------------------------------------------------------
SFXVoice::~SFXVoice()
{
smVoiceDestroyedSignal.trigger( this );
if( mBuffer )
mBuffer->mOnStatusChange.remove( this, &SFXVoice::_onBufferStatusChange );
}
//-----------------------------------------------------------------------------
void SFXVoice::_attachToBuffer()
{
using namespace SFXInternal;
// If the buffer is unique, attach us as its unique voice.
if( mBuffer->isUnique() )
{
AssertFatal( !mBuffer->mUniqueVoice,
"SFXVoice::SFXVoice - streaming buffer already is assigned a voice" );
mBuffer->mUniqueVoice = this;
// The buffer can start its queuing now so give it a chance
// to run an update.
SFXInternal::TriggerUpdate();
}
mBuffer->mOnStatusChange.notify( this, &SFXVoice::_onBufferStatusChange );
smVoiceCreatedSignal.trigger( this );
}
//-----------------------------------------------------------------------------
void SFXVoice::_onBufferStatusChange( SFXBuffer* buffer, SFXBuffer::Status newStatus )
{
AssertFatal( buffer == mBuffer, "SFXVoice::_onBufferStatusChange() - got an invalid buffer" );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Buffer changes status to: %s",
newStatus == SFXBuffer::STATUS_Null ? "STATUS_Null" :
newStatus == SFXBuffer::STATUS_Loading ? "STATUS_Loading" :
newStatus == SFXBuffer::STATUS_Ready ? "STATUS_Ready" :
newStatus == SFXBuffer::STATUS_Blocked ? "STATUS_Blocked" :
newStatus == SFXBuffer::STATUS_AtEnd ? "STATUS_AtEnd" : "unknown" );
#endif
// This is called concurrently!
switch( newStatus )
{
case SFXBuffer::STATUS_Loading:
// Can ignore this. Buffer simply lets us know it has started
// its initial stream load.
break;
case SFXBuffer::STATUS_AtEnd:
// Streaming voice has played to end of stream.
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) )
{
_stop();
mOffset = 0;
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusStopped );
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Voice stopped as end of stream reached" );
#endif
break;
case SFXBuffer::STATUS_Blocked:
// Streaming has fallen behind.
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) )
{
_pause();
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusBlocked );
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Voice waiting for buffer to catch up" );
#endif
break;
case SFXBuffer::STATUS_Ready:
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
{
// Get the playback going again.
_play();
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPlaying );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Buffer caught up with voice" );
#endif
}
break;
case SFXBuffer::STATUS_Null:
AssertFatal( false, "SFXVoice::_onBufferStatusChange - Buffer changed to invalid NULL status" );
break;
}
}
//-----------------------------------------------------------------------------
SFXStatus SFXVoice::getStatus() const
{
// Detect when the device has finished playback. Only for
// non-streaming voices. For streaming voices, we rely on
// the buffer to send us a STATUS_AtEnd signal when it is
// done playing.
if( mStatus == SFXStatusPlaying &&
!mBuffer->isStreaming() &&
_status() == SFXStatusStopped )
mStatus = SFXStatusStopped;
return mStatus;
}
//-----------------------------------------------------------------------------
void SFXVoice::play( bool looping )
{
AssertFatal( mBuffer != NULL, "SFXVoice::play() - no buffer" );
using namespace SFXInternal;
// For streaming, check whether we have played previously.
// If so, reset the buffer's stream.
if( mBuffer->isStreaming() &&
mStatus == SFXStatusStopped )
_resetStream( 0 );
// Now switch state.
while( mStatus != SFXStatusPlaying &&
mStatus != SFXStatusBlocked )
{
if( !mBuffer->isReady() &&
( dCompareAndSwap( ( U32& ) mStatus, SFXStatusNull, SFXStatusBlocked ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusStopped, SFXStatusBlocked ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusBlocked ) ) )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Wanted to start playback but buffer isn't ready" );
#endif
break;
}
else if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusNull, SFXStatusTransition ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusStopped, SFXStatusTransition ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusTransition ) )
{
_play();
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPlaying );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Started playback" );
#endif
break;
}
}
}
//-----------------------------------------------------------------------------
void SFXVoice::pause()
{
while( mStatus != SFXStatusPaused &&
mStatus != SFXStatusNull &&
mStatus != SFXStatusStopped )
{
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
{
_pause();
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPaused );
break;
}
}
}
//-----------------------------------------------------------------------------
void SFXVoice::stop()
{
while( mStatus != SFXStatusStopped &&
mStatus != SFXStatusNull )
{
if( dCompareAndSwap( ( U32& ) mStatus, ( U32 ) SFXStatusPlaying, ( U32 ) SFXStatusTransition ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusTransition ) ||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
{
_stop();
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusStopped );
break;
}
}
}
//-----------------------------------------------------------------------------
U32 SFXVoice::getPosition() const
{
// When stopped, always return 0.
if( getStatus() == SFXStatusStopped )
return 0;
// It depends on the device if and when it will return a count of the total samples
// played so far. With streaming buffers, all devices will do that. With non-streaming
// buffers, some may do for looping voices thus returning a number that exceeds the actual
// source stream size. So, clamp things into range here and also take care of any offsetting
// resulting from a setPosition() call.
U32 pos = _tell() + mOffset;
const U32 numStreamSamples = mBuffer->getFormat().getSampleCount( mBuffer->getDuration() );
if( mBuffer->mIsLooping )
pos %= numStreamSamples;
else if( pos > numStreamSamples )
{
// Ensure we never report out-of-range positions even if the device does.
pos = numStreamSamples;
}
return pos;
}
//-----------------------------------------------------------------------------
void SFXVoice::setPosition( U32 inSample )
{
// Clamp to sample range.
const U32 sample = inSample % ( mBuffer->getFormat().getSampleCount( mBuffer->getDuration() ) - 1 );
// Don't perform a seek when we already are at the
// given position. Especially avoids a costly stream
// clone when seeking on a streamed voice.
if( getPosition() == sample )
return;
if( !mBuffer->isStreaming() )
{
// Non-streaming sound. Just seek in the device buffer.
_seek( sample );
}
else
{
// Streaming sound. Reset the stream and playback.
//
// Unfortunately, the logic here is still prone to subtle timing dependencies
// in relation to the buffer updates. In retrospect, I feel that solving all issues
// of asynchronous operation on a per-voice/buffer level has greatly complicated
// the system. It seems now that it would have been a lot simpler to have a single
// asynchronous buffer/voice manager that manages the updates of all voices and buffers
// currently in the system in one spot. Packet reads could still be pushed out to
// the thread pool but queue updates would all be handled centrally in one spot. This
// would do away with problems like those (mostly) solved by the multi-step procedure
// here.
// Go into transition.
SFXStatus oldStatus;
while( true )
{
oldStatus = mStatus;
if( oldStatus != SFXStatusTransition &&
dCompareAndSwap( ( U32& ) mStatus, oldStatus, SFXStatusTransition ) )
break;
}
// Switch the stream.
_resetStream( sample, false );
// Come out of transition.
SFXStatus newStatus = oldStatus;
if( oldStatus == SFXStatusPlaying )
newStatus = SFXStatusBlocked;
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, newStatus );
// Trigger an update.
SFXInternal::TriggerUpdate();
}
}
//-----------------------------------------------------------------------------
void SFXVoice::_resetStream( U32 sampleStartPos, bool triggerUpdate )
{
AssertFatal( mBuffer->isStreaming(), "SFXVoice::_resetStream - Not a streaming voice!" );
ThreadSafeRef< SFXBuffer::AsyncState > oldState = mBuffer->mAsyncState;
AssertFatal( oldState != NULL,
"SFXVoice::_resetStream() - streaming buffer must have valid async state" );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXVoice] Resetting stream to %i", sampleStartPos );
#endif
// Rather than messing up the async code by adding repositioning (which
// further complicates synchronizing the various parts), just construct
// a complete new AsyncState and discard the old one. The only problem
// here is the stateful sound streams. We can't issue a new packet as long
// as we aren't sure there's no request pending, so we just clone the stream
// and leave the old one to the old AsyncState.
ThreadSafeRef< SFXStream > sfxStream = oldState->mStream->getSourceStream()->clone();
if( sfxStream == NULL )
{
Con::errorf( "SFXVoice::_resetStream - could not clone SFXStream" );
return;
}
IPositionable< U32 >* sfxPositionable = dynamic_cast< IPositionable< U32 >* >( sfxStream.ptr() );
if( !sfxPositionable )
{
Con::errorf( "SFXVoice::_resetStream - could not seek in SFXStream" );
return;
}
sfxPositionable->setPosition( sampleStartPos * sfxStream->getFormat().getBytesPerSample() );
ThreadSafeRef< SFXInternal::SFXAsyncStream > newStream =
new SFXInternal::SFXAsyncStream
( sfxStream,
true,
oldState->mStream->getPacketDuration() / 1000,
oldState->mStream->getReadAhead(),
oldState->mStream->isLooping() );
newStream->setReadSilenceAtEnd( oldState->mStream->getReadSilenceAtEnd() );
AssertFatal( newStream->getPacketSize() == oldState->mStream->getPacketSize(),
"SFXVoice::setPosition() - packet size mismatch with new stream" );
ThreadSafeRef< SFXBuffer::AsyncState > newState =
new SFXBuffer::AsyncState( newStream );
newStream->start();
// Switch the states.
mOffset = sampleStartPos;
mBuffer->mAsyncState = newState;
// Stop the old state from reading more data.
oldState->mStream->stop();
// Trigger update.
if( triggerUpdate )
SFXInternal::TriggerUpdate();
}