//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _SFXINTERNAL_H_
#define _SFXINTERNAL_H_
#ifndef _THREADPOOL_H_
#include "platform/threads/threadPool.h"
#endif
#ifndef _ASYNCUPDATE_H_
#include "platform/async/asyncUpdate.h"
#endif
#ifndef _ASYNCPACKETSTREAM_H_
#include "platform/async/asyncPacketStream.h"
#endif
#ifndef _ASYNCPACKETQUEUE_H_
#include "platform/async/asyncPacketQueue.h"
#endif
#ifndef _SFXSTREAM_H_
#include "sfx/sfxStream.h"
#endif
#ifndef _SFXBUFFER_H_
#include "sfx/sfxBuffer.h"
#endif
#ifndef _SFXVOICE_H_
#include "sfx/sfxVoice.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
#ifndef _TSINGLETON_H_
#include "core/util/tSingleton.h"
#endif
/// @file
/// Mostly internal definitions for sound stream handling.
/// The code here is used by SFXBuffer for asynchronously loading
/// sample data from sound files, both for streaming buffers
/// as well as for "normal" buffers.
///
/// This is all pretty low-level code here.
namespace SFXInternal {
typedef AsyncUpdateThread SFXUpdateThread;
typedef AsyncUpdateList SFXBufferProcessList;
//--------------------------------------------------------------------------
// Async sound packets.
//--------------------------------------------------------------------------
/// Sound stream packets are raw byte buffers containing PCM sample data.
class SFXStreamPacket : public AsyncPacket< U8 >
{
public:
typedef AsyncPacket< U8 > Parent;
SFXStreamPacket() {}
SFXStreamPacket( U8* data, U32 size, bool ownMemory = false )
: Parent( data, size, ownMemory ) {}
/// The format of the sound samples in the packet.
SFXFormat mFormat;
/// @return the number of samples contained in the packet.
U32 getSampleCount() const { return ( mSizeActual / mFormat.getBytesPerSample() ); }
};
//--------------------------------------------------------------------------
// Async SFXStream I/O.
//--------------------------------------------------------------------------
/// Asynchronous sound data stream that delivers sound data
/// in discrete packets.
class SFXAsyncStream : public AsyncPacketBufferedInputStream< SFXStreamRef, SFXStreamPacket >
{
public:
typedef AsyncPacketBufferedInputStream< SFXStreamRef, SFXStreamPacket > Parent;
enum
{
/// The number of seconds of sample data to load per streaming packet by default.
/// Set this reasonably high to ensure the system is able to cope with latencies
/// in the buffer update chain.
DEFAULT_STREAM_PACKET_LENGTH = 8
};
protected:
/// If true, the stream reads one packet of silence beyond the
/// sound streams actual sound data. This is to avoid wrap-around
/// playback queues running into old data when there is a delay
/// in playback being stopped.
///
/// @note The silence packet is not counting towards stream
/// playback time.
bool mReadSilenceAtEnd;
// AsyncPacketStream.
virtual SFXStreamPacket* _newPacket( U32 packetSize )
{
SFXStreamPacket* packet = Parent::_newPacket( packetSize );
packet->mFormat = getSourceStream()->getFormat();
return packet;
}
virtual void _requestNext();
virtual void _onArrival( SFXStreamPacket* const& packet );
virtual void _newReadItem( PacketReadItemRef& outRef, SFXStreamPacket* packet, U32 numElements )
{
if( !this->mNumRemainingSourceElements && mReadSilenceAtEnd )
packet->mIsLast = false;
Parent::_newReadItem( outRef, packet, numElements );
}
public:
/// Construct a new async sound stream reading data from "stream".
///
/// @param stream The sound data source stream.
/// @param isIncremental If true, "stream" is read in packets of "streamPacketLength" size
/// each; otherwise the stream is read in a single packet containing the entire stream.
/// @param streamPacketLength Seconds of sample data to read per streaming packet. Only
/// relevant if "isIncremental" is true.
/// @param numReadAhead Number of stream packets to read and buffer in advance.
/// @param isLooping If true, the packet stream infinitely loops over "stream".
SFXAsyncStream( const SFXStreamRef& stream,
bool isIncremental,
U32 streamPacketLength = DEFAULT_STREAM_PACKET_LENGTH,
U32 numReadAhead = DEFAULT_STREAM_LOOKAHEAD,
bool isLooping = false );
/// Returns true if the stream will read a packet of silence after the actual sound data.
U32 getReadSilenceAtEnd() const { return mReadSilenceAtEnd; }
/// Set whether the stream should read one packet of silence past the
/// actual sound data. This is useful for situations where continued
/// playback may run into old data.
void setReadSilenceAtEnd( bool value ) { mReadSilenceAtEnd = value; }
/// Return the playback time of a single sound packet in milliseconds.
/// For non-incremental streams, this will be the duration of the
/// entire stream.
U32 getPacketDuration() const
{
const SFXFormat& format = getSourceStream()->getFormat();
return format.getDuration( mPacketSize / format.getBytesPerSample() );
}
};
//--------------------------------------------------------------------------
// Voice time source wrapper.
//--------------------------------------------------------------------------
/// Wrapper around SFXVoice that yields the raw underlying sample position
/// rather than the virtualized position returned by SFXVoice::getPosition().
class SFXVoiceTimeSource
{
public:
typedef void Parent;
protected:
/// The voice to sample the position from.
SFXVoice* mVoice;
/// Last position returned by voice.
mutable U32 mLastPos;
public:
SFXVoiceTimeSource( SFXVoice* voice )
: mVoice( voice ), mLastPos( 0 ) {}
U32 getPosition() const
{
U32 samplePos = mVoice->_tell();
// The device playback cursor may snap back to an undefined value as soon
// as all the data has been consumed. However, for us to be a reliable
// time source, we can't let that happen so as soon as the device play cursor
// goes back to a sample position we have already passed, we start reporting an
// end-of-stream position.
if( samplePos < mLastPos && mVoice->mBuffer != NULL )
samplePos = mVoice->mBuffer->getNumSamples();
else
mLastPos = samplePos;
return samplePos;
}
};
//--------------------------------------------------------------------------
// Async sound packet queue.
//--------------------------------------------------------------------------
/// An async stream queue that writes sound packets to SFXBuffers in sync
/// to the playback of an SFXVoice.
///
/// Sound packet queues use sample counts as tick counts.
class SFXAsyncQueue : public AsyncPacketQueue< SFXStreamPacket*, SFXVoiceTimeSource, SFXBuffer* >
{
public:
typedef AsyncPacketQueue< SFXStreamPacket*, SFXVoiceTimeSource, SFXBuffer* > Parent;
enum
{
/// The number of stream packets that the playback queue for streaming
/// sounds will be sliced into. This should generally be left at
/// three since there is an overhead incurred for each additional
/// segment. Having three segments gives one segment for current
/// immediate playback, one segment as intermediate buffer, and one segment
/// for stream writes.
DEFAULT_STREAM_QUEUE_LENGTH = 3,
};
/// Construct a new sound queue that pushes sound packets to "buffer" in sync
/// to the playback of "voice".
///
/// @param voice The SFXVoice to synchronize to.
/// @param buffer The sound buffer to push sound packets to.
SFXAsyncQueue( SFXVoice* voice,
SFXBuffer* buffer,
bool looping = false )
: Parent( DEFAULT_STREAM_QUEUE_LENGTH,
voice,
buffer,
( looping
? 0
: ( buffer->getDuration() * ( buffer->getFormat().getSamplesPerSecond() / 1000 ) ) - voice->mOffset ) ) {}
};
//--------------------------------------------------------------------------
// SFXBuffer with a wrap-around buffering scheme.
//--------------------------------------------------------------------------
/// Buffer that uses wrap-around packet buffering.
///
/// This class automatically coordinates retrieval and submitting of
/// sound packets and also protects against play cursors running beyond
/// the last packet by making sure some silence is submitted after the
/// last packet (does not count towards playback time).
///
/// @note Note that the reaction times of this class depend on the SFXDevice
/// triggering timely SFXBuffer:update() calls.
class SFXWrapAroundBuffer : public SFXBuffer
{
public:
typedef SFXBuffer Parent;
protected:
/// Absolute byte offset into the sound stream that the next packet write
/// will occur at. This is not an offset into the device buffer
/// in order to allow us to track how far in the source stream we are.
U32 mWriteOffset;
/// Size of the device buffer in bytes.
U32 mBufferSize;
// SFXBuffer.
virtual void _flush()
{
mWriteOffset = 0;
}
/// Copy "length" bytes from "data" into the device at "offset".
virtual bool _copyData( U32 offset, const U8* data, U32 length ) = 0;
// SFXBuffer.
virtual void write( SFXStreamPacket* const* packets, U32 num );
/// @return the sample position in the sound stream as determined from the
/// given buffer offset.
U32 getSamplePos( U32 bufferOffset ) const
{
if( !mBufferSize )
return ( bufferOffset / getFormat().getBytesPerSample() );
const U32 writeOffset = mWriteOffset; // Concurrent writes on this one.
const U32 writeOffsetRelative = writeOffset % mBufferSize;
U32 numBufferedBytes;
if( !writeOffset )
numBufferedBytes = 0;
else if( writeOffsetRelative > bufferOffset )
numBufferedBytes = writeOffsetRelative - bufferOffset;
else
// Wrap-around.
numBufferedBytes = mBufferSize - bufferOffset + writeOffsetRelative;
const U32 bytePos = writeOffset - numBufferedBytes;
return ( bytePos / getFormat().getBytesPerSample() );
}
public:
SFXWrapAroundBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
SFXWrapAroundBuffer( SFXDescription* description )
: Parent( description ), mBufferSize( 0 ), mWriteOffset(0) {}
virtual U32 getMemoryUsed() const { return mBufferSize; }
};
//--------------------------------------------------------------------------
// Global state.
//--------------------------------------------------------------------------
enum
{
/// Soft limit on milliseconds to spend on updating sound buffers
/// when doing buffer updates on the main thread.
MAIN_THREAD_PROCESS_TIMEOUT = 512,
/// Default time interval between periodic sound updates in milliseconds.
/// Only relevant for devices that perform periodic updates.
DEFAULT_UPDATE_INTERVAL = 512,
};
/// Thread pool for sound I/O.
///
/// We are using a separate pool for sound packets in order to be
/// able to submit packet items from different threads. This would
/// violate the invariant of the global thread pool that only the
/// main thread is feeding the queues.
///
/// Note that this also means that only at certain very well-defined
/// points is it possible to safely flush the work item queue on this
/// pool.
///
/// @note Don't use this directly but rather use THREAD_POOL() instead.
/// This way, the sound code may be easily switched to using a common
/// pool later on.
class SFXThreadPool : public ThreadPool, public ManagedSingleton< SFXThreadPool >
{
public:
typedef ThreadPool Parent;
/// Create a ThreadPool called "SFX" with two threads.
SFXThreadPool()
: Parent( "SFX", 2 ) {}
// For ManagedSingleton.
static const char* getSingletonName() { return "SFXThreadPool"; }
};
/// Dedicated thread that does sound buffer updates.
/// May be NULL if sound API used does not do asynchronous buffer
/// updates but rather uses per-frame polling.
///
/// @note SFXDevice automatically polls if this is NULL.
extern ThreadSafeRef< AsyncUpdateThread > gUpdateThread;
/// List of buffers that need updating.
///
/// It depends on the actual device whether this list is processed
/// on a stream update thread or on the main thread.
extern ThreadSafeRef< SFXBufferProcessList > gBufferUpdateList;
/// List of buffers that are pending deletion.
///
/// This is a messy issue. Buffers with live async states cannot be instantly
/// deleted since they may still be running concurrent updates. However, they
/// also cannot be deleted on the update thread since the StrongRefBase stuff
/// isn't thread-safe (i.e weak references kept by client code would cause trouble).
///
/// So, what we do is mark buffers for deletion, wait till they surface on the
/// process list and then ping them back to this list to have them deleted by the
/// SFXDevice itself on the main thread. A bit of overhead but only a fraction of
/// the buffers will ever undergo this procedure.
extern ThreadSafeDeque< SFXBuffer* > gDeadBufferList;
/// Return the thread pool used for SFX work.
inline ThreadPool& THREAD_POOL()
{
return *( SFXThreadPool::instance() );
}
/// Return the dedicated SFX update thread; NULL if updating on the main thread.
inline ThreadSafeRef< SFXUpdateThread > UPDATE_THREAD()
{
return gUpdateThread;
}
/// Return the processing list for SFXBuffers that need updating.
inline SFXBufferProcessList& UPDATE_LIST()
{
return *gBufferUpdateList;
}
/// Trigger an SFX update.
inline bool TriggerUpdate()
{
ThreadSafeRef< SFXUpdateThread > sfxThread = UPDATE_THREAD();
if( sfxThread != NULL )
{
sfxThread->triggerUpdate();
return true;
}
else
return false;
}
/// Delete all buffers currently on the dead buffer list.
inline void PurgeDeadBuffers()
{
SFXBuffer* buffer;
while( gDeadBufferList.tryPopFront( buffer ) )
delete buffer;
}
/// Return true if the current thread is the one responsible for doing SFX updates.
inline bool isSFXThread()
{
ThreadSafeRef< SFXUpdateThread > sfxThread = UPDATE_THREAD();
dsize_t threadId;
if( sfxThread != NULL )
threadId = sfxThread->getId();
else
threadId = ThreadManager::getMainThreadId();
return ThreadManager::compare( ThreadManager::getCurrentThreadId(), threadId );
}
} // namespace SFXInternal
#endif // !_SFXINTERNAL_H_