mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-03-06 14:00:39 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
420
Engine/source/sfx/sfxVoice.cpp
Normal file
420
Engine/source/sfx/sfxVoice.cpp
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 )
|
||||
: mBuffer( buffer ),
|
||||
mStatus( SFXStatusNull ),
|
||||
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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue