Torque3D/Engine/source/sfx/sfxBuffer.cpp
2012-09-19 11:15:01 -04:00

292 lines
8.4 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/sfxBuffer.h"
#include "sfx/sfxVoice.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxInternal.h"
//#define DEBUG_SPEW
Signal< void( SFXBuffer* ) > SFXBuffer::smBufferDestroyedSignal;
//-----------------------------------------------------------------------------
SFXBuffer::SFXBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description, bool createAsyncState )
: mStatus( STATUS_Null ),
mIsStreaming( description->mIsStreaming ),
mFormat( stream->getFormat() ),
mDuration( stream->getDuration() ),
mUniqueVoice( NULL ),
mIsDead( false ),
mIsLooping( description->mIsLooping ),
mIsUnique( description->mIsStreaming )
{
using namespace SFXInternal;
if( createAsyncState )
{
U32 packetLength = description->mStreamPacketSize;
U32 readAhead = description->mStreamReadAhead;
ThreadSafeRef< SFXStream > streamRef( stream );
mAsyncState = new AsyncState(
new SFXAsyncStream
( streamRef, mIsStreaming, packetLength, readAhead,
mIsStreaming ? description->mIsLooping : false ) );
}
}
//-----------------------------------------------------------------------------
SFXBuffer::SFXBuffer( SFXDescription* description )
: mStatus( STATUS_Ready ),
mIsStreaming( false ), // Not streaming through our system.
mDuration( 0 ), // Must be set by subclass.
mUniqueVoice( NULL ),
mIsDead( false ),
mIsLooping( description->mIsLooping ),
mIsUnique( false ) // Must be set by subclass.
{
}
//-----------------------------------------------------------------------------
SFXBuffer::~SFXBuffer()
{
smBufferDestroyedSignal.trigger( this );
}
//-----------------------------------------------------------------------------
void SFXBuffer::load()
{
if( getStatus() == STATUS_Null )
{
AssertFatal( mAsyncState != NULL, "SFXBuffer::load() - no async state!" );
_setStatus( STATUS_Loading );
SFXInternal::UPDATE_LIST().add( this );
mAsyncState->mStream->start();
}
}
//-----------------------------------------------------------------------------
bool SFXBuffer::update()
{
using namespace SFXInternal;
if( isDead() )
{
// Buffer requested to finish its async operations.
// Kill our async state and put us on the dead buffer list.
mAsyncState->mStream->stop();
mAsyncState = NULL;
gDeadBufferList.pushFront( this );
return false;
}
else if( isAtEnd() && isStreaming() )
{
// Streaming buffers remain in the update loop even if they
// have played in full to allow for stream seeks.
return true;
}
AssertFatal( mAsyncState != NULL, "SFXBuffer::update() - async state has already been released" );
bool needFurtherUpdates = true;
if( !isStreaming() )
{
// Not a streaming buffer. If the async stream has its data
// ready, we load it and finish up on our async work.
SFXStreamPacket* packet;
while( mAsyncState->mStream->read( &packet, 1 ) )
{
bool isLast = packet->mIsLast;
// Write packet data into buffer.
write( &packet, 1 );
packet = NULL;
if( isLast )
{
// Release async state.
mAsyncState = NULL;
// Once loaded, non-streaming buffers disappear from the SFX
// update thread.
needFurtherUpdates = false;
// Signal that we are ready.
_setStatus( STATUS_Ready );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXBuffer] Buffer ready" );
#endif
break;
}
}
}
else
{
// A streaming buffer.
//
// If we don't have a queue, construct one now. Note that when doing
// a stream seek on us, SFXVoice will drop our async stream and queue.
// Work on local copies of the pointers to allow having the async state
// be switched in parallel.
//
// Note that despite us being a streamed buffer, our unique voice may
// not yet have been assigned to us.
AsyncStatePtr state = mAsyncState;
if( !state->mQueue && !mUniqueVoice.isNull() )
{
// Make sure we have no data currently submitted to the device.
// This will stop and discard an outdated feed if we've been
// switching streams.
_setStatus( STATUS_Loading );
_flush();
// Create a new queue.
state->mQueue = new SFXAsyncQueue( mUniqueVoice, this, mIsLooping );
}
// Check the queue.
if( state->mQueue != NULL )
{
// Feed the queue, if necessary and possible.
while( state->mQueue->needPacket() )
{
// Try to read a packet.
SFXStreamPacket* packet;
if( !state->mStream->read( &packet, 1 ) )
break;
// Submit it.
state->mQueue->submitPacket( packet, packet->getSampleCount() );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXBuffer] Stream packet queued" );
#endif
// Signal that the buffer has data ready.
_setStatus( STATUS_Ready );
}
// Detect buffer underrun and end-of-stream.
if( !isReady() && state->mQueue->isEmpty() )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXBuffer] Stream blocked" );
#endif
_setStatus( STATUS_Blocked );
}
else if( state->mQueue->isAtEnd() )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXBuffer] Stream at end" );
#endif
_setStatus( STATUS_AtEnd );
_flush();
}
}
}
return needFurtherUpdates;
}
//-----------------------------------------------------------------------------
void SFXBuffer::destroySelf()
{
AssertFatal( !isDead(), "SFXBuffer::destroySelf() - buffer already dead" );
mIsDead = true;
if( !mAsyncState )
{
// Easy way. This buffer has finished all its async
// processing, so we can just kill it.
delete this;
}
else
{
// Hard way. We will have to make the buffer finish
// all its concurrent stuff, so we mark it dead, make sure
// to see an update, and then wait for the buffer to surface
// on the dead buffer list.
SFXInternal::TriggerUpdate();
}
}
//-----------------------------------------------------------------------------
void SFXBuffer::_setStatus( Status status )
{
if( mStatus != status )
{
mOnStatusChange.trigger( this, status );
mStatus = status;
}
}
//-----------------------------------------------------------------------------
SFXBuffer::AsyncState::AsyncState()
: mQueue( NULL )
{
}
//-----------------------------------------------------------------------------
SFXBuffer::AsyncState::AsyncState( SFXInternal::SFXAsyncStream* stream )
: mStream( stream ), mQueue( NULL )
{
}
//-----------------------------------------------------------------------------
SFXBuffer::AsyncState::~AsyncState()
{
if( mQueue )
SAFE_DELETE( mQueue );
}