Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,27 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// The various functions we need to grab from DSound.dll
DS_FUNCTION( DirectSoundEnumerateA, HRESULT, (LPDSENUMCALLBACKA pDSEnumCallback, LPVOID pContext) )
DS_FUNCTION( DirectSoundEnumerateW, HRESULT, (LPDSENUMCALLBACKW pDSEnumCallback, LPVOID pContext) )
DS_FUNCTION( DirectSoundCreate8, HRESULT, (LPCGUID pcGuidDevice, LPDIRECTSOUND8 *ppDS8, LPUNKNOWN pUnkOuter) )

View file

@ -0,0 +1,277 @@
//-----------------------------------------------------------------------------
// 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/dsound/sfxDSBuffer.h"
#include "sfx/sfxStream.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxInternal.h"
#include "platform/async/asyncUpdate.h"
#include "core/util/safeRelease.h"
#include "core/util/safeCast.h"
SFXDSBuffer* SFXDSBuffer::create( IDirectSound8 *dsound,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware )
{
AssertFatal( dsound, "SFXDSBuffer::create() - Got null dsound!" );
AssertFatal( stream, "SFXDSBuffer::create() - Got a null stream!" );
AssertFatal( description, "SFXDSBuffer::create() - Got a null description" );
SFXDSBuffer* buffer = new SFXDSBuffer( dsound,
stream,
description,
useHardware );
if( !buffer->_createBuffer( &buffer->mBuffer ) )
SAFE_DELETE( buffer );
return buffer;
}
SFXDSBuffer::SFXDSBuffer( IDirectSound8* dsound,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware )
: Parent( stream, description ),
mDSound( dsound ),
mIs3d( description->mIs3D ),
mUseHardware( useHardware ),
mBuffer( NULL ),
mDuplicate( false )
{
AssertFatal( mDSound, "SFXDSBuffer::SFXDSBuffer() - Got null dsound!" );
mDSound->AddRef();
}
SFXDSBuffer::~SFXDSBuffer()
{
SAFE_RELEASE( mBuffer );
SAFE_RELEASE( mDSound );
}
bool SFXDSBuffer::_createBuffer( IDirectSoundBuffer8 **buffer8 )
{
AssertFatal( mAsyncState != NULL,
"SFXDSBuffer::_createBuffer() - Can't create buffer when not connected to stream!" );
const SFXFormat& format = getFormat();
// Set up WAV format structure.
WAVEFORMATEX wfx;
dMemset( &wfx, 0, sizeof( WAVEFORMATEX ) );
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = format.getChannels();
wfx.nSamplesPerSec = format.getSamplesPerSecond();
wfx.wBitsPerSample = format.getBitsPerChannel();
wfx.nBlockAlign = wfx.nChannels * wfx.wBitsPerSample / 8;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
// Set up DSBUFFERDESC structure.
DSBUFFERDESC dsbdesc;
dMemset( &dsbdesc, 0, sizeof( DSBUFFERDESC ) );
dsbdesc.dwSize = sizeof( DSBUFFERDESC );
dsbdesc.dwFlags =
( mIs3d ? DSBCAPS_CTRL3D | DSBCAPS_MUTE3DATMAXDISTANCE : DSBCAPS_CTRLPAN ) |
( isStreaming() ? DSBCAPS_CTRLPOSITIONNOTIFY : 0 ) |
DSBCAPS_CTRLFREQUENCY |
DSBCAPS_CTRLVOLUME |
DSBCAPS_GETCURRENTPOSITION2 |
DSBCAPS_GLOBALFOCUS |
DSBCAPS_STATIC |
( mUseHardware ? DSBCAPS_LOCHARDWARE : DSBCAPS_LOCSOFTWARE );
dsbdesc.dwBufferBytes = mBufferSize;
if ( mIs3d )
dsbdesc.guid3DAlgorithm = DS3DALG_HRTF_FULL;
dsbdesc.lpwfxFormat = &wfx;
// Create the buffer.
IDirectSoundBuffer *buffer = NULL;
HRESULT hr = mDSound->CreateSoundBuffer( &dsbdesc, &buffer, NULL );
if ( FAILED( hr ) || !buffer )
return false;
// Grab the version 8 interface.
IDirectSoundBuffer8* buffer8Ptr;
hr = buffer->QueryInterface( IID_IDirectSoundBuffer8, ( LPVOID* ) &buffer8Ptr );
// Release the original interface.
buffer->Release();
// If we failed to get the 8 interface... exit.
if ( FAILED( hr ) || !buffer8Ptr )
return false;
// Set up notification positions, if this is a streaming buffer.
if( isStreaming() )
{
using namespace SFXInternal;
const U32 maxQueuedPackets = SFXAsyncQueue::DEFAULT_STREAM_QUEUE_LENGTH;
const U32 packetSize = mAsyncState->mStream->getPacketSize();
LPDIRECTSOUNDNOTIFY8 lpDsNotify;
if( FAILED( hr = buffer8Ptr->QueryInterface( IID_IDirectSoundNotify8, ( LPVOID* ) &lpDsNotify ) ) )
{
SAFE_RELEASE( buffer8Ptr );
return false;
}
const U32 numNotifies = maxQueuedPackets + 1; // Add one for end-of-stream notification pos.
DSBPOSITIONNOTIFY* dsbNotifyPos =
( DSBPOSITIONNOTIFY* ) _alloca( numNotifies * sizeof( DSBPOSITIONNOTIFY ) );
// Events seem to be triggered way too early by DS causing the playback queues to
// reject updates, so we nudge the update markers "somewhat" to the right here.
// This value here is based on experimentation. No harm should result if we don't
// hit it other than updates happening in sub-optimal timing.
enum { OFFSET_DELTA = 5000 };
U32 offset = ( packetSize + OFFSET_DELTA ) % mBufferSize;
HANDLE updateEvent = ( HANDLE ) UPDATE_THREAD()->getUpdateEvent();
for( U32 i = 0; i < maxQueuedPackets; ++ i, offset = ( offset + packetSize ) % mBufferSize )
{
dsbNotifyPos[ i ].dwOffset = offset;
dsbNotifyPos[ i ].hEventNotify = updateEvent;
}
// A end-of-stream notification position.
//FIXME: this position will start to be wrong when doing stream seeks
dsbNotifyPos[ numNotifies - 1 ].dwOffset = ( format.getDataLength( getDuration() ) + OFFSET_DELTA ) % mBufferSize;
dsbNotifyPos[ numNotifies - 1 ].hEventNotify = updateEvent;
// Install the notifications.
lpDsNotify->SetNotificationPositions( numNotifies, dsbNotifyPos );
SAFE_RELEASE( lpDsNotify );
// Don't need to notify on stop as when playback is stopped,
// the packet buffers will just fill up and stop updating
// when saturated.
}
*buffer8 = buffer8Ptr;
return true;
}
bool SFXDSBuffer::_copyData( U32 offset,
const U8 *data,
U32 length )
{
AssertFatal( mBuffer, "SFXDSBuffer::_copyData() - no buffer" );
// Fill the buffer with the resource data.
VOID* lpvWrite;
DWORD dwLength;
VOID* lpvWrite2;
DWORD dwLength2;
HRESULT hr = mBuffer->Lock(
offset, // Offset at which to start lock.
length, // Size of lock.
&lpvWrite, // Gets address of first part of lock.
&dwLength, // Gets size of first part of lock.
&lpvWrite2, // Address of wraparound not needed.
&dwLength2, // Size of wraparound not needed.
0 );
if ( FAILED( hr ) )
return false;
// Copy the first part.
dMemcpy( lpvWrite, data, dwLength );
// Do we have a wrap?
if ( lpvWrite2 )
dMemcpy( lpvWrite2, data + dwLength, dwLength2 );
// And finally, unlock.
hr = mBuffer->Unlock(
lpvWrite, // Address of lock start.
dwLength, // Size of lock.
lpvWrite2, // No wraparound portion.
dwLength2 ); // No wraparound size.
// Return success code.
return SUCCEEDED(hr);
}
void SFXDSBuffer::_flush()
{
AssertFatal( isStreaming(), "SFXDSBuffer::_flush() - not a streaming buffer" );
AssertFatal( SFXInternal::isSFXThread(), "SFXDSBuffer::_flush() - not on SFX thread" );
Parent::_flush();
mBuffer->SetCurrentPosition( 0 );
}
bool SFXDSBuffer::_duplicateBuffer( IDirectSoundBuffer8 **buffer8 )
{
AssertFatal( mBuffer, "SFXDSBuffer::_duplicateBuffer() - Duplicate buffer is null!" );
// If this is the first duplicate then
// give the caller our original buffer.
if ( !mDuplicate )
{
mDuplicate = true;
*buffer8 = mBuffer;
(*buffer8)->AddRef();
return true;
}
IDirectSoundBuffer *buffer1 = NULL;
HRESULT hr = mDSound->DuplicateSoundBuffer( mBuffer, &buffer1 );
if ( FAILED( hr ) || !buffer1 )
return false;
// Grab the version 8 interface.
hr = buffer1->QueryInterface( IID_IDirectSoundBuffer8, (LPVOID*)buffer8 );
// Release the original interface.
buffer1->Release();
return SUCCEEDED( hr ) && (*buffer8);
}
bool SFXDSBuffer::createVoice( IDirectSoundBuffer8 **buffer8 )
{
return ( mBuffer && _duplicateBuffer( buffer8 ) && *buffer8 );
}
void SFXDSBuffer::releaseVoice( IDirectSoundBuffer8 **buffer )
{
AssertFatal( *buffer, "SFXDSBuffer::releaseVoice() - Got null buffer!" );
if ( *buffer == mBuffer )
{
mDuplicate = false;
(*buffer)->Stop();
}
SAFE_RELEASE( (*buffer) );
}

View file

@ -0,0 +1,101 @@
//-----------------------------------------------------------------------------
// 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 _SFXDSBUFFER_H_
#define _SFXDSBUFFER_H_
#include <dsound.h>
#ifndef _SFXINTERNAL_H_
# include "sfx/sfxInternal.h"
#endif
/// DirectSound SFXBuffer implementation.
///
/// Note that the actual sound buffer held by the buffer may
/// get duplicated around for individual voices. This is kind
/// of ugly as the resulting buffers aren't tied to a SFXDSBuffer
/// anymore.
class SFXDSBuffer : public SFXInternal::SFXWrapAroundBuffer
{
typedef SFXInternal::SFXWrapAroundBuffer Parent;
friend class SFXDSDevice;
friend class SFXDSVoice;
protected:
///
bool mIs3d;
///
bool mUseHardware;
IDirectSound8 *mDSound;
/// The buffer used when duplication is allowed.
IDirectSoundBuffer8 *mBuffer;
/// We set this to true when the original buffer has
/// been handed out and duplicates need to be made.
bool mDuplicate;
///
SFXDSBuffer( IDirectSound8 *dsound,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware );
virtual ~SFXDSBuffer();
/// Set up a DirectSound buffer.
/// @note This method will not fill the buffer with data.
/// @note If this is a streaming buffer, the resulting buffer
/// will have its notification positions set up and already
/// be registered with SFXDSStreamThread.
bool _createBuffer( IDirectSoundBuffer8 **buffer8 );
///
bool _duplicateBuffer( IDirectSoundBuffer8 **buffer8 );
// SFXWrapAroundBuffer.
virtual bool _copyData( U32 offset, const U8* data, U32 length );
virtual void _flush();
public:
///
static SFXDSBuffer* create( IDirectSound8* dsound,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware );
//
bool createVoice( IDirectSoundBuffer8 **buffer );
//
void releaseVoice( IDirectSoundBuffer8 **buffer );
};
#endif // _SFXDSBUFFER_H_

View file

@ -0,0 +1,229 @@
//-----------------------------------------------------------------------------
// 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/dsound/sfxDSDevice.h"
#include "sfx/dsound/sfxDSBuffer.h"
#include "sfx/dsound/sfxDSVoice.h"
#include "platformWin32/platformWin32.h"
#include "core/util/safeRelease.h"
#include "platform/async/asyncUpdate.h"
#include "console/console.h"
SFXDSDevice::SFXDSDevice( SFXProvider* provider,
DSoundFNTable *dsFnTbl,
GUID* guid,
String name,
bool useHardware,
S32 maxBuffers )
: SFXDevice( name, provider, useHardware, maxBuffers ),
mDSound( NULL ),
mPrimaryBuffer( NULL ),
mListener( NULL ),
mDSoundTbl( dsFnTbl ),
mGUID( guid )
{
}
bool SFXDSDevice::_init()
{
HRESULT hr = mDSoundTbl->DirectSoundCreate8( mGUID, &mDSound, NULL );
if ( FAILED( hr ) || !mDSound )
{
Con::errorf( "SFXDSDevice::SFXDSDevice() - DirectSoundCreate8 failed" );
return false;
}
hr = mDSound->SetCooperativeLevel( getWin32WindowHandle(), DSSCL_PRIORITY );
if ( FAILED( hr ) )
{
Con::errorf( "SFXDSDevice::SFXDSDevice() - SetCooperativeLevel failed" );
return false;
}
// Get the primary buffer.
DSBUFFERDESC dsbd;
dMemset( &dsbd, 0, sizeof( DSBUFFERDESC ) );
dsbd.dwSize = sizeof( DSBUFFERDESC );
dsbd.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;
hr = mDSound->CreateSoundBuffer( &dsbd, &mPrimaryBuffer, NULL );
if ( FAILED( hr ) )
{
Con::errorf( "SFXDSDevice::SFXDSDevice - Creating primary sound buffer failed" );
return false;
}
// Set the format and bitrate on the primary buffer.
S32 frequency = Con::getIntVariable( "$pref::SFX::frequency", 44100 );
S32 bitrate = Con::getIntVariable( "$pref::SFX::bitrate", 32 );
WAVEFORMATEX wfx;
dMemset( &wfx, 0, sizeof( WAVEFORMATEX ) );
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = frequency;
wfx.wBitsPerSample = bitrate;
wfx.nBlockAlign = ( wfx.nChannels * wfx.wBitsPerSample ) / 8;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
hr = mPrimaryBuffer->SetFormat( &wfx );
if( FAILED( hr ) )
{
Con::errorf( "SFXDSDevice::SFXDSDevice() - Setting format of primary buffer failed" );
return false;
}
// Grab the 3d listener.
hr = mPrimaryBuffer->QueryInterface( IID_IDirectSound3DListener8, (LPVOID*)&mListener );
if ( FAILED( hr ) )
{
Con::errorf( "SFXDSDevice::SFXDevice() - Querying the listener interface failed!" );
mListener = NULL;
}
mCaps.dwSize = sizeof( DSCAPS );
mDSound->GetCaps( &mCaps );
// If the device reports no hardware buffers then
// we have no choice but to disable hardware.
if ( mCaps.dwMaxHw3DAllBuffers == 0 )
mUseHardware = false;
// If mMaxBuffers is negative then use the caps
// to decide on a good maximum value... or set 8.
if ( mMaxBuffers < 0 )
mMaxBuffers = getMax( mCaps.dwMaxHw3DAllBuffers, 8 );
// Start the stream thread.
if( !Con::getBoolVariable( "$_forceAllMainThread" ) )
{
SFXInternal::gUpdateThread =
new AsyncUpdateThread( "DirectSound Update Thread", SFXInternal::gBufferUpdateList );
SFXInternal::gUpdateThread->start();
}
return true;
}
SFXDSDevice::~SFXDSDevice()
{
// And release our resources.
SAFE_RELEASE( mListener );
SAFE_RELEASE( mPrimaryBuffer );
SAFE_RELEASE( mDSound );
}
SFXBuffer* SFXDSDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
{
AssertFatal( stream, "SFXDSDevice::createBuffer() - Got null stream!" );
AssertFatal( description, "SFXDSDevice::createBuffer() - Got null description!" );
SFXDSBuffer* buffer = SFXDSBuffer::create( mDSound,
stream,
description,
mUseHardware );
if( buffer )
_addBuffer( buffer );
return buffer;
}
SFXVoice* SFXDSDevice::createVoice( bool is3D, SFXBuffer *buffer )
{
// Don't bother going any further if we've
// exceeded the maximum voices.
if ( mVoices.size() >= mMaxBuffers )
return NULL;
AssertFatal( buffer, "SFXDSDevice::createVoice() - Got null buffer!" );
SFXDSBuffer* dsBuffer = dynamic_cast<SFXDSBuffer*>( buffer );
AssertFatal( dsBuffer, "SFXDSDevice::createVoice() - Got bad buffer!" );
SFXDSVoice* voice = SFXDSVoice::create( this, dsBuffer );
if ( !voice )
return NULL;
_addVoice( voice );
return voice;
}
void SFXDSDevice::_commitDeferred()
{
if( mListener )
mListener->CommitDeferredSettings();
}
void SFXDSDevice::update()
{
Parent::update();
// Apply the deferred settings that changed between updates.
mListener->CommitDeferredSettings();
}
void SFXDSDevice::setListener( U32 index, const SFXListenerProperties& listener )
{
// Get the transform from the listener.
const MatrixF& transform = listener.getTransform();
Point3F pos, dir, up;
transform.getColumn( 3, &pos );
transform.getColumn( 1, &dir );
transform.getColumn( 2, &up );
// And the velocity...
const VectorF& velocity = listener.getVelocity();
// Finally, set it all to DSound!
mListener->SetPosition( pos.x, pos.z, pos.y, DS3D_DEFERRED );
mListener->SetOrientation( dir.x, dir.z, dir.y, up.x, up.z, up.y, DS3D_DEFERRED );
mListener->SetVelocity( velocity.x, velocity.z, velocity.y, DS3D_DEFERRED );
}
void SFXDSDevice::setDistanceModel( SFXDistanceModel model )
{
switch( model )
{
case SFXDistanceModelLinear:
Con::errorf( "SFXDSDevice::setDistanceModel - 'linear' distance attenuation not supported by DirectSound" );
break;
case SFXDistanceModelLogarithmic:
break; // Nothing to do.
default:
AssertWarn( false, "SFXDSDevice::setDistanceModel() - model not implemented" );
}
}
void SFXDSDevice::setDopplerFactor( F32 factor )
{
if( mListener )
mListener->SetDopplerFactor( factor, DS3D_DEFERRED ); // Committed in update.
}
void SFXDSDevice::setRolloffFactor( F32 factor )
{
if( mListener )
mListener->SetRolloffFactor( factor, DS3D_DEFERRED ); // Committed in update.
}

View file

@ -0,0 +1,142 @@
//-----------------------------------------------------------------------------
// 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 _SFXDSDEVICE_H_
#define _SFXDSDEVICE_H_
#ifndef _STRINGFUNCTIONS_H_
# include "core/strings/stringFunctions.h"
#endif
#ifndef _SFXDEVICE_H_
#include "sfx/sfxDevice.h"
#endif
#ifndef _SFXDSVOICE_H_
#include "sfx/dsound/sfxDSVoice.h"
#endif
#ifndef OS_DLIBRARY_H
#include "platform/platformDlibrary.h"
#endif
#include <dsound.h>
// Typedefs
#define DS_FUNCTION(fn_name, fn_return, fn_args) \
typedef fn_return (WINAPI *DSFNPTR##fn_name##)##fn_args##;
#include "sfx/dsound/dsFunctions.h"
#undef DS_FUNCTION
// Function table
struct DSoundFNTable
{
DSoundFNTable() : isLoaded( false ) {};
bool isLoaded;
DLibraryRef dllRef;
#define DS_FUNCTION(fn_name, fn_return, fn_args) \
DSFNPTR##fn_name fn_name;
#include "sfx/dsound/dsFunctions.h"
#undef DS_FUNCTION
};
/// Helper for asserting on dsound HRESULTS.
inline void DSAssert( HRESULT hr, const char *info )
{
#ifdef TORQUE_DEBUG
if( FAILED( hr ) )
{
char buf[256];
dSprintf( buf, 256, "Error code: %x\n%s", hr, info );
AssertFatal( false, buf );
}
#endif
}
/// The DirectSound device implementation exposes a couple
/// of settings to script that you should be aware of:
///
/// $DirectSound::dopplerFactor - This controls the scale of
/// the doppler effect. Valid factors are 0.0 to 10.0 and it
/// defaults to 0.75.
///
/// $DirectSound::distanceFactor - This sets the unit conversion
/// for
///
/// $DirectSound::rolloffFactor - ;
///
///
class SFXDSDevice : public SFXDevice
{
typedef SFXDevice Parent;
friend class SFXDSVoice;
friend class SFXDSProvider; // _init
public:
//explicit SFXDSDevice();
SFXDSDevice( SFXProvider* provider,
DSoundFNTable *dsFnTbl,
GUID* guid,
String name,
bool useHardware,
S32 maxBuffers );
virtual ~SFXDSDevice();
protected:
IDirectSound8 *mDSound;
IDirectSound3DListener8 *mListener;
IDirectSoundBuffer *mPrimaryBuffer;
DSoundFNTable *mDSoundTbl;
DSCAPS mCaps;
GUID* mGUID;
bool _init();
/// Called from SFXDSVoice to commit any deferred
/// settings before playback begins.
void _commitDeferred();
public:
// SFXDevice
virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer );
virtual void update();
virtual void setListener( U32 index, const SFXListenerProperties& listener );
virtual void setDistanceModel( SFXDistanceModel mode );
virtual void setDopplerFactor( F32 factor );
virtual void setRolloffFactor( F32 factor );
};
#endif // _SFXDSDEVICE_H_

View file

@ -0,0 +1,198 @@
//-----------------------------------------------------------------------------
// 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/sfxProvider.h"
#include "sfx/dsound/sfxDSDevice.h"
#include "core/util/safeRelease.h"
#include "console/console.h"
#include "core/strings/unicode.h"
#include "core/util/safeDelete.h"
#include "core/module.h"
class SFXDSProvider : public SFXProvider
{
public:
SFXDSProvider()
: SFXProvider( "DirectSound" ) {}
virtual ~SFXDSProvider();
void init();
protected:
DSoundFNTable mDSound;
struct DSDeviceInfo : SFXDeviceInfo
{
GUID* guid;
DSCAPS caps;
};
static BOOL CALLBACK dsEnumProc(
LPGUID lpGUID,
LPCTSTR lpszDesc,
LPCTSTR lpszDrvName,
LPVOID lpContext );
void addDeviceDesc( GUID* guid, const String& name, const String& desc );
public:
SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers );
};
MODULE_BEGIN( DirectSound )
MODULE_INIT_BEFORE( SFX )
MODULE_SHUTDOWN_AFTER( SFX )
SFXDSProvider* mProvider;
MODULE_INIT
{
mProvider = new SFXDSProvider;
}
MODULE_SHUTDOWN
{
delete mProvider;
}
MODULE_END;
//------------------------------------------------------------------------------
// Helper
bool dsBindFunction( DLibrary *dll, void *&fnAddress, const char *name )
{
fnAddress = dll->bind( name );
if (!fnAddress)
Con::warnf( "DSound Loader: DLL bind failed for %s", name );
return fnAddress != 0;
}
//------------------------------------------------------------------------------
SFXDSProvider::~SFXDSProvider()
{
}
void SFXDSProvider::init()
{
// Grab the functions we'll want from the dsound DLL.
mDSound.dllRef = OsLoadLibrary( "dsound.dll" );
mDSound.isLoaded = true;
#define DS_FUNCTION(fn_name, fn_return, fn_args) \
mDSound.isLoaded &= dsBindFunction(mDSound.dllRef, *(void**)&mDSound.fn_name, #fn_name);
#include "sfx/dsound/dsFunctions.h"
#undef DS_FUNCTION
AssertISV( mDSound.isLoaded, "DirectSound failed to load." );
// All we need to do to init is enumerate the
// devices... if this fails then don't register
// the provider as it's broken in some way.
if ( FAILED( mDSound.DirectSoundEnumerate( dsEnumProc, (VOID*)this ) ) )
{
Con::errorf( "SFXDSProvider - Device enumeration failed!" );
return;
}
// Did we get any devices?
if ( mDeviceInfo.empty() )
{
Con::errorf( "SFXDSProvider - No valid devices found!" );
return;
}
// Wow, we made it - register the provider.
regProvider( this );
}
BOOL CALLBACK SFXDSProvider::dsEnumProc(
LPGUID lpGUID,
LPCTSTR lpszDesc,
LPCTSTR lpszDrvName,
LPVOID lpContext )
{
SFXDSProvider* provider = (SFXDSProvider*)lpContext;
provider->addDeviceDesc( lpGUID, lpszDrvName, lpszDesc );
return TRUE;
}
void SFXDSProvider::addDeviceDesc( GUID* guid, const String& name, const String& desc )
{
// Create a dummy device to get the caps.
IDirectSound8* dsound;
HRESULT hr = mDSound.DirectSoundCreate8( guid, &dsound, NULL );
if ( FAILED( hr ) || !dsound )
return;
// Init the caps structure and have the device fill it out.
DSCAPS caps;
dMemset( &caps, 0, sizeof( caps ) );
caps.dwSize = sizeof( caps );
hr = dsound->GetCaps( &caps );
// Clean up and handle errors.
SAFE_RELEASE( dsound );
if ( FAILED( hr ) )
return;
// Now, record the desc info into our own internal list.
DSDeviceInfo* info = new DSDeviceInfo;
info->name = desc;
info->driver = name;
info->hasHardware = caps.dwMaxHw3DAllBuffers > 0;
info->maxBuffers = caps.dwMaxHw3DAllBuffers;
info->guid = guid;
info->caps = caps;
mDeviceInfo.push_back( info );
}
SFXDevice* SFXDSProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers )
{
DSDeviceInfo* info = dynamic_cast< DSDeviceInfo* >
( _findDeviceInfo( deviceName ) );
if( !info )
return NULL;
SFXDSDevice* device = new SFXDSDevice( this,
&mDSound,
info->guid,
info->name,
useHardware,
maxBuffers );
if( !device->_init() )
SAFE_DELETE( device );
return device;
}

View file

@ -0,0 +1,229 @@
//-----------------------------------------------------------------------------
// 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/dsound/sfxDSVoice.h"
#include "sfx/dsound/sfxDSDevice.h"
#include "core/util/safeRelease.h"
SFXDSVoice* SFXDSVoice::create( SFXDSDevice *device, SFXDSBuffer *buffer )
{
AssertFatal( buffer, "SFXDSVoice::create() - Got null buffer!" );
IDirectSoundBuffer8 *dsBuffer8 = NULL;
if ( !buffer->createVoice( &dsBuffer8 ) || !dsBuffer8 )
return NULL;
// Now try to grab a 3D interface... if we don't
// get one its probably because its not a 3d sound.
IDirectSound3DBuffer8* dsBuffer3d8 = NULL;
dsBuffer8->QueryInterface( IID_IDirectSound3DBuffer8, (LPVOID*)&dsBuffer3d8 );
// Create the voice and return!
SFXDSVoice* voice = new SFXDSVoice( device,
buffer,
dsBuffer8,
dsBuffer3d8 );
// Now set the voice to a default state.
// The buffer from which we have duplicated may have been assigned different
// properties and we don't want to inherit these.
voice->setVolume( 1.0 );
voice->setPitch( 1.0 );
return voice;
}
SFXDSVoice::SFXDSVoice( SFXDSDevice *device,
SFXDSBuffer *buffer,
IDirectSoundBuffer8 *dsBuffer,
IDirectSound3DBuffer8 *dsBuffer3d )
: Parent( buffer ),
mDevice( device ),
mDSBuffer( dsBuffer ),
mDSBuffer3D( dsBuffer3d ),
mIsLooping( false )
{
AssertFatal( mDevice, "SFXDSVoice::SFXDSVoice() - SFXDSDevice is null!" );
AssertFatal( mBuffer, "SFXDSVoice::SFXDSVoice() - SFXDSBuffer is null!" );
AssertFatal( mDSBuffer, "SFXDSVoice::SFXDSVoice() - Dsound buffer is null!" );
}
SFXDSVoice::~SFXDSVoice()
{
SAFE_RELEASE( mDSBuffer3D );
SFXDSBuffer* dsBuffer = _getBuffer();
if( dsBuffer )
dsBuffer->releaseVoice( &mDSBuffer );
mBuffer = NULL;
}
SFXStatus SFXDSVoice::_status() const
{
DWORD status = 0;
mDSBuffer->GetStatus( &status );
if ( status & DSBSTATUS_PLAYING )
return SFXStatusPlaying;
else
return SFXStatusStopped;
}
void SFXDSVoice::_play()
{
DSAssert( mDSBuffer->Play( 0, 0, mIsLooping ? DSBPLAY_LOOPING : 0 ),
"SFXDSVoice::_play() - Playback failed!" );
}
void SFXDSVoice::_stop()
{
DSAssert( mDSBuffer->Stop(), "SFXDSVoice::pause - stop failed!" );
mDSBuffer->SetCurrentPosition( 0 );
}
void SFXDSVoice::_pause()
{
DSAssert( mDSBuffer->Stop(), "SFXDSVoice::pause - stop failed!" );
}
void SFXDSVoice::_seek( U32 sample )
{
U32 pos = mBuffer->getFormat().getBytesPerSample() * sample;
mDSBuffer->SetCurrentPosition( pos );
}
U32 SFXDSVoice::_tell() const
{
DWORD position = 0;
mDSBuffer->GetCurrentPosition( &position, NULL );
U32 samplePos = _getBuffer()->getSamplePos( position );
return samplePos;
}
void SFXDSVoice::setMinMaxDistance( F32 min, F32 max )
{
if ( !mDSBuffer3D )
return;
mDSBuffer3D->SetMinDistance( min, DS3D_DEFERRED );
mDSBuffer3D->SetMaxDistance( max, DS3D_DEFERRED );
}
void SFXDSVoice::play( bool looping )
{
// If this is a 3d sound then we need
// to commit any deferred settings before
// we start playback else we can get some
// glitches.
if ( mDSBuffer3D )
mDevice->_commitDeferred();
// If this is a streaming buffer,
// force looping.
const bool isStreaming = mBuffer->isStreaming();
if( isStreaming )
looping = true;
mIsLooping = looping;
Parent::play( looping );
}
void SFXDSVoice::setVelocity( const VectorF& velocity )
{
if ( !mDSBuffer3D )
return;
DSAssert( mDSBuffer3D->SetVelocity( velocity.x, velocity.z, velocity.y, DS3D_DEFERRED ),
"SFXDSVoice::setVelocity - couldn't update buffer!" );
}
void SFXDSVoice::setTransform( const MatrixF& transform )
{
if ( !mDSBuffer3D )
return;
Point3F pos, dir;
transform.getColumn( 3, &pos );
transform.getColumn( 1, &dir );
DSAssert( mDSBuffer3D->SetPosition( pos.x, pos.z, pos.y, DS3D_DEFERRED ),
"SFXDSVoice::setTransform - couldn't set position of the buffer." );
DSAssert( mDSBuffer3D->SetConeOrientation( dir.x, dir.z, dir.y, DS3D_DEFERRED ),
"SFXDSVoice::setTransform - couldn't set cone orientation of the buffer." );
}
/// Helper for converting floating point linear volume
/// to a logrithmic integer volume for dsound.
LONG SFXDSVoice::_linearToLogVolume( F32 linVolume )
{
LONG logVolume;
if ( linVolume <= 0.0f )
logVolume = DSBVOLUME_MIN;
else
{
logVolume = -2000.0 * mLog( 1.0f / linVolume );
logVolume = mClamp( logVolume, DSBVOLUME_MIN, DSBVOLUME_MAX );
}
return logVolume;
}
void SFXDSVoice::setVolume( F32 volume )
{
LONG logVolume = _linearToLogVolume( volume );
HRESULT hr = mDSBuffer->SetVolume( logVolume );
DSAssert( hr, "SFXDSVoice::setVolume - couldn't set volume!" );
}
void SFXDSVoice::setPitch( F32 pitch )
{
F32 sampleRate = _getBuffer()->getFormat().getSamplesPerSecond();
F32 frequency = mFloor( mClampF( sampleRate * pitch, DSBFREQUENCY_MIN, DSBFREQUENCY_MAX ) );
DSAssert( mDSBuffer->SetFrequency( ( U32 )frequency ),
"SFXDSVoice::setPitch - couldn't set playback frequency.");
}
void SFXDSVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume )
{
if ( !mDSBuffer3D )
return;
DSAssert( mDSBuffer3D->SetConeAngles( innerAngle,
outerAngle,
DS3D_DEFERRED ),
"SFXDSVoice::setCone - couldn't set cone angles!" );
LONG logVolume = _linearToLogVolume( outerVolume );
DSAssert( mDSBuffer3D->SetConeOutsideVolume( logVolume,
DS3D_DEFERRED ),
"SFXDSVoice::setCone - couldn't set cone outside volume!" );
}

View file

@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// 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 _SFXDSVOICE_H_
#define _SFXDSVOICE_H_
#ifndef _SFXVOICE_H_
#include "sfx/sfxVoice.h"
#endif
#ifndef _SFXDSBUFFER_H_
#include "sfx/dsound/sfxDSBuffer.h"
#endif
#include <dsound.h>
class SFXDSDevice;
class SFXDSVoice : public SFXVoice
{
typedef SFXVoice Parent;
protected:
SFXDSVoice( SFXDSDevice *device,
SFXDSBuffer *buffer,
IDirectSoundBuffer8 *dsBuffer,
IDirectSound3DBuffer8 *dsBuffer3d );
/// The device used to commit deferred settings.
SFXDSDevice *mDevice;
IDirectSoundBuffer8 *mDSBuffer;
IDirectSound3DBuffer8 *mDSBuffer3D;
bool mIsLooping;
SFXDSBuffer* _getBuffer() const { return ( SFXDSBuffer* ) mBuffer.getPointer(); }
/// Helper for converting floating point linear volume
/// to a logrithmic integer volume for dsound.
static LONG _linearToLogVolume( F32 linVolume );
// SFXVoice
virtual SFXStatus _status() const;
virtual void _play();
virtual void _pause();
virtual void _stop();
virtual void _seek( U32 sample );
virtual U32 _tell() const;
public:
///
static SFXDSVoice* create( SFXDSDevice *device,
SFXDSBuffer *buffer );
///
virtual ~SFXDSVoice();
// SFXVoice
void setMinMaxDistance( F32 min, F32 max );
void play( bool looping );
void setVelocity( const VectorF& velocity );
void setTransform( const MatrixF& transform );
void setVolume( F32 volume );
void setPitch( F32 pitch );
void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
};
#endif // _SFXDSBUFFER_H_

View file

@ -0,0 +1,115 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
FMOD_ERROR( FMOD_OK )
FMOD_ERROR( FMOD_ERR_ALREADYLOCKED )
FMOD_ERROR( FMOD_ERR_BADCOMMAND )
FMOD_ERROR( FMOD_ERR_CDDA_DRIVERS )
FMOD_ERROR( FMOD_ERR_CDDA_INIT )
FMOD_ERROR( FMOD_ERR_CDDA_INVALID_DEVICE )
FMOD_ERROR( FMOD_ERR_CDDA_NOAUDIO )
FMOD_ERROR( FMOD_ERR_CDDA_NODEVICES )
FMOD_ERROR( FMOD_ERR_CDDA_NODISC )
FMOD_ERROR( FMOD_ERR_CDDA_READ )
FMOD_ERROR( FMOD_ERR_CHANNEL_ALLOC )
FMOD_ERROR( FMOD_ERR_CHANNEL_STOLEN )
FMOD_ERROR( FMOD_ERR_COM )
FMOD_ERROR( FMOD_ERR_DMA )
FMOD_ERROR( FMOD_ERR_DSP_CONNECTION )
FMOD_ERROR( FMOD_ERR_DSP_FORMAT )
FMOD_ERROR( FMOD_ERR_DSP_NOTFOUND )
FMOD_ERROR( FMOD_ERR_DSP_RUNNING )
FMOD_ERROR( FMOD_ERR_DSP_TOOMANYCONNECTIONS )
FMOD_ERROR( FMOD_ERR_FILE_BAD )
FMOD_ERROR( FMOD_ERR_FILE_COULDNOTSEEK )
FMOD_ERROR( FMOD_ERR_FILE_DISKEJECTED )
FMOD_ERROR( FMOD_ERR_FILE_EOF )
FMOD_ERROR( FMOD_ERR_FILE_NOTFOUND )
FMOD_ERROR( FMOD_ERR_FILE_UNWANTED )
FMOD_ERROR( FMOD_ERR_FORMAT )
FMOD_ERROR( FMOD_ERR_HTTP )
FMOD_ERROR( FMOD_ERR_HTTP_ACCESS )
FMOD_ERROR( FMOD_ERR_HTTP_PROXY_AUTH )
FMOD_ERROR( FMOD_ERR_HTTP_SERVER_ERROR )
FMOD_ERROR( FMOD_ERR_HTTP_TIMEOUT )
FMOD_ERROR( FMOD_ERR_INITIALIZATION )
FMOD_ERROR( FMOD_ERR_INITIALIZED )
FMOD_ERROR( FMOD_ERR_INTERNAL )
FMOD_ERROR( FMOD_ERR_INVALID_ADDRESS )
FMOD_ERROR( FMOD_ERR_INVALID_FLOAT )
FMOD_ERROR( FMOD_ERR_INVALID_HANDLE )
FMOD_ERROR( FMOD_ERR_INVALID_PARAM )
FMOD_ERROR( FMOD_ERR_INVALID_POSITION )
FMOD_ERROR( FMOD_ERR_INVALID_SPEAKER )
FMOD_ERROR( FMOD_ERR_INVALID_SYNCPOINT )
FMOD_ERROR( FMOD_ERR_INVALID_VECTOR )
FMOD_ERROR( FMOD_ERR_MAXAUDIBLE )
FMOD_ERROR( FMOD_ERR_MEMORY )
FMOD_ERROR( FMOD_ERR_MEMORY_CANTPOINT )
FMOD_ERROR( FMOD_ERR_MEMORY_SRAM )
FMOD_ERROR( FMOD_ERR_NEEDS2D )
FMOD_ERROR( FMOD_ERR_NEEDS3D )
FMOD_ERROR( FMOD_ERR_NEEDSHARDWARE )
FMOD_ERROR( FMOD_ERR_NEEDSSOFTWARE )
FMOD_ERROR( FMOD_ERR_NET_CONNECT )
FMOD_ERROR( FMOD_ERR_NET_SOCKET_ERROR )
FMOD_ERROR( FMOD_ERR_NET_URL )
FMOD_ERROR( FMOD_ERR_NET_WOULD_BLOCK )
FMOD_ERROR( FMOD_ERR_NOTREADY )
FMOD_ERROR( FMOD_ERR_OUTPUT_ALLOCATED )
FMOD_ERROR( FMOD_ERR_OUTPUT_CREATEBUFFER )
FMOD_ERROR( FMOD_ERR_OUTPUT_DRIVERCALL )
FMOD_ERROR( FMOD_ERR_OUTPUT_ENUMERATION )
FMOD_ERROR( FMOD_ERR_OUTPUT_FORMAT )
FMOD_ERROR( FMOD_ERR_OUTPUT_INIT )
FMOD_ERROR( FMOD_ERR_OUTPUT_NOHARDWARE )
FMOD_ERROR( FMOD_ERR_OUTPUT_NOSOFTWARE )
FMOD_ERROR( FMOD_ERR_PAN )
FMOD_ERROR( FMOD_ERR_PLUGIN )
FMOD_ERROR( FMOD_ERR_PLUGIN_INSTANCES )
FMOD_ERROR( FMOD_ERR_PLUGIN_MISSING )
FMOD_ERROR( FMOD_ERR_PLUGIN_RESOURCE )
FMOD_ERROR( FMOD_ERR_RECORD )
FMOD_ERROR( FMOD_ERR_REVERB_INSTANCE )
FMOD_ERROR( FMOD_ERR_SUBSOUND_ALLOCATED )
FMOD_ERROR( FMOD_ERR_SUBSOUND_CANTMOVE )
FMOD_ERROR( FMOD_ERR_SUBSOUND_MODE )
FMOD_ERROR( FMOD_ERR_SUBSOUNDS )
FMOD_ERROR( FMOD_ERR_TAGNOTFOUND )
FMOD_ERROR( FMOD_ERR_TOOMANYCHANNELS )
FMOD_ERROR( FMOD_ERR_UNIMPLEMENTED )
FMOD_ERROR( FMOD_ERR_UNINITIALIZED )
FMOD_ERROR( FMOD_ERR_UNSUPPORTED )
FMOD_ERROR( FMOD_ERR_UPDATE )
FMOD_ERROR( FMOD_ERR_VERSION )
FMOD_ERROR( FMOD_ERR_PRELOADED )
FMOD_ERROR( FMOD_ERR_EVENT_FAILED )
FMOD_ERROR( FMOD_ERR_EVENT_INFOONLY )
FMOD_ERROR( FMOD_ERR_EVENT_INTERNAL )
FMOD_ERROR( FMOD_ERR_EVENT_MAXSTREAMS )
FMOD_ERROR( FMOD_ERR_EVENT_MISMATCH )
FMOD_ERROR( FMOD_ERR_EVENT_NAMECONFLICT )
FMOD_ERROR( FMOD_ERR_EVENT_NOTFOUND )
FMOD_ERROR( FMOD_ERR_EVENT_NEEDSSIMPLE )
FMOD_ERROR( FMOD_ERR_EVENT_GUIDCONFLICT )
FMOD_ERROR( FMOD_ERR_EVENT_ALREADY_LOADED )
FMOD_ERROR( FMOD_ERR_MUSIC_UNINITIALIZED )

View file

@ -0,0 +1,157 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// Xcode has problems properly detecting the dependencies for this file.
// If you change something here, manually recompile all the FMOD modules.
// FMOD Ex API:
FMOD_FUNCTION( FMOD_Channel_GetPaused, (FMOD_CHANNEL *channel, FMOD_BOOL *paused))
FMOD_FUNCTION( FMOD_Channel_SetPaused, (FMOD_CHANNEL *channel, FMOD_BOOL paused));
FMOD_FUNCTION( FMOD_Channel_IsPlaying, (FMOD_CHANNEL *channel, FMOD_BOOL *isplaying))
FMOD_FUNCTION( FMOD_Channel_Set3DAttributes, (FMOD_CHANNEL *channel, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel))
FMOD_FUNCTION( FMOD_Channel_SetFrequency, (FMOD_CHANNEL *channel, float frequency))
FMOD_FUNCTION( FMOD_Channel_SetLoopCount, (FMOD_CHANNEL *channel, int loopcount))
FMOD_FUNCTION( FMOD_Channel_SetPosition, (FMOD_CHANNEL *channel, unsigned int position, FMOD_TIMEUNIT postype))
FMOD_FUNCTION( FMOD_Channel_GetPosition, (FMOD_CHANNEL* channel, unsigned int* position, FMOD_TIMEUNIT postype))
FMOD_FUNCTION( FMOD_Channel_SetVolume, (FMOD_CHANNEL *channel, float volume))
FMOD_FUNCTION( FMOD_Channel_GetVolume, (FMOD_CHANNEL *channel, float *volume))
FMOD_FUNCTION( FMOD_Channel_Stop, (FMOD_CHANNEL *channel))
FMOD_FUNCTION( FMOD_Channel_SetMode, (FMOD_CHANNEL *channel, FMOD_MODE mode))
FMOD_FUNCTION( FMOD_Channel_Set3DMinMaxDistance, (FMOD_CHANNEL *channel, float mindistance, float maxdistance));
FMOD_FUNCTION( FMOD_Channel_Set3DConeSettings, (FMOD_CHANNEL *channel, float insideconeangle, float outsideconeangle, float outsidevolume))
FMOD_FUNCTION( FMOD_Channel_Set3DConeOrientation, (FMOD_CHANNEL *channel, FMOD_VECTOR *orientation))
FMOD_FUNCTION( FMOD_Channel_SetReverbProperties, ( FMOD_CHANNEL* channel, const FMOD_REVERB_CHANNELPROPERTIES* prop ) )
FMOD_FUNCTION( FMOD_Channel_SetPriority, ( FMOD_CHANNEL* channel, int priority ) )
FMOD_FUNCTION( FMOD_Channel_IsVirtual, ( FMOD_CHANNEL* channel, FMOD_BOOL* isvirtual ) )
FMOD_FUNCTION( FMOD_Channel_AddDSP, ( FMOD_CHANNEL* channel, FMOD_DSP* dsp, FMOD_DSPCONNECTION** connection ) )
FMOD_FUNCTION( FMOD_Sound_Lock, (FMOD_SOUND *sound, unsigned int offset, unsigned int length, void **ptr1, void **ptr2, unsigned int *len1, unsigned int *len2))
FMOD_FUNCTION( FMOD_Sound_Unlock, (FMOD_SOUND *sound, void *ptr1, void *ptr2, unsigned int len1, unsigned int len2))
FMOD_FUNCTION( FMOD_Sound_Release, (FMOD_SOUND *sound))
FMOD_FUNCTION( FMOD_Sound_Set3DMinMaxDistance, (FMOD_SOUND *sound, float min, float max))
FMOD_FUNCTION( FMOD_Sound_SetMode, (FMOD_SOUND *sound, FMOD_MODE mode))
FMOD_FUNCTION( FMOD_Sound_GetMode, (FMOD_SOUND *sound, FMOD_MODE *mode))
FMOD_FUNCTION( FMOD_Sound_SetLoopCount, (FMOD_SOUND *sound, int loopcount))
FMOD_FUNCTION( FMOD_Sound_GetFormat, ( FMOD_SOUND* sound, FMOD_SOUND_TYPE* type, FMOD_SOUND_FORMAT* format, int* channels, int* bits ) )
FMOD_FUNCTION( FMOD_Sound_GetLength, ( FMOD_SOUND* sound, unsigned int* length, FMOD_TIMEUNIT lengthtype ) )
FMOD_FUNCTION( FMOD_Sound_GetDefaults, ( FMOD_SOUND* sound, float* frequency, float* volume, float* pan, int* priority ) )
FMOD_FUNCTION( FMOD_Sound_GetMemoryInfo, ( FMOD_SOUND* sound, unsigned int memorybits, unsigned int event_memorybits, unsigned int* memoryused, unsigned int* memoryused_array ) );
FMOD_FUNCTION( FMOD_Geometry_AddPolygon, ( FMOD_GEOMETRY* geometry, float directocclusion, float reverbocclusion, FMOD_BOOL doublesided, int numvertices, const FMOD_VECTOR* vertices, int* polygonindex ) )
FMOD_FUNCTION( FMOD_Geometry_SetPosition, ( FMOD_GEOMETRY* geometry, const FMOD_VECTOR* position ) )
FMOD_FUNCTION( FMOD_Geometry_SetRotation, ( FMOD_GEOMETRY* geometry, const FMOD_VECTOR* forward, const FMOD_VECTOR* up ) )
FMOD_FUNCTION( FMOD_Geometry_SetScale, ( FMOD_GEOMETRY* geometry, const FMOD_VECTOR* scale ) )
FMOD_FUNCTION( FMOD_Geometry_Release, ( FMOD_GEOMETRY* geometry ) )
FMOD_FUNCTION( FMOD_DSP_GetInfo, ( FMOD_DSP* dsp, char* name, unsigned int* version, int* channels, int* configwidth, int* configheight ) )
FMOD_FUNCTION( FMOD_DSP_Release, ( FMOD_DSP* dsp ) )
FMOD_FUNCTION( FMOD_DSP_GetParameterInfo, ( FMOD_DSP* dsp, int index, char* name, char* label, char* description, int descriptionlen, float* min, float* max ) )
FMOD_FUNCTION( FMOD_DSP_GetNumParameters, ( FMOD_DSP* dsp, int* numparams ) )
FMOD_FUNCTION( FMOD_DSP_GetParameter, ( FMOD_DSP* dsp, int index, float* value, char* valuestr, int valuestrlen ) )
FMOD_FUNCTION( FMOD_DSP_SetParameter, ( FMOD_DSP* dsp, int index, float value ) )
FMOD_FUNCTION( FMOD_DSP_GetNumInputs, ( FMOD_DSP* dsp, int* numinputs ) )
FMOD_FUNCTION( FMOD_DSP_GetNumOutputs, ( FMOD_DSP* dsp, int* numoutputs ) )
FMOD_FUNCTION( FMOD_System_Close, (FMOD_SYSTEM *system))
FMOD_FUNCTION( FMOD_System_Create, (FMOD_SYSTEM **system))
FMOD_FUNCTION( FMOD_System_Release, (FMOD_SYSTEM *system))
FMOD_FUNCTION( FMOD_System_CreateSound, (FMOD_SYSTEM *system, const char *name_or_data, FMOD_MODE mode, FMOD_CREATESOUNDEXINFO *exinfo, FMOD_SOUND **sound))
FMOD_FUNCTION( FMOD_System_CreateGeometry, ( FMOD_SYSTEM* system, int maxpolygons, int maxvertices, FMOD_GEOMETRY** geometry ) )
FMOD_FUNCTION( FMOD_System_SetDriver, (FMOD_SYSTEM *system, int driver))
FMOD_FUNCTION( FMOD_System_GetDriverCaps, (FMOD_SYSTEM *system, int id, FMOD_CAPS *caps, int *controlpaneloutputrate, FMOD_SPEAKERMODE *controlpanelspeakermode))
FMOD_FUNCTION( FMOD_System_GetDriverInfo, (FMOD_SYSTEM *system, int id, char *name, int namelen, FMOD_GUID *GUID))
FMOD_FUNCTION( FMOD_System_GetNumDrivers, (FMOD_SYSTEM *system, int *numdrivers))
FMOD_FUNCTION( FMOD_System_GetVersion, (FMOD_SYSTEM *system, unsigned int *version))
FMOD_FUNCTION( FMOD_System_Init, (FMOD_SYSTEM *system, int maxchannels, FMOD_INITFLAGS flags, void *extradriverdata))
FMOD_FUNCTION( FMOD_System_PlaySound, (FMOD_SYSTEM *system, FMOD_CHANNELINDEX channelid, FMOD_SOUND *sound, FMOD_BOOL paused, FMOD_CHANNEL **channel))
FMOD_FUNCTION( FMOD_System_Set3DListenerAttributes, (FMOD_SYSTEM *system, int listener, const FMOD_VECTOR *pos, const FMOD_VECTOR *vel, const FMOD_VECTOR *forward, const FMOD_VECTOR *up))
FMOD_FUNCTION( FMOD_System_Set3DNumListeners, ( FMOD_SYSTEM* system, int numlisteners ) )
FMOD_FUNCTION( FMOD_System_SetGeometrySettings, ( FMOD_SYSTEM* system, float maxworldsize ) )
FMOD_FUNCTION( FMOD_System_Get3DSettings, (FMOD_SYSTEM* system, float* dopplerFactor, float* distanceFactor, float* rolloffFactor))
FMOD_FUNCTION( FMOD_System_Set3DSettings, (FMOD_SYSTEM *system, float dopplerscale, float distancefactor, float rolloffscale))
FMOD_FUNCTION( FMOD_System_SetDSPBufferSize, (FMOD_SYSTEM *system, unsigned int bufferlength, int numbuffers))
FMOD_FUNCTION( FMOD_System_SetSpeakerMode, (FMOD_SYSTEM *system, FMOD_SPEAKERMODE speakermode))
FMOD_FUNCTION( FMOD_System_SetReverbProperties, ( FMOD_SYSTEM* system, const FMOD_REVERB_PROPERTIES* prop ) )
FMOD_FUNCTION( FMOD_System_SetReverbAmbientProperties, ( FMOD_SYSTEM* system, FMOD_REVERB_PROPERTIES* prop ) )
FMOD_FUNCTION( FMOD_System_Update, ( FMOD_SYSTEM *system ) )
FMOD_FUNCTION( FMOD_System_CreateDSPByType, ( FMOD_SYSTEM* system, FMOD_DSP_TYPE type, FMOD_DSP** dsp ) )
FMOD_FUNCTION( FMOD_System_AddDSP, ( FMOD_SYSTEM* system, FMOD_DSP* dsp, FMOD_DSPCONNECTION** connection ) )
FMOD_FUNCTION( FMOD_System_GetMemoryInfo, ( FMOD_SYSTEM* system, unsigned int memorybits, unsigned int event_memorybits, unsigned int* memoryused, unsigned int* memoryused_array ) )
FMOD_FUNCTION( FMOD_System_SetFileSystem, ( FMOD_SYSTEM* system, FMOD_FILE_OPENCALLBACK useropen, FMOD_FILE_CLOSECALLBACK userclose, FMOD_FILE_READCALLBACK userread, FMOD_FILE_SEEKCALLBACK userseek, int blockalign ) )
FMOD_FUNCTION( FMOD_System_SetPluginPath, ( FMOD_SYSTEM* system, const char* path ) )
FMOD_FUNCTION( FMOD_System_GetHardwareChannels, ( FMOD_SYSTEM* system, int* num2d, int* num3d, int* total ) )
FMOD_FUNCTION( FMOD_Memory_GetStats, ( int*, int* ) )
FMOD_FUNCTION( FMOD_Memory_Initialize, ( void *poolmem, int poollen, FMOD_MEMORY_ALLOCCALLBACK useralloc, FMOD_MEMORY_REALLOCCALLBACK userrealloc, FMOD_MEMORY_FREECALLBACK userfree ) )
// FMOD Designer API:
FMOD_EVENT_FUNCTION( FMOD_EventSystem_Create, ( FMOD_EVENTSYSTEM** eventsystem ), "_FMOD_EventSystem_Create@4" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_GetSystemObject, ( FMOD_EVENTSYSTEM* eventsystem, FMOD_SYSTEM** system ), "_FMOD_EventSystem_GetSystemObject@8" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_GetVersion, ( FMOD_EVENTSYSTEM* eventsystem, unsigned int* version ), "_FMOD_EventSystem_GetVersion@8" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_Init, ( FMOD_EVENTSYSTEM* eventsystem, int maxchannels, FMOD_INITFLAGS flags, void* extradriverdata, FMOD_EVENT_INITFLAGS eventflags ), "_FMOD_EventSystem_Init@20" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_Release, ( FMOD_EVENTSYSTEM* eventsystem ), "_FMOD_EventSystem_Release@4" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_Load, ( FMOD_EVENTSYSTEM* eventsystem, const char* name_or_data, FMOD_EVENT_LOADINFO* loadinfo, FMOD_EVENTPROJECT** project ), "_FMOD_EventSystem_Load@16" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_Update, ( FMOD_EVENTSYSTEM* eventsystem ), "_FMOD_EventSystem_Update@4" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_GetMemoryInfo, ( FMOD_EVENTSYSTEM* eventsystem, unsigned int memorybits, unsigned int event_memorybits, unsigned int* memoryused, unsigned int* memoryused_array ), "_FMOD_EventSystem_GetMemoryInfo@20" )
FMOD_EVENT_FUNCTION( FMOD_EventSystem_SetMediaPath, ( FMOD_EVENTSYSTEM* eventsystem, const char* path ), "_FMOD_EventSystem_SetMediaPath@8" )
FMOD_EVENT_FUNCTION( FMOD_EventProject_Release, ( FMOD_EVENTPROJECT* eventproject ), "_FMOD_EventProject_Release@4" )
FMOD_EVENT_FUNCTION( FMOD_EventProject_GetInfo, ( FMOD_EVENTPROJECT* eventproject, FMOD_EVENT_PROJECTINFO* info ), "_FMOD_EventProject_GetInfo@8" )
FMOD_EVENT_FUNCTION( FMOD_EventProject_GetNumEvents, ( FMOD_EVENTPROJECT* eventproject, int* numevents ), "_FMOD_EventProject_GetNumEvents@8" )
FMOD_EVENT_FUNCTION( FMOD_EventProject_GetNumGroups, ( FMOD_EVENTPROJECT* eventproject, int* numgroups ), "_FMOD_EventProject_GetNumGroups@8" )
FMOD_EVENT_FUNCTION( FMOD_EventProject_GetGroupByIndex, ( FMOD_EVENTPROJECT* eventproject, int index, FMOD_BOOL cacheevents, FMOD_EVENTGROUP** group ), "_FMOD_EventProject_GetGroupByIndex@16" )
FMOD_EVENT_FUNCTION( FMOD_EventProject_GetGroup, ( FMOD_EVENTPROJECT* eventproject, const char* name, FMOD_BOOL cacheevents, FMOD_EVENTGROUP** group ), "_FMOD_EventProject_GetGroup@16" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetInfo, ( FMOD_EVENTGROUP* eventgroup, int* index, char** name ), "_FMOD_EventGroup_GetInfo@12" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_LoadEventData, ( FMOD_EVENTGROUP* eventgroup, FMOD_EVENT_RESOURCE resource, FMOD_EVENT_MODE mode ), "_FMOD_EventGroup_LoadEventData@12" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_FreeEventData, ( FMOD_EVENTGROUP* eventgroup, FMOD_EVENT* event, FMOD_BOOL waituntilready ), "_FMOD_EventGroup_FreeEventData@12" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetNumEvents, ( FMOD_EVENTGROUP* eventgroup, int* numevents ), "_FMOD_EventGroup_GetNumEvents@8" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetNumGroups, ( FMOD_EVENTGROUP* eventgroup, int* numgroups ), "_FMOD_EventGroup_GetNumGroups@8" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetEventByIndex, ( FMOD_EVENTGROUP* eventgroup, int index, FMOD_EVENT_MODE mode, FMOD_EVENT** event ), "_FMOD_EventGroup_GetEventByIndex@16" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetEvent, ( FMOD_EVENTGROUP* eventgroup, const char* name, FMOD_EVENT_MODE mode, FMOD_EVENT** event ), "_FMOD_EventGroup_GetEvent@16" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetGroupByIndex, ( FMOD_EVENTGROUP* eventgroup, int index, FMOD_BOOL cacheevents, FMOD_EVENTGROUP** group ), "_FMOD_EventGroup_GetGroupByIndex@16" )
FMOD_EVENT_FUNCTION( FMOD_EventGroup_GetGroup, ( FMOD_EVENTGROUP* eventgroup, const char* name, FMOD_BOOL cacheevents, FMOD_EVENTGROUP** group ), "_FMOD_EventGroup_GetGroup@16" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetInfo, ( FMOD_EVENT* event, int* index, char** name, FMOD_EVENT_INFO* info ), "_FMOD_Event_GetInfo@16" )
FMOD_EVENT_FUNCTION( FMOD_Event_Release, ( FMOD_EVENT* event, FMOD_BOOL freeeventdata, FMOD_BOOL waituntilready ), "_FMOD_Event_Release@12" )
FMOD_EVENT_FUNCTION( FMOD_Event_Start, ( FMOD_EVENT* event ), "_FMOD_Event_Start@4" )
FMOD_EVENT_FUNCTION( FMOD_Event_Stop, ( FMOD_EVENT* event, FMOD_BOOL immediate ), "_FMOD_Event_Stop@8" )
FMOD_EVENT_FUNCTION( FMOD_Event_SetPaused, ( FMOD_EVENT* event, FMOD_BOOL paused ), "_FMOD_Event_SetPaused@8" )
FMOD_EVENT_FUNCTION( FMOD_Event_SetVolume, ( FMOD_EVENT* event, float volume ), "_FMOD_Event_SetVolume@8" )
FMOD_EVENT_FUNCTION( FMOD_Event_SetPitch, ( FMOD_EVENT* event, float pitch, FMOD_EVENT_PITCHUNITS units ), "_FMOD_Event_SetPitch@12" )
FMOD_EVENT_FUNCTION( FMOD_Event_Set3DAttributes, ( FMOD_EVENT* event, const FMOD_VECTOR* position, const FMOD_VECTOR* velocity, const FMOD_VECTOR* orientation ), "_FMOD_Event_Set3DAttributes@16" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetState, ( FMOD_EVENT* event, FMOD_EVENT_STATE* state ), "_FMOD_Event_GetState@8" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetNumParameters, ( FMOD_EVENT* event, int* numparameters ), "_FMOD_Event_GetNumParameters@8" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetParameter, ( FMOD_EVENT* event, const char* name, FMOD_EVENTPARAMETER** parameter ), "_FMOD_Event_GetParameter@12" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetParameterByIndex, ( FMOD_EVENT* event, int index, FMOD_EVENTPARAMETER** parameter ), "_FMOD_Event_GetParameterByIndex@12" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetPropertyByIndex, ( FMOD_EVENT* event, int propertyidex, void* value, FMOD_BOOL this_instance ), "_FMOD_Event_GetPropertyByIndex@16" )
FMOD_EVENT_FUNCTION( FMOD_Event_SetPropertyByIndex, ( FMOD_EVENT* event, int propertyidex, void* value, FMOD_BOOL this_instance ), "_FMOD_Event_SetPropertyByIndex@16" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetProperty, ( FMOD_EVENT* event, const char* propertyname, void* value, FMOD_BOOL this_instance ), "_FMOD_Event_GetProperty@16" )
FMOD_EVENT_FUNCTION( FMOD_Event_GetPropertyInfo, ( FMOD_EVENT* event, int* propertyindex, char** propertyname, FMOD_EVENTPROPERTY_TYPE* type ), "_FMOD_Event_GetPropertyInfo@16" )
FMOD_EVENT_FUNCTION( FMOD_EventParameter_GetInfo, ( FMOD_EVENTPARAMETER* eventparameter, int* index, char** name ), "_FMOD_EventParameter_GetInfo@12" )
FMOD_EVENT_FUNCTION( FMOD_EventParameter_GetValue, ( FMOD_EVENTPARAMETER* eventparameter, float* value ), "_FMOD_EventParameter_GetValue@8" )
FMOD_EVENT_FUNCTION( FMOD_EventParameter_SetValue, ( FMOD_EVENTPARAMETER* eventparameter, float value ), "_FMOD_EventParameter_SetValue@8" )
FMOD_EVENT_FUNCTION( FMOD_EventParameter_GetRange, ( FMOD_EVENTPARAMETER* eventparameter, float* rangemin, float* rangemax ), "_FMOD_EventParameter_GetRange@12" )

View file

@ -0,0 +1,322 @@
//-----------------------------------------------------------------------------
// 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/fmod/sfxFMODBuffer.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "sfx/sfxDescription.h"
#include "core/util/safeDelete.h"
#include "core/volume.h"
//-----------------------------------------------------------------------------
static const char* sExtensions[] =
{
"", // First try without doing anything with the given path.
"", // Then try it without an extension but by expanding it through Torque::FS.
"aiff",
"asf",
"asx",
"dls",
"flac",
"fsb",
"it",
"m3u",
"mid",
"mod",
"mp2",
"mp3",
"ogg",
"pls",
"s3m",
"vag",
"wav",
"wax",
"wma",
"xm",
#ifdef TORQUE_OS_XENON
".xma",
#endif
NULL
};
//-----------------------------------------------------------------------------
SFXFMODBuffer* SFXFMODBuffer::create( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
{
SFXFMODBuffer *buffer = new SFXFMODBuffer( stream, description );
if( !buffer->mSound )
SAFE_DELETE( buffer );
return buffer;
}
//-----------------------------------------------------------------------------
SFXFMODBuffer* SFXFMODBuffer::create( const String& filename, SFXDescription* description )
{
if( Con::getBoolVariable( "$pref::SFX::FMOD::noCustomFileLoading", false ) )
return NULL;
SFXFMODBuffer *buffer = new SFXFMODBuffer( filename, description );
if( !buffer->mSound )
SAFE_DELETE( buffer );
return buffer;
}
//-----------------------------------------------------------------------------
SFXFMODBuffer::SFXFMODBuffer( const String& filename, SFXDescription* description )
: Parent( description ),
mSound( NULL )
{
FMOD_MODE fMode = ( description->mUseHardware ? FMOD_HARDWARE : FMOD_SOFTWARE )
| ( description->mIs3D ? FMOD_3D : FMOD_2D );
if( description->mIsStreaming )
{
fMode |= FMOD_CREATESTREAM;
mIsUnique = true;
}
// Go through the extensions and try each with the given path. The
// first two are special. First we try without touching the filename at all
// so FMOD gets a chance to handle URLs and whatever, and then second we
// try by expanding the path but without adding an extension.
Torque::Path path = filename;
for( U32 i = 0; sExtensions[ i ]; ++ i )
{
path.setExtension( sExtensions[ i ] );
if( !i || Torque::FS::IsFile( path ) )
{
// Translate to full path.
//TODO: Remove this when hooking up the file system functions in sfxFMODDevice.cpp
String fullPath;
if( !i )
fullPath = filename;
else
{
Torque::Path realPath;
if( !Torque::FS::GetFSPath( path, realPath ) )
continue;
fullPath = realPath.getFullPath().c_str();
}
mSound = NULL;
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_System_CreateSound(
SFXFMODDevice::smSystem,
fullPath.c_str(),
fMode,
( FMOD_CREATESOUNDEXINFO* ) NULL,
&mSound );
if( result == FMOD_OK )
{
SFXFMODDevice::smFunc->FMOD_Sound_GetMode( mSound, &mMode );
// Read out format.
int numChannels;
int bitsPerSample;
unsigned int length;
float frequency;
SFXFMODDevice::smFunc->FMOD_Sound_GetFormat( mSound, ( FMOD_SOUND_TYPE* ) NULL, ( FMOD_SOUND_FORMAT* ) NULL, &numChannels, &bitsPerSample );
SFXFMODDevice::smFunc->FMOD_Sound_GetLength( mSound, &length, FMOD_TIMEUNIT_MS );
SFXFMODDevice::smFunc->FMOD_Sound_GetDefaults( mSound, &frequency, ( float* ) NULL, ( float* ) NULL, ( int* ) NULL );
mDuration = length;
mFormat = SFXFormat( numChannels, numChannels * bitsPerSample, frequency );
break;
}
}
}
if( !mSound )
Con::errorf( "SFXFMODBuffer::SFXFMODBuffer - failed to load '%s' through FMOD", filename.c_str() );
}
//-----------------------------------------------------------------------------
SFXFMODBuffer::SFXFMODBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
: Parent( stream, description ),
mSound( NULL )
{
FMOD_MODE fMode = ( description->mUseHardware ? FMOD_HARDWARE : FMOD_SOFTWARE )
| ( description->mIs3D ? FMOD_3D : FMOD_2D );
FMOD_CREATESOUNDEXINFO* pCreatesoundexinfo = NULL;
FMOD_CREATESOUNDEXINFO createsoundexinfo;
fMode |= FMOD_OPENUSER; // this tells fmod we are supplying the data directly
if( isStreaming() )
fMode |= FMOD_LOOP_NORMAL | FMOD_UNIQUE;
const SFXFormat& format = getFormat();
U32 channels = format.getChannels();
U32 frequency = format.getSamplesPerSecond();
U32 bitsPerChannel = format.getBitsPerSample() / channels;
U32 dataSize = mBufferSize;
FMOD_SOUND_FORMAT sfxFmt = FMOD_SOUND_FORMAT_NONE;
switch(bitsPerChannel)
{
case 8:
sfxFmt = FMOD_SOUND_FORMAT_PCM8;
break;
case 16:
sfxFmt = FMOD_SOUND_FORMAT_PCM16;
break;
case 24:
sfxFmt = FMOD_SOUND_FORMAT_PCM24;
break;
case 32:
sfxFmt = FMOD_SOUND_FORMAT_PCM32;
break;
default:
AssertISV(false, "SFXFMODBuffer::SFXFMODBuffer() - unsupported bits-per-sample (what format is it in, 15bit PCM?)");
break;
}
dMemset(&createsoundexinfo, 0, sizeof(FMOD_CREATESOUNDEXINFO));
createsoundexinfo.cbsize = sizeof(FMOD_CREATESOUNDEXINFO); /* required. */
createsoundexinfo.decodebuffersize = frequency; /* Chunk size of stream update in samples. This will be the amount of data passed to the user callback. */
createsoundexinfo.length = dataSize; /* Length of PCM data in bytes of whole sound (for Sound::getLength) */
createsoundexinfo.numchannels = channels; /* Number of channels in the sound. */
createsoundexinfo.defaultfrequency = frequency; /* Default playback rate of sound. */
createsoundexinfo.format = sfxFmt; /* Data format of sound. */
createsoundexinfo.pcmreadcallback = NULL; /* User callback for reading. */
createsoundexinfo.pcmsetposcallback = NULL; /* User callback for seeking. */
pCreatesoundexinfo = &createsoundexinfo;
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_System_CreateSound(
SFXFMODDevice::smSystem,
( const char* ) NULL,
fMode,
pCreatesoundexinfo,
&mSound );
if( result != FMOD_OK )
{
mSound = NULL;
Con::errorf( "SFXFMODBuffer::SFXFMODBuffer - failed to create buffer (%i)", result );
}
else
SFXFMODDevice::smFunc->FMOD_Sound_GetMode( mSound, &mMode );
}
//-----------------------------------------------------------------------------
SFXFMODBuffer::~SFXFMODBuffer()
{
if( mSound )
FModAssert( SFXFMODDevice::smFunc->FMOD_Sound_Release( mSound ),
"SFXFMODBuffer::~SFXFMODBuffer - Failed to release a sound!" );
mSound = NULL;
}
//-----------------------------------------------------------------------------
void SFXFMODBuffer::_flush()
{
AssertFatal( isStreaming(), "SFXFMODBuffer::_flush() - not a streaming buffer" );
AssertFatal( SFXInternal::isSFXThread(), "SFXFMODBuffer::_flush() - not on SFX thread" );
Parent::_flush();
SFXFMODDevice::smFunc->FMOD_Channel_SetPosition
( ( ( SFXFMODVoice* ) mUniqueVoice.getPointer() )->mChannel, 0, FMOD_TIMEUNIT_PCM );
}
//-----------------------------------------------------------------------------
bool SFXFMODBuffer::_copyData( U32 offset, const U8* data, U32 length )
{
AssertFatal( data != NULL && length > 0, "Must have data!" );
// Fill the buffer with the resource data.
void* lpvWrite;
U32 dwLength;
void* lpvWrite2;
U32 dwLength2;
int res = SFXFMODDevice::smFunc->FMOD_Sound_Lock(
mSound,
offset, // Offset at which to start lock.
length, // Size of lock.
&lpvWrite, // Gets address of first part of lock.
&lpvWrite2, // Address of wraparound not needed.
&dwLength, // Gets size of first part of lock.
&dwLength2 // Size of wraparound not needed.
);
if ( res != FMOD_OK )
{
// You can remove this if it gets spammy. However since we can
// safely fail in this case it doesn't seem right to assert...
// at the same time it can be very annoying not to know why
// an upload fails!
Con::errorf("SFXFMODBuffer::_copyData - failed to lock a sound buffer! (%d)", this);
return false;
}
// Copy the first part.
dMemcpy( lpvWrite, data, dwLength );
// Do we have a wrap?
if ( lpvWrite2 )
dMemcpy( lpvWrite2, data + dwLength, dwLength2 );
// And finally, unlock.
FModAssert( SFXFMODDevice::smFunc->FMOD_Sound_Unlock(
mSound,
lpvWrite, // Address of lock start.
lpvWrite2, // No wraparound portion.
dwLength, // Size of lock.
dwLength2 ), // No wraparound size.
"Failed to unlock sound buffer!" );
return true;
}
//-----------------------------------------------------------------------------
U32 SFXFMODBuffer::getMemoryUsed() const
{
unsigned int memoryUsed;
SFXFMODDevice::smFunc->FMOD_Sound_GetMemoryInfo(
mSound,
FMOD_MEMBITS_ALL,
FMOD_EVENT_MEMBITS_ALL,
&memoryUsed,
( unsigned int* ) NULL );
return memoryUsed;
}

View file

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODBUFFER_H_
#define _SFXFMODBUFFER_H_
#include "fmod.h"
#ifndef _SFXINTERNAL_H_
# include "sfx/sfxInternal.h"
#endif
class SFXFMODBuffer : public SFXInternal::SFXWrapAroundBuffer
{
typedef SFXInternal::SFXWrapAroundBuffer Parent;
friend class SFXFMODDevice;
friend class SFXFMODVoice;
protected:
FMOD_SOUND *mSound;
FMOD_MODE mMode;
SFXFMODBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
SFXFMODBuffer( const String& filename, SFXDescription* description );
// SFXWrapAroundBuffer.
virtual bool _copyData( U32 offset, const U8* data, U32 length );
virtual void _flush();
virtual ~SFXFMODBuffer();
public:
///
static SFXFMODBuffer* create( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
static SFXFMODBuffer* create( const String& filename, SFXDescription* description );
virtual U32 getMemoryUsed() const;
};
#endif // _SFXFMODBUFFER_H_

View file

@ -0,0 +1,581 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "platform/threads/mutex.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "sfx/fmod/sfxFMODBuffer.h"
#include "sfx/sfxSystem.h"
#include "platform/async/asyncUpdate.h"
#include "console/consoleTypes.h"
#include "core/volume.h"
bool SFXFMODDevice::smPrefDisableSoftware = false;
bool SFXFMODDevice::smPrefUseSoftwareOcclusion = true;
bool SFXFMODDevice::smPrefUseSoftwareHRTF = true;
bool SFXFMODDevice::smPrefUseSoftwareReverbLowmem = false;
bool SFXFMODDevice::smPrefEnableProfile = false;
bool SFXFMODDevice::smPrefGeometryUseClosest = false;
const char* SFXFMODDevice::smPrefDSoundHRTF = "full";
const char* SFXFMODDevice::smPrefPluginPath = "";
U32 SFXFMODDevice::smStatMemUsageCore;
U32 SFXFMODDevice::smStatMemUsageEvents;
U32 SFXFMODDevice::smStatNumEventSources;
SFXFMODDevice* SFXFMODDevice::smInstance;
FMOD_SYSTEM* SFXFMODDevice::smSystem;
FMOD_EVENTSYSTEM* SFXFMODDevice::smEventSystem;
FModFNTable* SFXFMODDevice::smFunc;
Mutex* FModFNTable::mutex;
//-----------------------------------------------------------------------------
String FMODResultToString( FMOD_RESULT result )
{
switch( result )
{
#define FMOD_ERROR( n ) case n: return #n;
#include "fmodErrors.h"
#undef FMOD_ERROR
default:
break;
}
return String();
}
//------------------------------------------------------------------------------
// FMOD filesystem wrappers.
//FIXME: these are not thread-safe and cannot be used as such
FMOD_RESULT F_CALLBACK fmodFileOpenCallback( const char* name, int unicode, unsigned int* filesize, void** handle, void** userdata )
{
String fileName;
if( unicode )
fileName = String( ( UTF16* ) name );
else
fileName = String( name );
Torque::FS::FileRef file = Torque::FS::OpenFile( fileName, Torque::FS::File::Read );
if( !file )
return FMOD_ERR_FILE_NOTFOUND;
else if( file->getStatus() != Torque::FS::File::Open )
return FMOD_ERR_FILE_BAD;
// Add a reference so we can pass it into FMOD.
file->incRefCount();
*filesize = U32( file->getSize() );
*handle = file.getPointer();
return FMOD_OK;
}
FMOD_RESULT F_CALLBACK fmodFileCloseCallback( void* handle, void* userdata )
{
Torque::FS::File* file = reinterpret_cast< Torque::FS::File* >( handle );
file->decRefCount();
return FMOD_OK;
}
FMOD_RESULT F_CALLBACK fmodFileReadCallback( void* handle, void* buffer, unsigned int sizebytes, unsigned int* bytesread, void* userdata )
{
Torque::FS::File* file = reinterpret_cast< Torque::FS::File* >( handle );
U32 numRead = file->read( buffer, sizebytes );
*bytesread = numRead;
if( file->getStatus() == Torque::FS::File::EndOfFile )
return FMOD_ERR_FILE_EOF;
else if( file->getStatus() != Torque::FS::File::Open )
return FMOD_ERR_FILE_BAD;
return FMOD_OK;
}
FMOD_RESULT F_CALLBACK fmodFileSeekCallback( void* handle, unsigned int pos, void* userdata )
{
Torque::FS::File* file = reinterpret_cast< Torque::FS::File* >( handle );
if( file->setPosition( pos, Torque::FS::File::Begin ) != pos )
return FMOD_ERR_FILE_COULDNOTSEEK;
return FMOD_OK;
}
//-----------------------------------------------------------------------------
SFXFMODDevice::SFXFMODDevice( SFXProvider* provider,
FModFNTable *fmodFnTbl,
int deviceIdx,
String name )
: SFXDevice( name, provider, false, 32 ),
m3drolloffmode( FMOD_3D_INVERSEROLLOFF ),
mDeviceIndex( deviceIdx )
{
// Store off the function pointers for later use.
smFunc = fmodFnTbl;
smStatMemUsageCore = 0;
smStatMemUsageEvents = 0;
smStatNumEventSources = 0;
// Register our SFXSystem plugin.
SFX->addPlugin( &mPlugin );
smInstance = this;
}
//-----------------------------------------------------------------------------
SFXFMODDevice::~SFXFMODDevice()
{
_releaseAllResources();
SFX->removePlugin( &mPlugin );
if( smEventSystem )
{
smFunc->FMOD_EventSystem_Release( smEventSystem );
smEventSystem = NULL;
smSystem = NULL;
}
else
smFunc->FMOD_System_Close( smSystem );
smInstance = NULL;
}
//-----------------------------------------------------------------------------
bool SFXFMODDevice::_init()
{
#define FMOD_CHECK( message ) \
if( result != FMOD_OK ) \
{ \
Con::errorf( "SFXFMODDevice::_init() - %s (%s)", \
message, \
FMOD_ErrorString( result ) ); \
return false; \
}
AssertISV(smSystem,
"SFXFMODDevice::_init() - can't init w/o an existing FMOD system handle!");
FMOD_RESULT result;
// Get some prefs.
if( smPrefPluginPath && smPrefPluginPath[ 0 ] )
{
char fullPath[ 4096 ];
Platform::makeFullPathName( smPrefPluginPath, fullPath, sizeof( fullPath ) );
smFunc->FMOD_System_SetPluginPath( smSystem, fullPath );
}
else
{
smFunc->FMOD_System_SetPluginPath( smSystem, Platform::getExecutablePath() );
}
// Initialize everything from fmod.
FMOD_SPEAKERMODE speakermode;
FMOD_CAPS caps;
result = smFunc->FMOD_System_GetDriverCaps(smSystem, 0, &caps, ( int* ) 0, &speakermode);
FMOD_CHECK( "SFXFMODDevice::init - Failed to get driver caps" );
result = smFunc->FMOD_System_SetDriver(smSystem, mDeviceIndex);
FMOD_CHECK( "SFXFMODDevice::init - Failed to set driver" );
result = smFunc->FMOD_System_SetSpeakerMode(smSystem, speakermode);
FMOD_CHECK( "SFXFMODDevice::init - Failed to set the user selected speaker mode" );
if (caps & FMOD_CAPS_HARDWARE_EMULATED) /* The user has the 'Acceleration' slider set to off! This is really bad for latency!. */
{ /* You might want to warn the user about this. */
result = smFunc->FMOD_System_SetDSPBufferSize(smSystem, 1024, 10);
FMOD_CHECK( "SFXFMODDevice::init - Failed to set DSP buffer size" );
}
Con::printf( "\nFMOD Device caps:" );
#define PRINT_CAP( name ) \
if( caps & FMOD_CAPS_ ## name ) \
Con::printf( #name );
PRINT_CAP( HARDWARE );
PRINT_CAP( HARDWARE_EMULATED );
PRINT_CAP( OUTPUT_MULTICHANNEL );
PRINT_CAP( OUTPUT_FORMAT_PCM8 );
PRINT_CAP( OUTPUT_FORMAT_PCM16 );
PRINT_CAP( OUTPUT_FORMAT_PCM24 );
PRINT_CAP( OUTPUT_FORMAT_PCM32 );
PRINT_CAP( OUTPUT_FORMAT_PCMFLOAT );
PRINT_CAP( REVERB_LIMITED );
Con::printf( "" );
bool tryAgain;
do
{
tryAgain = false;
FMOD_INITFLAGS flags = FMOD_INIT_NORMAL | FMOD_INIT_VOL0_BECOMES_VIRTUAL;
if( smPrefDisableSoftware )
flags |= FMOD_INIT_SOFTWARE_DISABLE;
if( smPrefUseSoftwareOcclusion )
flags |= FMOD_INIT_SOFTWARE_OCCLUSION;
if( smPrefUseSoftwareHRTF )
flags |= FMOD_INIT_SOFTWARE_HRTF;
if( smPrefUseSoftwareReverbLowmem )
flags |= FMOD_INIT_SOFTWARE_REVERB_LOWMEM;
if( smPrefEnableProfile )
flags |= FMOD_INIT_ENABLE_PROFILE;
if( smPrefGeometryUseClosest )
flags |= FMOD_INIT_GEOMETRY_USECLOSEST;
if( smEventSystem )
result = smFunc->FMOD_EventSystem_Init( smEventSystem, 100, flags, ( void* ) 0, FMOD_EVENT_INIT_NORMAL );
else
result = smFunc->FMOD_System_Init( smSystem, 100, flags, ( void* ) 0 );
if( result == FMOD_ERR_OUTPUT_CREATEBUFFER ) /* Ok, the speaker mode selected isn't supported by this soundcard. Switch it back to stereo... */
{
result = smFunc->FMOD_System_SetSpeakerMode( smSystem, FMOD_SPEAKERMODE_STEREO );
FMOD_CHECK( "SFXFMODDevice::init - failed on fallback speaker mode setup" );
tryAgain = true;
}
} while( tryAgain );
FMOD_CHECK( "SFXFMODDevice::init - failed to init system" );
// Print hardware channel info.
if( caps & FMOD_CAPS_HARDWARE )
{
int num3D, num2D, numTotal;
if( smFunc->FMOD_System_GetHardwareChannels( smSystem, &num2D, &num3D, &numTotal ) == FMOD_OK )
Con::printf( "FMOD Hardware channels: 2d=%i, 3d=%i, total=%i", num2D, num3D, numTotal );
}
// Set up filesystem.
//FIXME: Don't do this for now. Crashes on Windows.
#if 0
smFunc->FMOD_System_SetFileSystem( smSystem, fmodFileOpenCallback, fmodFileCloseCallback, fmodFileReadCallback, fmodFileSeekCallback, -1 );
#endif
// Set capabilities.
mCaps = CAPS_Reverb | CAPS_VoiceManagement;
if( smEventSystem )
mCaps |= CAPS_FMODDesigner;
// Start the update thread.
#ifndef TORQUE_DEDICATED // Avoid dependency on platform/async for Linx dedicated.
if( !Con::getBoolVariable( "$_forceAllMainThread" ) )
{
SFXInternal::gUpdateThread = new AsyncPeriodicUpdateThread
( "FMOD Update Thread", SFXInternal::gBufferUpdateList,
Con::getIntVariable( "$pref::SFX::updateInterval", SFXInternal::DEFAULT_UPDATE_INTERVAL ) );
SFXInternal::gUpdateThread->start();
}
#endif
return true;
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXFMODDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
{
AssertFatal( stream, "SFXFMODDevice::createBuffer() - Got a null stream!" );
AssertFatal( description, "SFXFMODDevice::createBuffer() - Got null description!" );
SFXFMODBuffer *buffer = SFXFMODBuffer::create( stream, description );
if ( buffer )
_addBuffer( buffer );
return buffer;
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXFMODDevice::createBuffer( const String& filename, SFXDescription* description )
{
AssertFatal( filename.isNotEmpty(), "SFXFMODDevice::createBuffer() - Got an empty filename!" );
AssertFatal( description, "SFXFMODDevice::createBuffer() - Got null description!" );
SFXFMODBuffer* buffer = SFXFMODBuffer::create( filename, description );
if( buffer )
_addBuffer( buffer );
return buffer;
}
//-----------------------------------------------------------------------------
SFXVoice* SFXFMODDevice::createVoice( bool is3D, SFXBuffer* buffer )
{
AssertFatal( buffer, "SFXFMODDevice::createVoice() - Got null buffer!" );
SFXFMODBuffer* fmodBuffer = dynamic_cast<SFXFMODBuffer*>( buffer );
AssertFatal( fmodBuffer, "SFXFMODDevice::createVoice() - Got bad buffer!" );
SFXFMODVoice* voice = SFXFMODVoice::create( this, fmodBuffer );
if ( !voice )
return NULL;
_addVoice( voice );
return voice;
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::update()
{
Parent::update();
if( smEventSystem )
{
FModAssert( smFunc->FMOD_EventSystem_Update( smEventSystem ), "Failed to update event system!" );
}
else
{
FModAssert(smFunc->FMOD_System_Update(smSystem), "Failed to update system!");
}
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::setNumListeners( U32 num )
{
smFunc->FMOD_System_Set3DNumListeners( smSystem, num );
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::setListener( U32 index, const SFXListenerProperties& listener )
{
FMOD_VECTOR position, forward, up, velocity;
TorqueTransformToFMODVectors( listener.getTransform(), position, forward, up );
TorqueVectorToFMODVector( listener.getVelocity(), velocity );
// Do the listener state update, then update!
smFunc->FMOD_System_Set3DListenerAttributes( smSystem, index, &position, &velocity, &forward, &up );
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::setDistanceModel( SFXDistanceModel model )
{
switch( model )
{
case SFXDistanceModelLinear:
m3drolloffmode = FMOD_3D_LINEARROLLOFF;
break;
case SFXDistanceModelLogarithmic:
m3drolloffmode = FMOD_3D_INVERSEROLLOFF;
break;
default:
AssertWarn( false, "SFXFMODDevice::setDistanceModel - model not implemented" );
}
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::setDopplerFactor( F32 factor )
{
F32 dopplerFactor;
F32 distanceFactor;
F32 rolloffFactor;
smFunc->FMOD_System_Get3DSettings( smSystem, &dopplerFactor, &distanceFactor, &rolloffFactor );
dopplerFactor = factor;
smFunc->FMOD_System_Set3DSettings( smSystem, dopplerFactor, distanceFactor, rolloffFactor );
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::setRolloffFactor( F32 factor )
{
F32 dopplerFactor;
F32 distanceFactor;
F32 rolloffFactor;
smFunc->FMOD_System_Get3DSettings( smSystem, &dopplerFactor, &distanceFactor, &rolloffFactor );
rolloffFactor = factor;
smFunc->FMOD_System_Set3DSettings( smSystem, dopplerFactor, distanceFactor, rolloffFactor );
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::setReverb( const SFXReverbProperties& reverb )
{
FMOD_REVERB_PROPERTIES prop = FMOD_PRESET_GENERIC;
prop.Environment = 0;
prop.EnvDiffusion = reverb.mEnvDiffusion;
prop.Room = reverb.mRoom;
prop.RoomHF = reverb.mRoomHF;
prop.RoomLF = reverb.mRoomLF;
prop.DecayTime = reverb.mDecayTime;
prop.DecayLFRatio = reverb.mDecayLFRatio;
prop.DecayHFRatio = reverb.mDecayHFRatio;
prop.Reflections = reverb.mReflections;
prop.ReflectionsDelay = reverb.mReflectionsDelay;
prop.Reverb = reverb.mReverb;
prop.ReverbDelay = reverb.mReverbDelay;
prop.ModulationTime = reverb.mModulationTime;
prop.ModulationDepth = reverb.mModulationDepth;
prop.HFReference = reverb.mHFReference;
prop.LFReference = reverb.mLFReference;
prop.Diffusion = reverb.mDiffusion;
prop.Density = reverb.mDensity;
prop.Flags = reverb.mFlags;
// Here we only want to affect 3D sounds. While not quite obvious from the docs,
// SetReverbProperties sets the global reverb environment for 2D sounds whereas
// SetAmbientReverbProperties sets the global reverb environment for 3D sounds.
FMOD_RESULT result = smFunc->FMOD_System_SetReverbAmbientProperties( smSystem, &prop );
if( result != FMOD_OK )
Con::errorf( "SFXFMODDevice::setReverb - Failed to set reverb (%s)", FMODResultToString( result ).c_str() );
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::resetReverb()
{
FMOD_REVERB_PROPERTIES prop = FMOD_PRESET_OFF;
smFunc->FMOD_System_SetReverbProperties( smSystem, &prop );
}
//-----------------------------------------------------------------------------
void SFXFMODDevice::updateMemUsageStats()
{
smFunc->FMOD_System_GetMemoryInfo( smSystem, ( unsigned int ) FMOD_MEMBITS_ALL,
( unsigned int ) 0, ( unsigned int* ) &smStatMemUsageCore, ( unsigned int* ) 0 );
if( smEventSystem )
smFunc->FMOD_EventSystem_GetMemoryInfo( smEventSystem, ( unsigned int ) 0,
( unsigned int ) FMOD_EVENT_MEMBITS_ALL, ( unsigned int* ) &smStatMemUsageEvents, ( unsigned int* ) 0 );
}
//=============================================================================
// Console Functions.
//=============================================================================
// MARK: ---- Console Functions ----
//------------------------------------------------------------------------------
ConsoleFunction( fmodDumpDSPInfo, void, 1, 1, "()"
"@brief Dump information about the standard DSP effects.\n\n"
"@ingroup SFXFMOD")
{
if( !SFXFMODDevice::smFunc )
return;
const U32 firstDSPType = FMOD_DSP_TYPE_MIXER;
const U32 lastDSPType = FMOD_DSP_TYPE_TREMOLO;
for( U32 i = firstDSPType; i <= lastDSPType; ++ i )
{
FMOD_DSP* dsp;
if( SFXFMODDevice::smFunc->FMOD_System_CreateDSPByType( SFXFMODDevice::smSystem, ( FMOD_DSP_TYPE ) i, &dsp ) == FMOD_OK )
{
// Print general info.
char name[ 33 ];
unsigned int version;
int channels;
int numParameters;
dMemset( name, 0, sizeof( name ) );
SFXFMODDevice::smFunc->FMOD_DSP_GetInfo( dsp, name, &version, &channels, ( int* ) NULL, ( int* ) NULL );
SFXFMODDevice::smFunc->FMOD_DSP_GetNumParameters( dsp, &numParameters );
Con::printf( "----------------------------------------------------------------" );
Con::printf( "DSP: %s", name );
Con::printf( "Version: %i.%i", ( version & 0xffff0000 ) >> 16, version & 0xffff );
Con::printf( "Channels: %i", channels );
Con::printf( "Parameters: %i", numParameters );
Con::printf( "" );
// Print parameter info.
for( U32 n = 0; n < numParameters; ++ n )
{
char name[ 17 ];
char label[ 17 ];
char description[ 1024 ];
float minValue, maxValue;
float value;
char valueString[ 256 ];
dMemset( name, 0, sizeof( name ) );
dMemset( label, 0, sizeof( label ) );
dMemset( description, 0, sizeof( description ) );
dMemset( valueString, 0, sizeof( valueString ) );
SFXFMODDevice::smFunc->FMOD_DSP_GetParameterInfo( dsp, n, name, label, description, sizeof( description ) - 1, &minValue, &maxValue );
SFXFMODDevice::smFunc->FMOD_DSP_GetParameter( dsp, n, &value, valueString, sizeof( valueString ) - 1 );
Con::printf( "* Parameter %i", n );
Con::printf( "Name: %s", name );
Con::printf( "Label: %s", label );
Con::printf( "Description: %s", description );
Con::printf( "Min: %f", minValue );
Con::printf( "Max: %f", maxValue );
Con::printf( "Value: %f (%s)", value, valueString );
Con::printf( "" );
}
// Release the DSP.
SFXFMODDevice::smFunc->FMOD_DSP_Release( dsp );
}
}
}
//-----------------------------------------------------------------------------
ConsoleFunction( fmodDumpMemoryStats, void, 1, 1, "()"
"@return Prints the current memory consumption of the FMOD module\n\n"
"@ingroup SFXFMOD")
{
int current = 0;
int max = 0;
if (SFXFMODDevice::smFunc && SFXFMODDevice::smFunc->FMOD_Memory_GetStats.fn)
SFXFMODDevice::smFunc->FMOD_Memory_GetStats(&current, &max);
Con::printf("Fmod current: %d, max: %d", current, max);
}

View file

@ -0,0 +1,345 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODDEVICE_H_
#define _SFXFMODDEVICE_H_
#ifndef _SFXDEVICE_H_
#include "sfx/sfxDevice.h"
#endif
#ifndef _SFXFMODVOICE_H_
#include "sfx/fmod/sfxFMODVoice.h"
#endif
#ifndef _SFXFMODBUFFER_H_
#include "sfx/fmod/sfxFMODBuffer.h"
#endif
#ifndef _SFXFMODPLUGIN_H_
#include "sfx/fmod/sfxFMODPlugin.h"
#endif
#include "core/util/tDictionary.h"
// Disable warning for unused static functions.
#ifdef TORQUE_COMPILER_VISUALC
#pragma warning( disable : 4505 )
#endif
#if defined( TORQUE_OS_XENON ) || defined( TORQUE_OS_PS3 )
#define TORQUE_FMOD_STATIC
#define TORQUE_FMOD_NO_EVENTS //TEMP
#endif
#include "fmod.h"
#include "fmod_errors.h"
#include "fmod_event.h"
#include "platform/platformDlibrary.h"
#include "platform/threads/mutex.h"
// This doesn't appear to exist in some contexts, so let's just add it.
#if defined(TORQUE_OS_WIN32) || defined(TORQUE_OS_XENON)
#ifndef WINAPI
#define WINAPI __stdcall
#endif
#else
#define WINAPI
#endif
#define FModAssert(x, msg) \
{ FMOD_RESULT result = ( x ); \
AssertISV( result == FMOD_OK, String::ToString( "%s: %s", msg, FMOD_ErrorString( result ) ) ); }
#define FMOD_FN_FILE "sfx/fmod/fmodFunctions.h"
// Typedefs
#define FMOD_FUNCTION(fn_name, fn_args) \
typedef FMOD_RESULT (WINAPI *FMODFNPTR##fn_name)fn_args;
#define FMOD_EVENT_FUNCTION(fn_name, fn_args, dllexport) \
typedef FMOD_RESULT (WINAPI *FMODFNPTR##fn_name)fn_args;
#include FMOD_FN_FILE
#undef FMOD_FUNCTION
#undef FMOD_EVENT_FUNCTION
/// FMOD API function table.
///
/// FMOD doesn't want to be called concurrently so in order to
/// not force everything to the main thread (where sound updates
/// would just stall during loading), we thunk all the API
/// calls and lock all API entry points to a single mutex.
struct FModFNTable
{
FModFNTable()
: isLoaded( false ),
eventIsLoaded( false ),
dllRef( NULL ),
eventDllRef( NULL )
{
AssertFatal( mutex == NULL,
"FModFNTable::FModFNTable() - this should be a singleton" );
mutex = new Mutex;
}
~FModFNTable()
{
eventDllRef = NULL;
dllRef = NULL;
delete mutex;
}
bool isLoaded;
bool eventIsLoaded;
DLibraryRef dllRef;
DLibraryRef eventDllRef;
static Mutex* mutex;
template< typename FN >
struct Thunk
{
FN fn;
template< typename A >
FMOD_RESULT operator()( A a )
{
mutex->lock();
FMOD_RESULT result = fn( a );
mutex->unlock();
return result;
}
template< typename A, typename B >
FMOD_RESULT operator()( A a, B b )
{
mutex->lock();
FMOD_RESULT result = fn( a, b );
mutex->unlock();
return result;
}
template< typename A, typename B, typename C >
FMOD_RESULT operator()( A a, B b, C c )
{
mutex->lock();
FMOD_RESULT result = fn( a, b, c );
mutex->unlock();
return result;
}
template< typename A, typename B, typename C, typename D >
FMOD_RESULT operator()( A a, B b, C c, D d )
{
mutex->lock();
FMOD_RESULT result = fn( a, b, c, d );
mutex->unlock();
return result;
}
template< typename A, typename B, typename C, typename D, typename E >
FMOD_RESULT operator()( A a, B b, C c, D d, E e )
{
mutex->lock();
FMOD_RESULT result = fn( a, b, c, d, e );
mutex->unlock();
return result;
}
template< typename A, typename B, typename C, typename D, typename E, typename F >
FMOD_RESULT operator()( A a, B b, C c, D d, E e, F f )
{
mutex->lock();
FMOD_RESULT result = fn( a, b, c, d, e, f );
mutex->unlock();
return result;
}
template< typename A, typename B, typename C, typename D, typename E, typename F, typename G >
FMOD_RESULT operator()( A a, B b, C c, D d, E e, F f, G g )
{
mutex->lock();
FMOD_RESULT result = fn( a, b, c, d, e, f, g );
mutex->unlock();
return result;
}
template< typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H >
FMOD_RESULT operator()( A a, B b, C c, D d, E e, F f, G g, H h )
{
mutex->lock();
FMOD_RESULT result = fn( a, b, c, d, e, f, g, h );
mutex->unlock();
return result;
}
};
#define FMOD_FUNCTION(fn_name, fn_args) \
Thunk< FMODFNPTR##fn_name > fn_name;
#define FMOD_EVENT_FUNCTION(fn_name, fn_args, dllexport) \
Thunk< FMODFNPTR##fn_name > fn_name;
#include FMOD_FN_FILE
#undef FMOD_FUNCTION
#undef FMOD_EVENT_FUNCTION
};
inline void TorqueVectorToFMODVector( const Point3F& torque, FMOD_VECTOR& fmod )
{
fmod.x = torque.x;
fmod.y = torque.z;
fmod.z = torque.y;
}
inline void TorqueTransformToFMODVectors( const MatrixF& transform, FMOD_VECTOR& position, FMOD_VECTOR& forward, FMOD_VECTOR& up )
{
Point3F _pos, _fwd, _up;
transform.getColumn( 3, &_pos );
transform.getColumn( 1, &_fwd );
transform.getColumn( 2, &_up );
TorqueVectorToFMODVector( _pos, position );
TorqueVectorToFMODVector( _fwd, forward );
TorqueVectorToFMODVector( _up, up );
}
inline int TorquePriorityToFMODPriority( F32 priority )
{
// Map [-2,2] to [256,0].
F32 n = mClampF( priority, -2.0f, 2.0f ) + 2.0f;
return ( n * 256.0f / 4.0f );
}
inline String FMODEventPathToTorqueName( const String& path )
{
String p = path;
p.replace( '/', '_' );
p.replace( '-', '_' );
p.replace( ' ', '_' );
p.replace( '(', '_' );
p.replace( ')', '_' );
p.replace( '%', '_' );
p.replace( '$', '_' );
return p;
}
inline String FMODEventPathToTorqueName( const String& projectName, const String& path )
{
return String::ToString( "%s_%s", projectName.c_str(), FMODEventPathToTorqueName( path ).c_str() );
}
extern String FMODResultToString( FMOD_RESULT result );
class SFXProvider;
class SFXFMODPlugin;
class SFXFMODDevice : public SFXDevice
{
public:
typedef SFXDevice Parent;
friend class SFXFMODProvider; // _init
friend class SFXFMODEventSource; // smStatNumEventSources
explicit SFXFMODDevice();
SFXFMODDevice( SFXProvider* provider, FModFNTable *fmodFnTbl, int deviceIdx, String name );
virtual ~SFXFMODDevice();
protected:
FMOD_MODE m3drolloffmode;
int mDeviceIndex;
/// The FMOD SFXSystemPlugin instance.
SFXFMODPlugin mPlugin;
/// @name Console Variables
/// @{
/// Current core FMOD memory usage in bytes.
static U32 smStatMemUsageCore;
/// Current FMOD Event DLL memory usage in bytes.
static U32 smStatMemUsageEvents;
/// Current number of SFXFMODEventSource instances.
static U32 smStatNumEventSources;
///
static bool smPrefDisableSoftware;
///
static bool smPrefUseSoftwareOcclusion;
///
static bool smPrefUseSoftwareHRTF;
///
static bool smPrefUseSoftwareReverbLowmem;
///
static bool smPrefEnableProfile;
///
static bool smPrefGeometryUseClosest;
///
static const char* smPrefDSoundHRTF;
///
static const char* smPrefPluginPath;
/// @}
bool _init();
static SFXFMODDevice* smInstance;
public:
static SFXFMODDevice* instance() { return smInstance; }
FMOD_MODE get3dRollOffMode() { return m3drolloffmode; }
static FMOD_SYSTEM* smSystem;
static FMOD_EVENTSYSTEM* smEventSystem;
static FModFNTable* smFunc;
// Update memory usage stats for metrics display.
void updateMemUsageStats();
// SFXDevice.
virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
virtual SFXBuffer* createBuffer( const String& filename, SFXDescription* description );
virtual SFXVoice* createVoice( bool is3D, SFXBuffer* buffer );
virtual void update();
virtual void setNumListeners( U32 num );
virtual void setListener( U32 index, const SFXListenerProperties& listener );
virtual void setDistanceModel( SFXDistanceModel model );
virtual void setDopplerFactor( F32 factor );
virtual void setRolloffFactor( F32 factor );
virtual void setReverb( const SFXReverbProperties& reverb );
virtual void resetReverb();
};
#endif // _SFXFMODDEVICE_H_

View file

@ -0,0 +1,339 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/fmod/sfxFMODEvent.h"
#include "sfx/fmod/sfxFMODEventGroup.h"
#include "sfx/fmod/sfxFMODProject.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "sfx/sfxParameter.h"
#include "sfx/sfxDescription.h"
#include "core/stream/bitStream.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXFMODEvent );
ConsoleDocClass( SFXFMODEvent,
"@brief A playable sound event in an FMOD Designer audio project.\n\n"
"@ingroup SFXFMOD\n"
"@ingroup Datablocks"
);
//-----------------------------------------------------------------------------
SFXFMODEvent::SFXFMODEvent()
: mGroup( NULL ),
mHandle( NULL ),
mGroupId( 0 ),
mSibling( NULL )
{
dMemset( mParameterRanges, 0, sizeof( mParameterRanges ) );
dMemset( mParameterValues, 0, sizeof( mParameterValues ) );
}
//-----------------------------------------------------------------------------
SFXFMODEvent::SFXFMODEvent( SFXFMODEventGroup* group, FMOD_EVENT* handle )
: mGroup( group ),
mHandle( handle ),
mGroupId( 0 ),
mSibling( NULL )
{
dMemset( mParameterRanges, 0, sizeof( mParameterRanges ) );
dMemset( mParameterValues, 0, sizeof( mParameterValues ) );
// Fetch name.
int index;
char* name = NULL;
SFXFMODDevice::smFunc->FMOD_Event_GetInfo( mHandle, &index, &name, ( FMOD_EVENT_INFO* ) 0 );
mName = name;
// Read out the parameter info so we can immediately create
// the events on the client-side without having to open and
// read all the project info there.
int numParameters;
SFXFMODDevice::smFunc->FMOD_Event_GetNumParameters( mHandle, &numParameters );
if( numParameters > MaxNumParameters )
{
Con::errorf( "SFXFMODEvent::SFXFMODEvent - event '%s' has more parameters (%i) than supported per SFXTrack (%i)",
getQualifiedName().c_str(),
numParameters,
MaxNumParameters );
numParameters = MaxNumParameters;
}
for( U32 i = 0; i < numParameters; ++ i )
{
FMOD_EVENTPARAMETER* parameter;
SFXFMODDevice::smFunc->FMOD_Event_GetParameterByIndex( mHandle, i, &parameter );
SFXFMODDevice::smFunc->FMOD_EventParameter_GetInfo( parameter, &index, &name );
setParameter( i, name );
// Get value and range of parameter.
SFXFMODDevice::smFunc->FMOD_EventParameter_GetValue( parameter, &mParameterValues[ i ] );
SFXFMODDevice::smFunc->FMOD_EventParameter_GetRange( parameter, &mParameterRanges[ i ].x, &mParameterRanges[ i ].y );
}
// Read out the properties and create a custom SFXDescription for the event.
mDescription = new SFXDescription;
if( !group->isClientOnly() )
mDescription->assignId();
mDescription->registerObject(
String::ToString( "%s_%s_Description",
group->getName(),
FMODEventPathToTorqueName( mName ).c_str()
)
);
if( group->isClientOnly() )
Sim::getRootGroup()->addObject( mDescription );
int intValue;
float floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_MODE, &intValue, true ) == FMOD_OK )
mDescription->mIs3D = ( intValue == FMOD_3D );
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_VOLUME, &floatValue, true ) == FMOD_OK )
mDescription->mVolume = floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_PITCH, &floatValue, true ) == FMOD_OK )
mDescription->mPitch = floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_3D_MINDISTANCE, &floatValue, true ) == FMOD_OK )
mDescription->mMinDistance = floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_3D_MAXDISTANCE, &floatValue, true ) == FMOD_OK )
mDescription->mMaxDistance = floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_3D_CONEINSIDEANGLE, &floatValue, true ) == FMOD_OK )
mDescription->mConeInsideAngle = floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_3D_CONEOUTSIDEANGLE, &floatValue, true ) == FMOD_OK )
mDescription->mConeOutsideAngle = floatValue;
if( SFXFMODDevice::smFunc->FMOD_Event_GetPropertyByIndex( mHandle, FMOD_EVENTPROPERTY_3D_CONEOUTSIDEVOLUME, &floatValue, true ) == FMOD_OK )
mDescription->mConeOutsideVolume = floatValue;
// Don't read out fade values as we want to leave fade-effects to
// FMOD rather than having the fading system built into SFX pick
// these values up.
}
//-----------------------------------------------------------------------------
SFXFMODEvent::~SFXFMODEvent()
{
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::initPersistFields()
{
addGroup( "DO NOT MODIFY!!" );
addField( "fmodGroup", TYPEID< SFXFMODEventGroup >(), Offset( mGroup, SFXFMODEvent ), "DO NOT MODIFY!!" );
addField( "fmodName", TypeRealString, Offset( mName, SFXFMODEvent ), "DO NOT MODIFY!!" );
addField( "fmodParameterRanges", TypePoint2F, Offset( mParameterRanges, SFXFMODEvent ), MaxNumParameters, "DO NOT MODIFY!!" );
addField( "fmodParameterValues", TypeF32, Offset( mParameterValues, SFXFMODEvent ), MaxNumParameters, "DO NOT MODIFY!!" );
endGroup( "DO NOT MODIFY!!" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXFMODEvent::onAdd()
{
if( !Parent::onAdd() )
return false;
if( !mGroup )
{
Con::errorf( "SFXFMODEvent::onAdd - no group set; this event was not properly constructed" );
return false;
}
mGroup->_addEvent( this );
mGroup->mProject->_addEvent( this );
// For non-networked event datablocks, create the parameter
// instances now.
if( isClientOnly() )
_createParameters();
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::onRemove()
{
Parent::onRemove();
if( !mGroup )
return;
release();
mGroup->_removeEvent( this );
}
//-----------------------------------------------------------------------------
bool SFXFMODEvent::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
if( !server )
{
if( !Sim::findObject( mGroupId, mGroup ) )
{
errorStr = String::ToString( "SFXFMODEvent - group '%i' does not exist", mGroupId );
return false;
}
// Create parameters.
_createParameters();
}
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::packData( BitStream* stream )
{
Parent::packData( stream );
stream->write( mName );
stream->writeRangedS32( mGroup->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
for( U32 i = 0; i < MaxNumParameters; ++ i )
if( stream->writeFlag( mParameters[ i ] ) )
{
stream->write( mParameterValues[ i ] );
stream->write( mParameterRanges[ i ].x );
stream->write( mParameterRanges[ i ].y );
}
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
stream->read( &mName );
mGroupId = stream->readRangedS32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
for( U32 i = 0; i < MaxNumParameters; ++ i )
if( stream->readFlag() )
{
stream->read( &mParameterValues[ i ] );
stream->read( &mParameterRanges[ i ].x );
stream->read( &mParameterRanges[ i ].y );
}
else
{
mParameterValues[ i ] = 0.f;
mParameterRanges[ i ].x = 0.f;
mParameterRanges[ i ].y = 0.f;
}
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::acquire()
{
if( mHandle )
return;
mGroup->acquire();
if( SFXFMODDevice::smFunc->FMOD_EventGroup_GetEvent(
mGroup->mHandle, mName.c_str(), FMOD_EVENT_INFOONLY, &mHandle ) != FMOD_OK )
{
Con::errorf( "SFXFMODEvent::acquire() - failed to acquire event '%s'", getQualifiedName().c_str() );
return;
}
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::release()
{
if( !mHandle )
return;
SFXFMODDevice::smFunc->FMOD_Event_Release( mHandle, true, false );
mHandle = NULL;
}
//-----------------------------------------------------------------------------
String SFXFMODEvent::getQualifiedName() const
{
return String::ToString( "%s/%s", getEventGroup()->getQualifiedName().c_str(), mName.c_str() );
}
//-----------------------------------------------------------------------------
void SFXFMODEvent::_createParameters()
{
const String& projectFileName = getEventGroup()->getProject()->getFileName();
const String qualifiedGroupName = getEventGroup()->getQualifiedName();
const String description = String::ToString( "FMOD Event Parameter (%s)", projectFileName.c_str() );
for( U32 i = 0; i < MaxNumParameters; ++ i )
{
StringTableEntry name = getParameter( i );
if( !name )
continue;
SFXParameter* parameter = SFXParameter::find( name );
if( !parameter )
{
parameter = new SFXParameter();
parameter->setInternalName( name );
parameter->registerObject();
// Set up parameter.
parameter->setChannel( SFXChannelUser0 );
parameter->setRange( mParameterRanges[ i ] );
parameter->setDefaultValue( mParameterValues[ i ] );
parameter->setValue( mParameterValues[ i ] );
parameter->setDescription( description );
// Set categories for easy filtering.
static StringTableEntry sCategories = StringTable->insert( "categories" );
parameter->setDataField( sCategories, "0", "FMOD" );
parameter->setDataField( sCategories, "1", avar( "FMOD Project: %s", projectFileName.c_str() ) );
parameter->setDataField( sCategories, "2", avar( "FMOD Group: %s", qualifiedGroupName.c_str() ) );
}
}
}

View file

@ -0,0 +1,135 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODEVENT_H_
#define _SFXFMODEVENT_H_
#ifndef _SFXTRACK_H_
#include "sfx/sfxTrack.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _MPOINT2_H_
#include "math/mPoint2.h"
#endif
#include "fmod_event.h"
class SFXFMODProject;
class SFXFMODEventGroup;
/// An event in an FMOD Designer project.
///
/// This class must not be manually instanced by the user. Instead, SFXFMODEvents
/// are automatically created when an SFXFMODProject is loaded.
///
/// Be aware that as all the playback happens internally within FMOD's event system,
/// this bypasses the SFX layer and will thus not work with features that rely the
/// structures there. Namely, sound occlusion (except for FMOD's own occlusion) will
/// not work with FMOD events.
///
/// The parameters of an FMOD event are automatically created and designed using the
/// information in the project.
///
class SFXFMODEvent : public SFXTrack
{
public:
typedef SFXTrack Parent;
friend class SFXFMODEventGroup;
friend class SFXFMODEventSource;
protected:
/// Name of the event in the Designer project.
String mName;
/// Event group that this event belongs to.
SFXFMODEventGroup* mGroup;
/// Next event in the group's event chain.
SFXFMODEvent* mSibling;
/// FMOD event handle when event is open. Client-side only.
FMOD_EVENT* mHandle;
///
Point2F mParameterRanges[ MaxNumParameters ];
///
F32 mParameterValues[ MaxNumParameters ];
/// Group ID for client net sync.
S32 mGroupId;
///
void _createParameters();
public:
///
SFXFMODEvent();
///
SFXFMODEvent( SFXFMODEventGroup* group, const String& name );
///
SFXFMODEvent( SFXFMODEventGroup* group, FMOD_EVENT* handle );
~SFXFMODEvent();
/// Create the event object on the FMOD device.
void acquire();
/// Release the event object on the FMOD device.
void release();
///
const String& getEventName() const { return mName; }
///
SFXFMODEventGroup* getEventGroup() const { return mGroup; }
///
String getQualifiedName() const;
///
bool isDataLoaded() const;
// SFXTrack.
virtual bool onAdd();
virtual void onRemove();
virtual bool preload( bool server, String& errorStr );
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
static void initPersistFields();
DECLARE_CONOBJECT( SFXFMODEvent );
DECLARE_CATEGORY( "SFX FMOD" );
DECLARE_DESCRIPTION( "An FMOD Designer event." );
};
#endif // !_SFXFMODEVENT_H_

View file

@ -0,0 +1,510 @@
//-----------------------------------------------------------------------------
// 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/fmod/sfxFMODEventGroup.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "sfx/fmod/sfxFMODEvent.h"
#include "sfx/fmod/sfxFMODProject.h"
#include "core/stream/bitStream.h"
#include "console/engineAPI.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXFMODEventGroup );
ConsoleDocClass( SFXFMODEventGroup,
"@brief A group of events in an imported FMOD Designer project.\n\n"
""
"@note Instances of this class \n\n"
"@ingroup SFXFMOD\n"
"@ingroup Datablocks"
);
//-----------------------------------------------------------------------------
SFXFMODEventGroup::SFXFMODEventGroup()
: mProject( NULL ),
mHandle( NULL ),
mParent( NULL ),
mChildren( NULL ),
mSibling( NULL ),
mLoadCount( 0 ),
mEvents( NULL ),
mNumEvents( 0 ),
mNumGroups( 0 ),
mParentId( 0 ),
mProjectId( 0 )
{
}
//-----------------------------------------------------------------------------
SFXFMODEventGroup::SFXFMODEventGroup( SFXFMODProject* project, FMOD_EVENTGROUP* handle, SFXFMODEventGroup* parent )
: mProject( project ),
mHandle( handle ),
mParent( parent ),
mChildren( NULL ),
mSibling( NULL ),
mLoadCount( 0 ),
mEvents( NULL ),
mNumEvents( 0 ),
mNumGroups( 0 ),
mParentId( 0 ),
mProjectId( 0 )
{
AssertFatal( project != NULL, "SFXFMODEventGroup::SFXFMODEventGroup - got a NULL project!" );
AssertFatal( handle != NULL, "SFXFMODEventGroup::SFXFMODEventGroup - got a NULL group handle!" );
// Fetch the name.
int index;
char* name = NULL;
SFXFMODDevice::smFunc->FMOD_EventGroup_GetInfo( handle, &index, &name );
mName = name;
}
//-----------------------------------------------------------------------------
SFXFMODEventGroup::~SFXFMODEventGroup()
{
AssertFatal( mEvents == NULL, "SFXFMODEventGroup::~SFXFMODEventGroup - group still has events attached" );
AssertFatal( mChildren == NULL, "SFXFMODEventGroup::~SFXFMODEventGroup - group still has subgroups attached" );
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::initPersistFields()
{
addGroup( "DO NOT MODIFY!!" );
addField( "fmodProject", TYPEID< SFXFMODProject >(), Offset( mProject, SFXFMODEventGroup ), "DO NOT MODIFY!!" );
addField( "fmodGroup", TYPEID< SFXFMODEventGroup >(), Offset( mParent, SFXFMODEventGroup ), "DO NOT MODIFY!!" );
addField( "fmodName", TypeRealString, Offset( mName, SFXFMODEventGroup ), "DO NOT MODIFY!!" );
endGroup( "DO NOT MODIFY!!" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXFMODEventGroup::onAdd()
{
if( !Parent::onAdd() )
return false;
if( !mProject )
{
Con::errorf( "SFXFMODEventGroup - not part of a project" );
return false;
}
if( mParent )
mParent->_addGroup( this );
mProject->_addGroup( this );
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::onRemove()
{
Parent::onRemove();
if( !mProject )
return;
release();
while( mEvents )
mEvents->deleteObject();
while( mChildren )
mChildren->deleteObject();
if( mParent )
mParent->_removeGroup( this );
mProject->_removeGroup( this );
}
//-----------------------------------------------------------------------------
bool SFXFMODEventGroup::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
if( !server )
{
if( mParentId != 0 && !Sim::findObject( mParentId, mParent ) )
{
errorStr = String::ToString( "SFXFMODEventGroup - parent group '%i' does not exist", mParentId );
return false;
}
if( !Sim::findObject( mProjectId, mProject ) )
{
errorStr = String::ToString( "SFXFMODEventGroup - project '%i' does not exist", mProjectId );
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::packData( BitStream* stream )
{
Parent::packData( stream );
stream->write( mName );
stream->writeRangedS32( mProject->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
if( stream->writeFlag( mParent ) )
stream->writeRangedS32( mParent->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
stream->read( &mName );
mProjectId = stream->readRangedS32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
if( stream->readFlag() )
mParentId = stream->readRangedS32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
else
mParentId = 0;
}
//-----------------------------------------------------------------------------
String SFXFMODEventGroup::getQualifiedName() const
{
if( mParent )
return String::ToString( "%s/%s", mParent->getQualifiedName().c_str(), mName.c_str() );
else
return mName;
}
//-----------------------------------------------------------------------------
bool SFXFMODEventGroup::isDataLoaded() const
{
// Check whether we or any of our parents has triggered a load.
for( const SFXFMODEventGroup* group = this; group != NULL; group = group->mParent )
if( group->mLoadCount > 0 )
return true;
return false;
}
//-----------------------------------------------------------------------------
bool SFXFMODEventGroup::loadData( bool samples, bool streams )
{
if( !mHandle )
acquire();
if( !mLoadCount )
{
FMOD_EVENT_RESOURCE resource;
if( samples && streams )
resource = FMOD_EVENT_RESOURCE_STREAMS_AND_SAMPLES;
else if( samples )
resource = FMOD_EVENT_RESOURCE_SAMPLES;
else if( streams )
resource = FMOD_EVENT_RESOURCE_STREAMS;
else
return true;
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_EventGroup_LoadEventData( mHandle, resource, FMOD_EVENT_DEFAULT );
if( result != FMOD_OK )
{
Con::errorf( "SFXFMODEventGroup::loadData - could not load data: %s", FMODResultToString( result ).c_str() );
return false;
}
SFXFMODDevice::instance()->updateMemUsageStats();
Con::printf( "SFXFMODProject - %s: Loaded data for group '%s'", mProject->getName(), getQualifiedName().c_str() );
}
mLoadCount ++;
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::freeData( bool force )
{
bool isLoaded = ( mLoadCount > 0 );
if( !isLoaded )
isLoaded = ( mParent ? mParent->isDataLoaded() : false );
else
{
if( force )
mLoadCount = 0;
else
-- mLoadCount;
}
if( !mLoadCount && isLoaded )
{
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_EventGroup_FreeEventData( mHandle, ( FMOD_EVENT* ) NULL, false );
if( result != FMOD_OK )
Con::errorf( "SFXFMODEventGroup - failed freeing event data: %s", FMODResultToString( result ).c_str() );
SFXFMODDevice::instance()->updateMemUsageStats();
Con::printf( "SFXFMODProject - %s: Cleared data for group '%s'", mProject->getName(), getQualifiedName().c_str() );
}
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::acquire( bool recursive )
{
// Make sure the project is acquired.
mProject->acquire();
// Acquire the group.
if( !mHandle )
{
if( mParent )
{
mParent->acquire();
SFXFMODDevice::smFunc->FMOD_EventGroup_GetGroup( mParent->mHandle, mName, true, &mHandle );
}
else
{
mProject->acquire();
SFXFMODDevice::smFunc->FMOD_EventProject_GetGroup( mProject->mHandle, mName, true, &mHandle );
}
}
// Acquite events and subgroups.
if( recursive )
{
for( SFXFMODEvent* event = mEvents; event != NULL; event = event->mSibling )
event->acquire();
for( SFXFMODEventGroup* group = mChildren; group != NULL; group = group->mSibling )
group->acquire( true );
}
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::release()
{
if( !mHandle )
return;
// Free the event data if we still have it loaded.
if( isDataLoaded() )
freeData( true );
// Release events.
for( SFXFMODEvent* event = mEvents; event != NULL; event = event->mSibling )
event->release();
// Release children.
for( SFXFMODEventGroup* child = mChildren; child != NULL; child = child->mSibling )
child->release();
// Release our handle.
freeData();
mHandle = NULL;
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::_load()
{
// Make sure we have the group open.
if( !mHandle )
acquire();
// Fetch info.
int numEvents;
int numGroups;
SFXFMODDevice::smFunc->FMOD_EventGroup_GetNumEvents( mHandle, &numEvents );
SFXFMODDevice::smFunc->FMOD_EventGroup_GetNumGroups( mHandle, &numGroups );
// Load events.
for( U32 i = 0; i < numEvents; ++ i )
{
FMOD_EVENT* handle;
if( SFXFMODDevice::smFunc->FMOD_EventGroup_GetEventByIndex( mHandle, i, FMOD_EVENT_INFOONLY, &handle ) == FMOD_OK )
{
SFXFMODEvent* event = new SFXFMODEvent( this, handle );
if( !isClientOnly() )
event->assignId();
event->registerObject( String::ToString( "%s_%s", getName(), FMODEventPathToTorqueName( event->getEventName() ).c_str() ) );
if( isClientOnly() )
Sim::getRootGroup()->addObject( event );
}
}
// Load subgroups.
for( U32 i = 0; i < numGroups; ++ i )
{
FMOD_EVENTGROUP* handle;
if( SFXFMODDevice::smFunc->FMOD_EventGroup_GetGroupByIndex( mHandle, i, true, &handle ) == FMOD_OK )
{
SFXFMODEventGroup* group = new SFXFMODEventGroup( mProject, handle, this );
if( !isClientOnly() )
group->assignId();
group->registerObject( String::ToString( "%s_%s", getName(), FMODEventPathToTorqueName( group->getGroupName() ).c_str() ) );
if( isClientOnly() )
Sim::getRootGroup()->addObject( group );
group->_load();
}
}
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::_addEvent( SFXFMODEvent* event )
{
event->mSibling = mEvents;
mEvents = event;
mNumEvents ++;
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::_removeEvent( SFXFMODEvent* event )
{
if( mEvents == event )
{
mEvents = event->mSibling;
event->mSibling = NULL;
mNumEvents --;
}
else
{
SFXFMODEvent* p = mEvents;
while( p != NULL && p->mSibling != event )
p = p->mSibling;
if( p )
{
p->mSibling = event->mSibling;
event->mSibling = NULL;
mNumEvents --;
}
}
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::_addGroup( SFXFMODEventGroup* group )
{
group->mSibling = mChildren;
mChildren = group;
mNumGroups ++;
}
//-----------------------------------------------------------------------------
void SFXFMODEventGroup::_removeGroup( SFXFMODEventGroup* group )
{
if( mChildren == group )
{
mChildren = group->mSibling;
group->mSibling = NULL;
mNumGroups --;
}
else
{
SFXFMODEventGroup* p = mChildren;
while( p != NULL && p->mSibling != group )
p = p->mSibling;
if( p )
{
p->mSibling = group->mSibling;
group->mSibling = NULL;
mNumGroups --;
}
}
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXFMODEventGroup, isDataLoaded, bool, (),,
"Test whether the resource data for this group has been loaded.\n\n"
"@return True if the resource data for this group is currently loaded.\n" )
{
return object->isDataLoaded();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXFMODEventGroup, loadData, bool, ( bool loadStreams, bool loadSamples ), ( true, true ),
"Load the resource data for this group, if it has not already been loaded (either directly "
"or indirectly through a parent group).\n"
"This method works recursively and thus data for direct and indirect child groups to this group will be "
"loaded as well.\n\n"
"@param loadStreams Whether to open streams.\n"
"@param loadSamples Whether to load sample banks.\n"
"@return True if the data has been successfully loaded; false otherwise.\n\n"
"@see SFXFMODProject_resources" )
{
return object->loadData( loadSamples, loadStreams );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXFMODEventGroup, freeData, void, (),,
"Release the resource data for this group and its subgroups.\n\n"
"@see SFXFMODProject_resources" )
{
object->freeData();
}

View file

@ -0,0 +1,159 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODEVENTGROUP_H_
#define _SFXFMODEVENTGROUP_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#include "fmod_event.h"
class SFXFMODProject;
class SFXFMODEvent;
///
class SFXFMODEventGroup : public SimDataBlock
{
public:
typedef SimDataBlock Parent;
friend class SFXFMODProject;
friend class SFXFMODEvent; // mHandle
friend class SFXFMODEventSource; // mHandle
protected:
///
String mName;
///
U32 mNumEvents;
///
U32 mNumGroups;
///
SFXFMODProject* mProject;
///
SFXFMODEventGroup* mParent;
///
SFXFMODEventGroup* mChildren;
///
SFXFMODEventGroup* mSibling;
///
SFXFMODEvent* mEvents;
///
FMOD_EVENTGROUP* mHandle;
///
U32 mLoadCount;
/// Project ID for client net sync.
S32 mParentId;
/// Project ID for client net sync.
S32 mProjectId;
///
void _load();
///
void _addEvent( SFXFMODEvent* event );
///
void _addGroup( SFXFMODEventGroup* group );
///
void _removeEvent( SFXFMODEvent* event );
///
void _removeGroup( SFXFMODEventGroup* group );
public:
///
SFXFMODEventGroup();
///
SFXFMODEventGroup( SFXFMODProject* project, const String& name, SFXFMODEventGroup* parent = NULL );
///
SFXFMODEventGroup( SFXFMODProject* project, FMOD_EVENTGROUP* handle, SFXFMODEventGroup* parent = NULL );
~SFXFMODEventGroup();
/// Create the event group object on the FMOD device.
void acquire( bool recursive = false );
/// Release the event group object on the FMOD device.
void release();
///
const String& getGroupName() const { return mName; }
///
String getQualifiedName() const;
///
SFXFMODProject* getProject() const { return mProject; }
/// Return true if the event data for this group has been loaded.
bool isDataLoaded() const;
/// Load the event data for this group.
///
/// @note Loading is reference-counted.
bool loadData( bool samples = true, bool streams = true );
///
void freeData( bool force = false );
// SimDataBlock.
virtual bool onAdd();
virtual void onRemove();
virtual bool preload( bool server, String& errorStr );
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
static void initPersistFields();
DECLARE_CONOBJECT( SFXFMODEventGroup );
DECLARE_CATEGORY( "SFX FMOD" );
DECLARE_DESCRIPTION( "An event group in an FMOD Designer project." );
};
#endif // !_SFXFMODEVENTGROUP_H_

View file

@ -0,0 +1,337 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/fmod/sfxFMODEventSource.h"
#include "sfx/fmod/sfxFMODEvent.h"
#include "sfx/fmod/sfxFMODEventGroup.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "sfx/sfxDescription.h"
IMPLEMENT_CONOBJECT( SFXFMODEventSource );
ConsoleDocClass( SFXFMODEventSource,
"@brief A sound source controller playing an %FMOD Designer event (SFXFMODEvent).\n\n"
"%FMOD event sources are internally created by the sound system to play events from imported %FMOD Designer projects.\n\n"
"@note This class cannot be instantiated directly by the user. Instead, instances of SFXFMODEventSource will be "
"implicitly created by the sound system when playing an SFXFMODEvent.\n\n"
"@ingroup SFXFMOD\n"
);
//-----------------------------------------------------------------------------
SFXFMODEventSource::SFXFMODEventSource()
: mHandle( NULL )
{
SFXFMODDevice::instance()->smStatNumEventSources ++;
}
//-----------------------------------------------------------------------------
SFXFMODEventSource::SFXFMODEventSource( SFXFMODEvent* event )
: Parent( event ),
mHandle( NULL )
{
SFXFMODDevice::instance()->smStatNumEventSources ++;
// Make sure the group has its data loaded.
SFXFMODEventGroup* group = event->getEventGroup();
if( !group->loadData() )
return;
// Create an event instance.
if( SFXFMODDevice::smFunc->FMOD_EventGroup_GetEvent(
event->getEventGroup()->mHandle,
event->getEventName(),
FMOD_EVENT_DEFAULT,
&mHandle ) != FMOD_OK )
{
Con::errorf( "SFXFMODEventSource::SFXFMODEventSource - failed to open event '%s'", event->getQualifiedName().c_str() );
mHandle = NULL;
}
}
//-----------------------------------------------------------------------------
SFXFMODEventSource::~SFXFMODEventSource()
{
SFXFMODDevice::instance()->smStatNumEventSources --;
if( mHandle )
SFXFMODDevice::smFunc->FMOD_Event_Release( mHandle, true, true );
if( getEvent() )
getEvent()->getEventGroup()->freeData();
}
//-----------------------------------------------------------------------------
SFXFMODEventSource* SFXFMODEventSource::create( SFXFMODEvent* event )
{
AssertFatal( event != NULL, "SFXFMODEventSource::create - got a NULL event!" );
// Create the source.
SFXFMODEventSource* source = new SFXFMODEventSource( event );
if( source->mHandle )
source->registerObject();
else
{
delete source;
source = NULL;
}
return source;
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::play( F32 fadeInTime )
{
if( getStatus() == SFXStatusPlaying )
return;
if( isPaused() )
SFXFMODDevice::smFunc->FMOD_Event_SetPaused( mHandle, false );
else
{
AssertFatal( getEvent()->getEventGroup()->isDataLoaded(), "SFXFMODEventSource::play() - event data for group not loaded" );
if( fadeInTime != -1.f )
{
U32 fade = U32( fadeInTime * 1000.f );
SFXFMODDevice::smFunc->FMOD_Event_SetPropertyByIndex(
mHandle, FMOD_EVENTPROPERTY_FADEIN,
&fade, true
);
}
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_Event_Start( mHandle );
if( result != FMOD_OK )
{
Con::errorf( "SFXFMODEventSoure::play() - failed to start event: %s", FMODResultToString( result ).c_str() );
return;
}
}
mPlayTimer.start();
_setStatus( SFXStatusPlaying );
_play();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::stop( F32 fadeOutTime )
{
if( getStatus() == SFXStatusStopped )
return;
AssertFatal( mHandle, "SFXFMODEvent::stop() - event not acquired" );
bool immediate = ( fadeOutTime == 0.f );
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_Event_Stop( mHandle, immediate );
if( result != FMOD_OK )
Con::errorf( "SFXFMODEventSource::stop() - failed to stop event: %s", FMODResultToString( result ).c_str() );
mPlayTimer.stop();
_setStatus( SFXStatusStopped );
// Reset fade-in to default in case it got overwritten
// in play().
U32 fade = U32( mFadeInTime * 1000.f );
SFXFMODDevice::smFunc->FMOD_Event_SetPropertyByIndex(
mHandle, FMOD_EVENTPROPERTY_FADEIN,
&fade, true
);
_stop();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::pause( F32 fadeOutTime )
{
if( getStatus() != SFXStatusPlaying )
return;
SFXFMODDevice::smFunc->FMOD_Event_SetPaused( mHandle, true );
mPlayTimer.pause();
_setStatus( SFXStatusPaused );
_pause();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::setTransform( const MatrixF& transform )
{
Parent::setTransform( transform );
_update3DAttributes();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::setVelocity( const VectorF& velocity )
{
Parent::setVelocity( velocity );
_update3DAttributes();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_update3DAttributes()
{
FMOD_VECTOR position;
FMOD_VECTOR velocity;
FMOD_VECTOR orientation;
Point3F direction;
getTransform().getColumn( 1, &direction );
TorqueVectorToFMODVector( getTransform().getPosition(), position );
TorqueVectorToFMODVector( getVelocity(), velocity );
TorqueVectorToFMODVector( direction, orientation );
SFXFMODDevice::smFunc->FMOD_Event_Set3DAttributes( mHandle, &position, &velocity, &orientation );
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_updateStatus()
{
if( mStatus == SFXStatusPlaying )
{
if( !getEvent() )
_setStatus( SFXStatusStopped );
else
{
FMOD_EVENT_STATE state;
SFXFMODDevice::smFunc->FMOD_Event_GetState( mHandle, &state );
if( !( state & FMOD_EVENT_STATE_PLAYING ) )
_setStatus( SFXStatusStopped );
}
}
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_updateVolume( const MatrixF& listener )
{
F32 oldPreAttenuatedVolume = mPreAttenuatedVolume;
Parent::_updateVolume( listener );
if( oldPreAttenuatedVolume != mPreAttenuatedVolume )
SFXFMODDevice::smFunc->FMOD_Event_SetVolume( mHandle, mPreAttenuatedVolume );
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_updatePitch()
{
F32 oldEffectivePitch = mEffectivePitch;
Parent::_updatePitch();
if( mEffectivePitch != oldEffectivePitch )
SFXFMODDevice::smFunc->FMOD_Event_SetPitch( mHandle, mEffectivePitch - 1.0f, FMOD_EVENT_PITCHUNITS_RAW );
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_updatePriority()
{
//TODO
Parent::_updatePriority();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_setMinMaxDistance( F32 min, F32 max )
{
Parent::_setMinMaxDistance( min, max );
_update3DAttributes();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_setFadeTimes( F32 fadeInTime, F32 fadeOutTime )
{
Parent::_setFadeTimes( fadeInTime, fadeOutTime );
U32 fadeIn = U32( mFadeInTime * 1000.f );
SFXFMODDevice::smFunc->FMOD_Event_SetPropertyByIndex(
mHandle, FMOD_EVENTPROPERTY_FADEIN,
&fadeIn, true
);
U32 fadeOut = U32( mFadeOutTime * 1000.f );
SFXFMODDevice::smFunc->FMOD_Event_SetPropertyByIndex(
mHandle, FMOD_EVENTPROPERTY_FADEOUT,
&fadeOut, true
);
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume )
{
Parent::_setCone( innerAngle, outerAngle, outerVolume );
_update3DAttributes();
}
//-----------------------------------------------------------------------------
void SFXFMODEventSource::_onParameterEvent( SFXParameter* parameter, SFXParameterEvent event )
{
Parent::_onParameterEvent( parameter, event );
// If it's a value-change on a custom parameter,
// pass it along to FMOD.
if( getEvent()
&& event == SFXParameterEvent_ValueChanged
&& parameter->getChannel() == SFXChannelUser0 )
{
const char* name = parameter->getInternalName();
FMOD_EVENTPARAMETER* fmodParameter;
if( SFXFMODDevice::smFunc->FMOD_Event_GetParameter( mHandle, name, &fmodParameter ) != FMOD_OK )
{
Con::errorf( "SFXFMODEventSource::_onParameterEvent - could not access parameter '%s' of event '%s'",
name, getEvent()->getQualifiedName().c_str() );
return;
}
SFXFMODDevice::smFunc->FMOD_EventParameter_SetValue( fmodParameter, parameter->getValue() );
}
}

View file

@ -0,0 +1,101 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODEVENTSOURCE_H_
#define _SFXFMODEVENTSOURCE_H_
#ifndef _SFXSOURCE_H_
#include "sfx/sfxSource.h"
#endif
#include "fmod_event.h"
class SFXFMODEvent;
/// An SFXSource that controls the playback of an SFXFMODEvent.
///
/// SFXFMODEvents can be played back directly through their console methods.
/// However, this class integrates them with the remaining SFX system and makes
/// events usable wherever SFX tracks are usable though with the important
/// distinction that there can only ever be a single source for a given event.
///
/// Note that calling playback methods directly on an event will cause a source
/// for the event to be created if there is not already one.
///
/// Be aware that using fade-outs in events in combination with play-once sources
/// does not work well at the moment.
///
class SFXFMODEventSource : public SFXSource
{
public:
typedef SFXSource Parent;
protected:
/// The event instance handle for this source.
FMOD_EVENT* mHandle;
///
SFXFMODEventSource( SFXFMODEvent* event );
/// Update 3D position, velocity, and orientation from current source transform.
void _update3DAttributes();
// SFXSource.
virtual void _updateStatus();
virtual void _updateVolume( const MatrixF& listener );
virtual void _updatePitch();
virtual void _updatePriority();
virtual void _onParameterEvent( SFXParameter* parameter, SFXParameterEvent event );
virtual void _setMinMaxDistance( F32 min, F32 max );
virtual void _setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
virtual void _setFadeTimes( F32 fadeInTime, F32 fadeOutTime );
public:
///
SFXFMODEventSource();
virtual ~SFXFMODEventSource();
/// Return the FMOD event object that is being played back by this source.
SFXFMODEvent* getEvent() const { return ( SFXFMODEvent* ) mTrack.getPointer(); }
/// Create a new source for the given event.
static SFXFMODEventSource* create( SFXFMODEvent* event );
// SFXSource.
virtual void play( F32 fadeInTime = -1.f ); // fadeInTime ignored when resuming from paused
virtual void stop( F32 fadeOutTime = -1.f ); // fadeOutTime!=0 ignored
virtual void pause( F32 fadeOutTime = -1.f ); // fadeOutTime currently ignored
virtual void setTransform( const MatrixF& transform );
virtual void setVelocity( const VectorF& velocity );
DECLARE_CONOBJECT( SFXFMODEventSource );
DECLARE_CATEGORY( "SFX FMOD" );
DECLARE_DESCRIPTION( "An SFX source controlling the playback of an FMOD Designer event." );
};
#endif // !_SFXFMODEVENTSOURCE_H_

View file

@ -0,0 +1,37 @@
//-----------------------------------------------------------------------------
// 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/fmod/sfxFMODPlugin.h"
#include "sfx/fmod/sfxFMODEvent.h"
#include "sfx/fmod/sfxFMODEventSource.h"
//-----------------------------------------------------------------------------
SFXSource* SFXFMODPlugin::createSource( SFXTrack* track )
{
SFXFMODEvent* event = dynamic_cast< SFXFMODEvent* >( track );
if( !event )
return NULL;
return SFXFMODEventSource::create( event );
}

View file

@ -0,0 +1,48 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODPLUGIN_H_
#define _SFXFMODPLUGIN_H_
#ifndef _SFXSYSTEM_H_
#include "sfx/sfxSystem.h"
#endif
/// SFXSystem plugin that adds the capability to create SFXSources for
/// Designer SFXFMODEvents.
///
/// The plugin will only be installed if an FMOD device has been created.
/// While SFXFMODEvents may be constructed without an FMOD device, trying
/// to play such an event then will result in an error.
///
class SFXFMODPlugin : public SFXSystemPlugin
{
public:
typedef SFXSystemPlugin Parent;
///
virtual SFXSource* createSource( SFXTrack* track );
};
#endif // !_SFXFMODPLUGIN_H_

View file

@ -0,0 +1,493 @@
//-----------------------------------------------------------------------------
// 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/fmod/sfxFMODProject.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "sfx/fmod/sfxFMODEvent.h"
#include "sfx/fmod/sfxFMODEventGroup.h"
#include "sfx/sfxDescription.h"
#include "core/stringTable.h"
#include "core/volume.h"
#include "core/util/path.h"
#include "core/stream/fileStream.h"
#include "core/stream/bitStream.h"
#include "core/util/safeDelete.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXFMODProject );
ConsoleDocClass( SFXFMODProject,
"@brief An FMOD Designer project loaded into Torque.\n\n"
"@section SFXFMODProject_resources Resource Loading\n\n"
"@ingroup SFXFMOD\n"
"@ingroup Datablocks"
);
//-----------------------------------------------------------------------------
SFXFMODProject::SFXFMODProject()
: mHandle( NULL ),
mRootGroups( NULL )
{
VECTOR_SET_ASSOCIATION( mGroups );
VECTOR_SET_ASSOCIATION( mEvents );
SFX->getEventSignal().notify( this, &SFXFMODProject::_onSystemEvent );
}
//-----------------------------------------------------------------------------
SFXFMODProject::~SFXFMODProject()
{
AssertFatal( mGroups.empty(), "SFXFMODProject::~SFXFMODProject - project still has groups attached" );
AssertFatal( mEvents.empty(), "SFXFMODProject::~SFXFMODProject - project still has events attached" );
if( SFX )
SFX->getEventSignal().remove( this, &SFXFMODProject::_onSystemEvent );
}
//-----------------------------------------------------------------------------
void SFXFMODProject::initPersistFields()
{
addGroup( "FMOD" );
addField( "fileName", TypeStringFilename, Offset( mFileName, SFXFMODProject ), "The compiled .fev file from FMOD Designer." );
addField( "mediaPath", TypeStringFilename, Offset( mMediaPath, SFXFMODProject ), "Path to the media files; if unset, defaults to project directory." );
endGroup( "FMOD" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXFMODProject::onAdd()
{
if( !Parent::onAdd() )
return false;
// If this is a non-networked datablock, load the
// project data now.
if( isClientOnly() && !_load() )
return false;
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODProject::onRemove()
{
Parent::onRemove();
_clear();
}
//-----------------------------------------------------------------------------
bool SFXFMODProject::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
if( server )
{
if( mFileName.isEmpty() )
{
errorStr = String::ToString( "SFXFMODProject::preload - no filename set on %i (%s)",
getId(), getName() );
return false;
}
if( mGroups.empty() || mEvents.empty() )
_load();
release();
}
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODProject::packData( BitStream* stream )
{
Parent::packData( stream );
stream->write( mFileName );
stream->write( mMediaPath );
}
//-----------------------------------------------------------------------------
void SFXFMODProject::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
stream->read( &mFileName );
stream->read( &mMediaPath );
}
//-----------------------------------------------------------------------------
void SFXFMODProject::_onSystemEvent( SFXSystemEventType event )
{
switch( event )
{
case SFXSystemEvent_DestroyDevice:
// If the FMOD device is being destroyed,
// release all our data.
if( SFXFMODDevice::instance() )
release();
break;
default:
break;
}
}
//-----------------------------------------------------------------------------
void SFXFMODProject::_clear()
{
release();
for( U32 i = 0; i < mGroups.size(); ++ i )
if( !mGroups[ i ]->isRemoved() )
mGroups[ i ]->deleteObject();
mGroups.clear();
mEvents.clear();
mRootGroups = NULL;
}
//-----------------------------------------------------------------------------
bool SFXFMODProject::_load()
{
const Torque::Path eventScriptFileName = mFileName + ".cs";
const Torque::Path eventScriptFileNameDSO = eventScriptFileName + ".dso";
const bool eventScriptFileExists = Torque::FS::IsFile( eventScriptFileName );
const bool eventScriptFileDSOExists = Torque::FS::IsFile( eventScriptFileNameDSO );
// Check if we need to (re-)generate the event script file.
bool needToGenerateEventScriptFile = false;
if( ( !eventScriptFileExists && !eventScriptFileDSOExists )
|| ( Torque::FS::CompareModifiedTimes( mFileName, eventScriptFileName ) > 0
|| Torque::FS::CompareModifiedTimes( mFileName, eventScriptFileNameDSO ) > 0 ) )
needToGenerateEventScriptFile = true;
// If we need to generate, check if we can.
SFXFMODDevice* fmodDevice = SFXFMODDevice::instance();
if( needToGenerateEventScriptFile && !fmodDevice )
{
// If we have neither FMOD nor the event scripts (even if outdated),
// there's nothing we can do.
if( !eventScriptFileExists && !eventScriptFileDSOExists )
{
Con::errorf( "SFXFMODProject::_load() - event script for '%s' does not exist and device is not FMOD; load this project under FMOD first",
mFileName.c_str() );
return false;
}
// Use the oudated versions.
Con::warnf( "SFXMODProject::_load() - event script for '%s' is outdated and device is not FMOD; event data may not match .fev contents",
mFileName.c_str() );
needToGenerateEventScriptFile = false;
}
// If we don't need to regenerate, try executing the event script now.
if( !needToGenerateEventScriptFile )
{
if( ( eventScriptFileExists || eventScriptFileDSOExists )
&& !Con::evaluatef( "exec( \"%s\" );", eventScriptFileName.getFullPath().c_str() ) )
{
Con::errorf( "SFXFMODProject::_load() - failed to execute event script for '%s'%s",
mFileName.c_str(),
fmodDevice != NULL ? "; trying to regenerate" : ""
);
if( !fmodDevice )
return false;
needToGenerateEventScriptFile = true;
}
else
Con::printf( "SFXFMODProject - %s: Loaded event script", getName() );
}
// If we need to generate the event script file,
// load the FMOD project now and then emit the file.
if( needToGenerateEventScriptFile )
{
// Try to load the project.
acquire();
if( !mHandle )
return false;
// Get the project info.
FMOD_EVENT_PROJECTINFO info;
int numEvents;
int numGroups;
SFXFMODDevice::smFunc->FMOD_EventProject_GetInfo( mHandle, &info );
SFXFMODDevice::smFunc->FMOD_EventProject_GetNumEvents( mHandle, &numEvents );
SFXFMODDevice::smFunc->FMOD_EventProject_GetNumGroups( mHandle, &numGroups );
Con::printf( "SFXFMODProject - %s: Loading '%s' from '%s' (index: %i, events: %i, groups: %i)",
getName(), info.name, mFileName.c_str(), info.index, numEvents, numGroups );
// Load the root groups.
for( U32 i = 0; i < numGroups; ++ i )
{
FMOD_EVENTGROUP* group;
if( SFXFMODDevice::smFunc->FMOD_EventProject_GetGroupByIndex( mHandle, i, true, &group ) == FMOD_OK )
{
SFXFMODEventGroup* object = new SFXFMODEventGroup( this, group );
object->mSibling = mRootGroups;
mRootGroups = object;
String qualifiedName = FMODEventPathToTorqueName( object->getQualifiedName() );
if( !isClientOnly() )
object->assignId();
object->registerObject( String::ToString( "%s_%s", getName(), qualifiedName.c_str() ) );
if( isClientOnly() )
Sim::getRootGroup()->addObject( object );
object->_load();
}
}
// Create the event script file.
FileStream stream;
if( !stream.open( eventScriptFileName.getFullPath(), Torque::FS::File::Write ) )
{
Con::errorf( "SFXFMODProject::_load - could not create event script file for '%s'", mFileName.c_str() );
return true; // Don't treat as failure.
}
// Write a header.
stream.writeText( String::ToString( "// This file has been auto-generated from '%s'\n", mFileName.c_str() ) );
stream.writeText( "// Do not edit this file manually and do not move it away from the Designer file.\n\n" );
// Write the group objects.
for( U32 i = 0; i < mGroups.size(); ++ i )
{
mGroups[ i ]->write( stream, 0 );
stream.writeText( "\n" );
}
// Write the event objects along with their
// SFXDescriptions.
for( U32 i = 0; i < mEvents.size(); ++ i )
{
mEvents[ i ]->getDescription()->write( stream, 0 );
mEvents[ i ]->write( stream, 0 );
stream.writeText( "\n" );
}
Con::printf( "SFXFMODProject - %s: Generated event script '%s'", getName(), eventScriptFileName.getFullPath().c_str() );
}
return true;
}
//-----------------------------------------------------------------------------
void SFXFMODProject::acquire( bool recursive )
{
// Load the project file.
if( !mHandle )
{
FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_EventSystem_Load(
SFXFMODDevice::smEventSystem,
mFileName.c_str(),
( FMOD_EVENT_LOADINFO* ) 0,
&mHandle
);
if( result != FMOD_OK )
{
Con::errorf( "SFXFMODProject::acquire - could not load '%s' (%s)",
mFileName.c_str(), FMODResultToString( result ).c_str() );
mHandle = NULL;
return;
}
Con::printf( "SFXFMODProject - %s: Opened project '%s'", getName(), mFileName.c_str() );
// Set the media path.
String mediaPath;
if( !mMediaPath.isEmpty() )
{
mediaPath = mMediaPath;
if( mediaPath[ mediaPath.length() - 1 ] != '/' )
mediaPath += '/';
}
else
{
// Set to project directory.
Torque::Path path = mFileName;
if( path.getRoot().isEmpty() )
path.setRoot( "game" );
path.setFileName( "" );
path.setExtension( "" );
mediaPath = path.getFullPath() + '/';
}
SFXFMODDevice::smFunc->FMOD_EventSystem_SetMediaPath(
SFXFMODDevice::smEventSystem,
mediaPath.c_str()
);
}
// Acquire the root groups.
if( recursive )
for( SFXFMODEventGroup* group = mRootGroups; group != NULL; group = group->mSibling )
group->acquire( true );
SFXFMODDevice::instance()->updateMemUsageStats();
}
//-----------------------------------------------------------------------------
void SFXFMODProject::release()
{
if( !mHandle )
return;
Con::printf( "SFXFMODProject - %s: Closing project '%s'",
getName(), mFileName.c_str() );
// Clear media path.
SFXFMODDevice::smFunc->FMOD_EventSystem_SetMediaPath(
SFXFMODDevice::smEventSystem, "" );
// Release the root groups.
for( SFXFMODEventGroup* group = mRootGroups; group != NULL; group = group->mSibling )
group->release();
// Release the project.
SFXFMODDevice::smFunc->FMOD_EventProject_Release( mHandle );
mHandle = NULL;
SFXFMODDevice::instance()->updateMemUsageStats();
}
//-----------------------------------------------------------------------------
void SFXFMODProject::_addEvent( SFXFMODEvent* event )
{
mEvents.push_back( event );
}
//-----------------------------------------------------------------------------
void SFXFMODProject::_addGroup( SFXFMODEventGroup* group )
{
mGroups.push_back( group );
}
//-----------------------------------------------------------------------------
void SFXFMODProject::_removeEvent( SFXFMODEvent* event )
{
for( U32 i = 0; i < mEvents.size(); ++ i )
if( mEvents[ i ] == event )
{
mEvents.erase( i );
break;
}
}
//-----------------------------------------------------------------------------
void SFXFMODProject::_removeGroup( SFXFMODEventGroup* group )
{
// Remove from group array.
for( U32 i = 0; i < mGroups.size(); ++ i )
if( mGroups[ i ] == group )
{
mGroups.erase( i );
break;;
}
// Unlink if it's a root group.
if( !group->mParent )
{
if( group == mRootGroups )
{
mRootGroups = group->mSibling;
group->mSibling = NULL;
}
else
{
SFXFMODEventGroup* p = mRootGroups;
while( p && p->mSibling != group )
p = p->mSibling;
if( p )
{
p->mSibling = group->mSibling;
group->mSibling = NULL;
}
}
}
}

View file

@ -0,0 +1,162 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODPROJECT_H_
#define _SFXFMODPROJECT_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _SFXSYSTEM_H_
#include "sfx/sfxSystem.h"
#endif
#include "fmod_event.h"
class SFXFMODEvent;
class SFXFMODEventGroup;
class SimGroup;
/// Datablock that loads an FMOD Designer project.
///
/// All events in the project are automatically made available as SFXFMODEvent track
/// datablock instances. Each event object is automatically named by substituting
/// the slashes in its fully qualified name with underscores and preprending the project
/// name to this; event 'group1/group2/event' in the SFXFMODProject instance called
/// 'project', for example, will be available as a TorqueScript object called
/// 'project_group1_group2_event'.
///
/// This class also works in a client-server environment where the server is
/// not running FMOD. The event objects are cached in an auto-generated TorqueScript
/// file alongside the .fev project file (x/y.fev -> x/y.fev.cs) which, when available
/// and up-to-date, does not require FMOD for the server-side objects to correctly
/// initialize.
///
/// To establish good loading behavior and for good memory management, it is necessary to
/// wisely distribute events to groups and to manually pre-load groups. The best solution
/// probably is to have one group of common events that is loaded during game startup and
/// then have one event group for each level in the game that is only loaded for the
/// duration of its particular level.
///
/// SFXFMODProject will propagate it's networking model to all its contents. This means
/// that if the project is a non-networked datablock, then all event groups, events, and
/// descriptions contained in the project will also be non-networked datablocks.
///
/// It usually makes the most sense to use non-networked ("client-only") datablocks as
/// otherwise the FMOD datablocks will be purged on each mission load.
///
/// @note Only one project's music data can ever be loaded at any one time.
/// Usually you wouldn't want more than a single SFXFMODProject instance in your game
/// data. Also, only a single media path can be set through the designer API so when
/// loading multiple projects, note that each project will set the media path to its
/// own directory. For data loading to work, all project thus need to be placed in
/// the same directory.
///
class SFXFMODProject : public SimDataBlock
{
public:
typedef SimDataBlock Parent;
friend class SFXFMODEventGroup; // _addGroup
friend class SFXFMODEvent; // _addEvent
protected:
///
String mFileName;
///
String mMediaPath;
///
SFXFMODEventGroup* mRootGroups;
/// A flat list of all the groups in this projet.
Vector< SFXFMODEventGroup* > mGroups;
/// A flat list of all the events in the project.
Vector< SFXFMODEvent* > mEvents;
///
FMOD_EVENTPROJECT* mHandle;
///
void _onSystemEvent( SFXSystemEventType event );
///
void _clear();
///
bool _load();
///
void _addEvent( SFXFMODEvent* event );
///
void _addGroup( SFXFMODEventGroup* group );
///
void _removeEvent( SFXFMODEvent* event );
///
void _removeGroup( SFXFMODEventGroup* group );
public:
///
SFXFMODProject();
virtual ~SFXFMODProject();
///
void acquire( bool recursive = false );
///
void release();
///
const String& getFileName() const { return mFileName; }
// SimDataBlock.
virtual bool onAdd();
virtual void onRemove();
virtual bool preload( bool server, String& errorStr );
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
static void initPersistFields();
DECLARE_CONOBJECT( SFXFMODProject );
DECLARE_CATEGORY( "SFX FMOD" );
DECLARE_DESCRIPTION( "An FMOD Designer project." );
};
#endif // !_SFXFMODPROJECT_H_

View file

@ -0,0 +1,403 @@
//-----------------------------------------------------------------------------
// 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/sfxProvider.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "core/util/safeRelease.h"
#include "console/console.h"
#include "core/util/safeDelete.h"
#include "core/module.h"
#include "console/consoleTypes.h"
class SFXFMODProvider : public SFXProvider
{
public:
SFXFMODProvider()
: SFXProvider( "FMOD" )
{
Con::addVariable( "$SFX::Device::fmodNumEventSources", TypeS32, &SFXFMODDevice::smStatNumEventSources,
"The current number of SFXFMODEventSource instances in the system.\n"
"This tells the number of sounds in the system that are currently playing FMOD Designer events.\n\n"
"@note Only relevant if an %FMOD sound device is used.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$SFX::Device::fmodCoreMem", TypeS32, &SFXFMODDevice::smStatMemUsageCore,
"Current number of bytes allocated by the core %FMOD sound system.\n\n"
"@note Only relevant if an %FMOD sound device is used.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$SFX::Device::fmodEventMem", TypeS32, &SFXFMODDevice::smStatMemUsageEvents,
"Current number of bytes allocated by the %FMOD Designer event system.\n\n"
"@note Only relevant if an %FMOD sound device is used and the FMOD event DLL is loaded.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$pref::SFX::FMOD::disableSoftware", TypeBool, &SFXFMODDevice::smPrefDisableSoftware,
"Whether to disable the %FMOD software mixer to conserve memory.\n"
"All sounds not created with SFXDescription::useHardware or using DSP effects will fail to load.\n\n"
"@note Only applies when using an %FMOD sound device.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$pref::SFX::FMOD::useSoftwareHRTF", TypeBool, &SFXFMODDevice::smPrefUseSoftwareHRTF,
"Whether to enable HRTF in %FMOD's software mixer.\n"
"This will add a lowpass filter effect to the DSP effect chain of all sounds mixed in software.\n\n"
"@note Only applies when using an %FMOD sound device.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$pref::SFX::FMOD::useSoftwareReverbLowmem", TypeBool, &SFXFMODDevice::smPrefUseSoftwareReverbLowmem,
"If true, %FMOD's SFX reverb is run using 22/24kHz delay buffers, halving the memory required.\n\n"
"@note Only applies when using an %FMOD sound device.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$pref::SFX::FMOD::enableProfile", TypeBool, &SFXFMODDevice::smPrefEnableProfile,
"Whether to enable support for %FMOD's profiler.\n\n"
"@note Only applies when using an %FMOD sound device.\n\n"
"@ref FMOD_profiler\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$pref::SFX::FMOD::DSoundHRTF", TypeString, &SFXFMODDevice::smPrefDSoundHRTF,
"The type of HRTF to use for hardware-mixed 3D sounds when %FMOD is using DirectSound for sound output "
"and hardware-acceleration is not available.\n\n"
"Options are\n"
"- \"none\": simple stereo panning/doppler/attenuation\n"
"- \"light\": slightly higher quality than \"none\"\n"
"- \"full\": full quality 3D playback\n\n"
"@note Only applies when using an %FMOD sound device.\n\n"
"@ingroup SFXFMOD" );
Con::addVariable( "$pref::SFX::FMOD::pluginPath", TypeString, &SFXFMODDevice::smPrefPluginPath,
"%Path to additional %FMOD plugins.\n\n"
"@note Only applies when using an %FMOD sound device.\n\n"
"@ingroup SFXFMOD" );
}
virtual ~SFXFMODProvider();
protected:
FModFNTable mFMod;
struct FModDeviceInfo : SFXDeviceInfo
{
FMOD_CAPS mCaps;
FMOD_SPEAKERMODE mSpeakerMode;
};
void init();
bool _createSystem();
public:
SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers );
};
MODULE_BEGIN( FMOD )
MODULE_INIT_BEFORE( SFX )
MODULE_SHUTDOWN_AFTER( SFX )
SFXFMODProvider* mProvider;
MODULE_INIT
{
mProvider = new SFXFMODProvider;
}
MODULE_SHUTDOWN
{
delete mProvider;
}
MODULE_END;
//------------------------------------------------------------------------------
// Helper
bool fmodBindFunction( DLibrary *dll, void *&fnAddress, const char* name )
{
if( !dll )
return false;
fnAddress = dll->bind( name );
if (!fnAddress)
Con::warnf( "FMOD Loader: DLL bind failed for %s", name );
return fnAddress != 0;
}
//------------------------------------------------------------------------------
void SFXFMODProvider::init()
{
#ifdef TORQUE_FMOD_STATIC
// FMOD statically linked.
mFMod.isLoaded = true;
#define FMOD_FUNCTION(fn_name, fn_args) \
(*(void**)&mFMod.fn_name.fn) = &fn_name;
#ifndef TORQUE_FMOD_NO_EVENTS
mFMod.eventIsLoaded = true;
#define FMOD_EVENT_FUNCTION(fn_name, fn_args) \
(*(void**)&mFMod.fn_name.fn) = &fn_name;
#else
#define FMOD_EVENT_FUNCTION( fn_name, fn_args )
#endif
#include FMOD_FN_FILE
#undef FMOD_FUNCTION
#undef FMOD_EVENT_FUNCTION
#else
// FMOD dynamically linked.
const char* dllName;
const char* pDllName; // plugin-based DLL
const char* eventDllName;
#ifdef TORQUE_OS_WIN32
dllName = "fmodex.dll";
pDllName = "fmodexp.dll";
eventDllName = "fmod_event.dll";
#elif defined( TORQUE_OS_MAC )
dllName = "libfmodex.dylib";
pDllName = "libfmodexp.dylib";
eventDllName = "libfmodevent.dylib";
#else
# warning Need to set FMOD DLL filename for platform.
return;
#endif
// Grab the functions we'll want from the fmod DLL.
mFMod.dllRef = OsLoadLibrary( dllName );
// Try the plugin-based version.
if( !mFMod.dllRef )
mFMod.dllRef = OsLoadLibrary( pDllName );
if(!mFMod.dllRef)
{
Con::warnf( "SFXFMODProvider - Could not locate '%s' or '%s' - FMOD not available.", dllName, pDllName );
return;
}
mFMod.eventDllRef = OsLoadLibrary( eventDllName );
if(!mFMod.eventDllRef)
Con::warnf( "SFXFMODProvider - Could not locate %s - FMOD Designer intergration not available.", eventDllName );
mFMod.isLoaded = true;
mFMod.eventIsLoaded = true;
#define FMOD_FUNCTION(fn_name, fn_args) \
mFMod.isLoaded &= fmodBindFunction(mFMod.dllRef, *(void**)&mFMod.fn_name.fn, #fn_name);
#ifdef TORQUE_OS_WIN32
#define FMOD_EVENT_FUNCTION(fn_name, fn_args, export) \
mFMod.eventIsLoaded &= fmodBindFunction(mFMod.eventDllRef, *(void**)&mFMod.fn_name.fn, export);
#else
#define FMOD_EVENT_FUNCTION(fn_name, fn_args, export) \
mFMod.eventIsLoaded &= fmodBindFunction(mFMod.eventDllRef, *(void**)&mFMod.fn_name.fn, #fn_name);
#endif
#include FMOD_FN_FILE
#undef FMOD_FUNCTION
#undef FMOD_EVENT_FUNCTION
if(mFMod.isLoaded == false)
{
Con::warnf("SFXFMODProvider - Could not locate %s - FMOD not available.", dllName);
return;
}
if( !mFMod.eventIsLoaded && mFMod.eventDllRef )
Con::warnf("SFXFMODProvider - Could not load the %s - FMOD Designer integration not available.", eventDllName);
#endif
FMOD_RESULT res;
// Create the FMOD system object.
if( !_createSystem() )
return;
// Check that the Ex API version is OK.
unsigned int version;
res = mFMod.FMOD_System_GetVersion(SFXFMODDevice::smSystem, &version);
FModAssert(res, "SFXFMODProvider - Failed to get FMOD version!");
Con::printf( "SFXFMODProvider - FMOD Ex API version: %x.%x.%x",
( version & 0xffff0000 ) >> 16,
( version & 0x0000ff00 ) >> 8,
( version & 0x000000ff )
);
if(version < FMOD_VERSION)
{
Con::warnf("SFXFMODProvider - FMOD Ex API version in DLL is too old - FMOD not available.");
return;
}
// Check that the Designer API version is ok.
if( mFMod.eventIsLoaded )
{
res = mFMod.FMOD_EventSystem_GetVersion( SFXFMODDevice::smEventSystem, &version );
FModAssert(res, "SFXFMODProvider - Failed to get FMOD version!");
Con::printf( "SFXFMODProvider - FMOD Designer API version: %x.%x.%x",
( version & 0xffff0000 ) >> 16,
( version & 0x0000ff00 ) >> 8,
( version & 0x000000ff )
);
if( version < FMOD_EVENT_VERSION )
{
Con::errorf( "SFXFMODProvider - FMOD Designer API version in DLL is too old!" );
return;
}
}
// Now, enumerate our devices.
int numDrivers;
res = mFMod.FMOD_System_GetNumDrivers(SFXFMODDevice::smSystem, &numDrivers);
FModAssert(res, "SFXFMODProvider - Failed to get driver count - FMOD not available.");
char nameBuff[256];
for(S32 i=0; i<numDrivers; i++)
{
res = mFMod.FMOD_System_GetDriverInfo(SFXFMODDevice::smSystem, i, nameBuff, 256, ( FMOD_GUID* ) NULL);
if( res != FMOD_OK )
{
Con::errorf( "SFXFMODProvider - Failed to get driver name (%s)", FMODResultToString( res ).c_str() );
continue;
}
nameBuff[ 255 ] = '\0';
FMOD_CAPS caps;
FMOD_SPEAKERMODE speakerMode;
res = mFMod.FMOD_System_GetDriverCaps( SFXFMODDevice::smSystem, i, &caps, ( int* ) 0, &speakerMode );
if( res != FMOD_OK )
{
Con::errorf( "SFXFMODProvider - Failed to get driver caps (%s)", FMODResultToString( res ).c_str() );
continue;
}
// Great - got something - so add it to the list of options.
FModDeviceInfo *fmodInfo = new FModDeviceInfo();
fmodInfo->name = String( nameBuff );
fmodInfo->hasHardware = caps & FMOD_CAPS_HARDWARE;
fmodInfo->maxBuffers = 32;
fmodInfo->driver = String();
fmodInfo->mCaps = caps;
fmodInfo->mSpeakerMode = speakerMode;
mDeviceInfo.push_back(fmodInfo);
}
// Did we get any devices?
if ( mDeviceInfo.empty() )
{
Con::warnf( "SFXFMODProvider - No valid devices found - FMOD not available." );
return;
}
// TODO: FMOD_Memory_Initialize
#ifdef TORQUE_OS_XENON
const dsize_t memSz = 5 * 1024 * 1024;
void *memBuffer = XPhysicalAlloc( memSz, MAXULONG_PTR, 0, PAGE_READWRITE );
mFMod.FMOD_Memory_Initialize( memBuffer, memSz, FMOD_MEMORY_ALLOCCALLBACK(NULL), FMOD_MEMORY_REALLOCCALLBACK(NULL), FMOD_MEMORY_FREECALLBACK(NULL) );
#endif
// Wow, we made it - register the provider.
regProvider( this );
}
SFXFMODProvider::~SFXFMODProvider()
{
if( SFXFMODDevice::smEventSystem )
{
mFMod.FMOD_EventSystem_Release( SFXFMODDevice::smEventSystem );
SFXFMODDevice::smEventSystem = NULL;
SFXFMODDevice::smSystem = NULL;
}
else if( SFXFMODDevice::smSystem )
{
mFMod.FMOD_System_Release( SFXFMODDevice::smSystem );
SFXFMODDevice::smSystem = NULL;
}
}
SFXDevice* SFXFMODProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers )
{
FModDeviceInfo* info = dynamic_cast< FModDeviceInfo* >
( _findDeviceInfo( deviceName ) );
if( !info )
return NULL;
if( !SFXFMODDevice::smSystem && !_createSystem() )
return false;
SFXFMODDevice* device = new SFXFMODDevice(this, &mFMod, 0, info->name );
if( !device->_init() )
SAFE_DELETE( device );
return device;
}
bool SFXFMODProvider::_createSystem()
{
AssertFatal( !SFXFMODDevice::smEventSystem, "SFXFMODProvider::_createSystem() - event system already created!" );
AssertFatal( !SFXFMODDevice::smSystem, "SFXFMODProvider::_createSystem() - system already created!" );
if( mFMod.eventIsLoaded )
{
FMOD_RESULT res = mFMod.FMOD_EventSystem_Create( &SFXFMODDevice::smEventSystem );
if( res != FMOD_OK )
{
Con::errorf( "SFXFMODProvider - could not create the FMOD event system." );
return false;
}
res = mFMod.FMOD_EventSystem_GetSystemObject( SFXFMODDevice::smEventSystem, &SFXFMODDevice::smSystem );
if( res != FMOD_OK )
{
Con::errorf( "SFXFMODProvider - could not retrieve the FMOD system object." );
return false;
}
}
else
{
// Allocate the FMod system.
FMOD_RESULT res = mFMod.FMOD_System_Create( &SFXFMODDevice::smSystem );
if( res != FMOD_OK )
{
// Failed - deal with it!
Con::errorf("SFXFMODProvider - could not create the FMOD system.");
return false;
}
}
return true;
}

View file

@ -0,0 +1,303 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/fmod/sfxFMODVoice.h"
#include "sfx/fmod/sfxFMODBuffer.h"
#include "sfx/fmod/sfxFMODDevice.h"
#include "core/tAlgorithm.h"
SFXFMODVoice* SFXFMODVoice::create( SFXFMODDevice *device,
SFXFMODBuffer *buffer )
{
AssertFatal( device, "SFXFMODVoice::create() - Got null device!" );
AssertFatal( buffer, "SFXFMODVoice::create() - Got null buffer!" );
return new SFXFMODVoice( device, buffer );
}
SFXFMODVoice::SFXFMODVoice( SFXFMODDevice *device,
SFXFMODBuffer *buffer )
: Parent( buffer ),
mDevice( device ),
mChannel( NULL )
{
AssertFatal( device, "SFXFMODVoice::SFXFMODVoice() - No device assigned!" );
AssertFatal( buffer, "SFXFMODVoice::SFXFMODVoice() - No buffer assigned!" );
AssertFatal( _getBuffer()->mSound != NULL, "SFXFMODVoice::SFXFMODVoice() - No sound assigned!" );
}
SFXFMODVoice::~SFXFMODVoice()
{
_stop();
}
SFXStatus SFXFMODVoice::_status() const
{
if( mChannel )
{
FMOD_BOOL isTrue = false;
SFXFMODDevice::smFunc->FMOD_Channel_GetPaused( mChannel, &isTrue );
if ( isTrue )
return SFXStatusPaused;
SFXFMODDevice::smFunc->FMOD_Channel_IsPlaying( mChannel, &isTrue );
if ( isTrue )
return SFXStatusPlaying;
}
SFXFMODDevice::smFunc->FMOD_Channel_Stop( mChannel );
mChannel = NULL;
return SFXStatusStopped;
}
void SFXFMODVoice::_play()
{
if( !mChannel )
_assignChannel();
SFXFMODDevice::smFunc->FMOD_Channel_SetPaused( mChannel, false );
}
void SFXFMODVoice::_pause()
{
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_SetPaused( mChannel, true );
}
void SFXFMODVoice::_stop()
{
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_Stop(mChannel);
mChannel = NULL;
}
void SFXFMODVoice::_seek( U32 sample )
{
if( !mChannel )
_assignChannel();
SFXFMODDevice::smFunc->FMOD_Channel_SetPosition
( mChannel, sample, FMOD_TIMEUNIT_PCM );
}
bool SFXFMODVoice::_assignChannel()
{
AssertFatal( _getBuffer()->mSound != NULL, "SFXFMODVoice::_assignChannel() - No sound assigned!" );
// we start playing it now in the paused state, so that we can immediately set attributes that
// depend on having a channel (position, volume, etc). According to the FMod docs
// it is ok to do this.
bool success = SFXFMODDevice::smFunc->FMOD_System_PlaySound(
SFXFMODDevice::smSystem,
FMOD_CHANNEL_FREE,
_getBuffer()->mSound,
true,
&mChannel ) == FMOD_OK;
if( success )
{
SFXFMODDevice::smFunc->FMOD_Channel_SetMode( mChannel, mMode );
SFXFMODDevice::smFunc->FMOD_Channel_SetLoopCount( mChannel, mMode & FMOD_LOOP_NORMAL ? -1 : 0 );
if( mSetFlags.test( SET_Velocity ) )
SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, ( const FMOD_VECTOR* ) NULL, &mVelocity );
if( mSetFlags.test( SET_MinMaxDistance ) )
SFXFMODDevice::smFunc->FMOD_Channel_Set3DMinMaxDistance(mChannel, mMinDistance, mMaxDistance);
if( mSetFlags.test( SET_Transform ) )
{
SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, &mPosition, ( const FMOD_VECTOR* ) NULL );
SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeOrientation( mChannel, &mDirection );
}
if( mSetFlags.test( SET_Volume ) )
SFXFMODDevice::smFunc->FMOD_Channel_SetVolume(mChannel, mVolume);
if( mSetFlags.test( SET_Pitch ) )
SFXFMODDevice::smFunc->FMOD_Channel_SetFrequency( mChannel, mFrequency );
if( mSetFlags.test( SET_Cone ) )
SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeSettings(
mChannel,
mConeInnerAngle,
mConeOuterAngle,
mConeOuterVolume );
if( mSetFlags.test( SET_Priority ) )
SFXFMODDevice::smFunc->FMOD_Channel_SetPriority( mChannel, TorquePriorityToFMODPriority( mPriority ) );
if( mSetFlags.test( SET_Reverb ) )
SFXFMODDevice::smFunc->FMOD_Channel_SetReverbProperties( mChannel, &mReverb );
}
return success;
}
U32 SFXFMODVoice::_tell() const
{
if( !mChannel )
return 0;
U32 pos;
SFXFMODDevice::smFunc->FMOD_Channel_GetPosition( mChannel, &pos, ( FMOD_TIMEUNIT ) FMOD_TIMEUNIT_PCMBYTES );
return _getBuffer()->getSamplePos( pos );
}
void SFXFMODVoice::setMinMaxDistance( F32 min, F32 max )
{
if ( !( _getBuffer()->mMode & FMOD_3D ) )
return;
mMinDistance = min;
mMaxDistance = max;
mSetFlags.set( SET_MinMaxDistance );
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_Set3DMinMaxDistance(mChannel, mMinDistance, mMaxDistance);
}
void SFXFMODVoice::play( bool looping )
{
if( mBuffer->isStreaming() )
looping = true;
mMode = mDevice->get3dRollOffMode();
mMode |= (looping ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF);
Parent::play( looping );
}
void SFXFMODVoice::setVelocity( const VectorF& velocity )
{
if( !( _getBuffer()->mMode & FMOD_3D ) )
return;
// Note we have to do a handedness swap; see the
// listener update code in SFXFMODDevice for details.
mVelocity.x = velocity.x;
mVelocity.y = velocity.z;
mVelocity.z = velocity.y;
mSetFlags.set( SET_Velocity );
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, ( const FMOD_VECTOR* ) NULL, &mVelocity );
}
void SFXFMODVoice::setTransform( const MatrixF& transform )
{
if ( !( _getBuffer()->mMode & FMOD_3D ) )
return;
transform.getColumn( 3, (Point3F*)&mPosition );
transform.getColumn( 1, (Point3F*)&mDirection );
// Note we have to do a handedness swap; see the
// listener update code in SFXFMODDevice for details.
swap( mPosition.y, mPosition.z );
swap( mDirection.y, mDirection.z );
mSetFlags.set( SET_Transform );
if( mChannel )
{
// This can fail safe, so don't assert if it fails.
SFXFMODDevice::smFunc->FMOD_Channel_Set3DAttributes( mChannel, &mPosition, ( const FMOD_VECTOR* ) NULL );
SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeOrientation( mChannel, &mDirection );
}
}
void SFXFMODVoice::setVolume( F32 volume )
{
mVolume = volume;
mSetFlags.set( SET_Volume );
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_SetVolume( mChannel, volume );
}
void SFXFMODVoice::setPriority( F32 priority )
{
mPriority = priority;
mSetFlags.set( SET_Priority );
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_SetPriority( mChannel, TorquePriorityToFMODPriority( priority ) );
}
void SFXFMODVoice::setPitch( F32 pitch )
{
// if we do not know the frequency, we cannot change the pitch
F32 frequency = _getBuffer()->getFormat().getSamplesPerSecond();
if ( frequency == 0 )
return;
mFrequency = frequency * pitch;
mSetFlags.set( SET_Pitch );
// Scale the original frequency by the pitch factor.
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_SetFrequency(mChannel, mFrequency);
}
void SFXFMODVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume )
{
mConeInnerAngle = innerAngle;
mConeOuterAngle = outerAngle;
mConeOuterVolume = outerVolume;
mSetFlags.set( SET_Cone );
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_Set3DConeSettings(
mChannel,
mConeInnerAngle,
mConeOuterAngle,
mConeOuterVolume );
}
void SFXFMODVoice::setReverb( const SFXSoundReverbProperties& reverb )
{
dMemset( &mReverb, 0, sizeof( mReverb ) );
mReverb.Direct = reverb.mDirect;
mReverb.Room = reverb.mRoom;
mReverb.Flags = reverb.mFlags;
mSetFlags.set( SET_Reverb );
if( mChannel )
SFXFMODDevice::smFunc->FMOD_Channel_SetReverbProperties( mChannel, &mReverb );
}
bool SFXFMODVoice::isVirtual() const
{
if( mChannel )
{
FMOD_BOOL result;
SFXFMODDevice::smFunc->FMOD_Channel_IsVirtual( mChannel, &result );
return result;
}
else
return false;
}

View file

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// 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 _SFXFMODVOICE_H_
#define _SFXFMODVOICE_H_
#ifndef _SFXDEVICE_H_
#include "sfx/sfxDevice.h"
#endif
#ifndef _SFXVOICE_H_
#include "sfx/sfxVoice.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#include "fmod.h"
class SFXSource;
class SFXFMODBuffer;
class SFXFMODDevice;
class SFXFMODVoice : public SFXVoice
{
typedef SFXVoice Parent;
friend class SFXFMODBuffer;
protected:
SFXFMODDevice *mDevice;
mutable FMOD_CHANNEL *mChannel;
enum ESettings
{
SET_MinMaxDistance = BIT( 0 ),
SET_Velocity = BIT( 1 ),
SET_Transform = BIT( 2 ),
SET_Volume = BIT( 3 ),
SET_Pitch = BIT( 4 ),
SET_Cone = BIT( 5 ),
SET_Priority = BIT( 6 ),
SET_Reverb = BIT( 7 ),
};
BitSet32 mSetFlags;
FMOD_MODE mMode;
F32 mMinDistance;
F32 mMaxDistance;
F32 mVolume;
F32 mPriority;
F32 mFrequency;
F32 mConeInnerAngle;
F32 mConeOuterAngle;
F32 mConeOuterVolume;
FMOD_VECTOR mVelocity;
FMOD_VECTOR mPosition;
FMOD_VECTOR mDirection;
FMOD_REVERB_CHANNELPROPERTIES mReverb;
///
SFXFMODVoice( SFXFMODDevice *device,
SFXFMODBuffer *buffer );
// prep for playback
bool _assignChannel();
SFXFMODBuffer* _getBuffer() const { return ( SFXFMODBuffer* ) mBuffer.getPointer(); }
// SFXVoice.
virtual SFXStatus _status() const;
virtual void _play();
virtual void _pause();
virtual void _stop();
virtual void _seek( U32 sample );
virtual U32 _tell() const;
public:
///
static SFXFMODVoice* create( SFXFMODDevice *device,
SFXFMODBuffer *buffer );
///
virtual ~SFXFMODVoice();
/// SFXVoice
void setMinMaxDistance( F32 min, F32 max );
void play( bool looping );
void setVelocity( const VectorF& velocity );
void setTransform( const MatrixF& transform );
void setVolume( F32 volume );
void setPriority( F32 priority );
void setPitch( F32 pitch );
void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
void setReverb( const SFXSoundReverbProperties& reverb );
bool isVirtual() const;
};
#endif // _SFXFMODBUFFER_H_

View file

@ -0,0 +1,283 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifdef TORQUE_OGGVORBIS
#include "sfx/media/sfxVorbisStream.h"
#include "core/stream/stream.h"
#include "console/console.h"
SFXVorbisStream* SFXVorbisStream::create( Stream *stream )
{
SFXVorbisStream *sfxStream = new SFXVorbisStream();
if ( sfxStream->open( stream, true ) )
return sfxStream;
delete sfxStream;
return NULL;
}
SFXVorbisStream::SFXVorbisStream()
: mVF( NULL ),
mBytesRead( 0 )
{
}
SFXVorbisStream::SFXVorbisStream( const SFXVorbisStream& cloneFrom )
: Parent( cloneFrom )
{
if( !mStream->hasCapability( Stream::StreamPosition ) )
{
Con::errorf( "SFXVorbisStream::SFXVorbisStream() - Source stream does not allow seeking" );
return;
}
mStream->setPosition( 0 );
if( !_readHeader() )
{
Con::errorf( "SFXVorbisStream::SFXVorbisStream() - Opening Vorbis stream failed" );
return;
}
ov_pcm_seek( mVF, ov_pcm_tell( cloneFrom.mVF ) );
mBitstream = cloneFrom.mBitstream;
mBytesRead = cloneFrom.mBytesRead;
}
SFXVorbisStream::~SFXVorbisStream()
{
// We must call close from our own destructor
// and not the base class... as it causes a
// pure virtual runtime assertion.
close();
}
size_t SFXVorbisStream::_read_func( void *ptr, size_t size, size_t nmemb, void *datasource )
{
Stream *stream = reinterpret_cast<Stream*>( datasource );
// Stream::read() returns true if any data was
// read, so we must track the read bytes ourselves.
U32 startByte = stream->getPosition();
stream->read( size * nmemb, ptr );
U32 endByte = stream->getPosition();
// How many did we actually read?
U32 readBytes = ( endByte - startByte );
U32 readItems = readBytes / size;
return readItems;
}
int SFXVorbisStream::_seek_func( void *datasource, ogg_int64_t offset, int whence )
{
Stream *stream = reinterpret_cast<Stream*>( datasource );
U32 newPos = 0;
if ( whence == SEEK_CUR )
newPos = stream->getPosition() + (U32)offset;
else if ( whence == SEEK_END )
newPos = stream->getStreamSize() - (U32)offset;
else
newPos = (U32)offset;
return stream->setPosition( newPos ) ? 0 : -1;
}
long SFXVorbisStream::_tell_func( void *datasource )
{
Stream *stream = reinterpret_cast<Stream*>( datasource );
return stream->getPosition();
}
bool SFXVorbisStream::_openVorbis()
{
#if defined(TORQUE_OS_XENON)
// For some reason the datasource pointer passed to the callbacks is not the
// same as it is when passed in to ov_open_callbacks
#pragma message("There is a strange bug in ov_open_callbacks as it compiles on the Xbox360. Use FMOD resource loading.")
AssertWarn(false, "There is a strange bug in ov_open_callbacks as it compiles on the Xbox360. Use FMOD resource loading.");
return false;
#endif
mVF = new OggVorbis_File;
dMemset( mVF, 0, sizeof( OggVorbis_File ) );
const bool canSeek = mStream->hasCapability( Stream::StreamPosition );
ov_callbacks cb;
cb.read_func = _read_func;
cb.seek_func = canSeek ? _seek_func : NULL;
cb.close_func = NULL;
cb.tell_func = canSeek ? _tell_func : NULL;
// Open it.
int ovResult = ov_open_callbacks( mStream, mVF, NULL, 0, cb );
if( ovResult != 0 )
return false;
return true;
}
bool SFXVorbisStream::_readHeader()
{
if( !_openVorbis() )
return false;
// Fill in the format info.
const vorbis_info *vi = getInfo();
mFormat.set( vi->channels, vi->channels * 16, vi->rate );
// Set the sample count.
mSamples = getPcmTotal();
// Reset the bitstream.
mBitstream = 0;
return true;
}
void SFXVorbisStream::_close()
{
if ( !mVF )
return;
ov_clear( mVF );
delete mVF;
mVF = NULL;
mBitstream = -1;
}
const vorbis_info* SFXVorbisStream::getInfo( S32 link )
{
AssertFatal( mVF, "SFXVorbisStream::getInfo() - Stream is closed!" );
return ov_info( mVF, link );
}
const vorbis_comment* SFXVorbisStream::getComment( S32 link )
{
AssertFatal( mVF, "SFXVorbisStream::getComment() - Stream is closed!" );
return ov_comment( mVF, link );
}
U64 SFXVorbisStream::getPcmTotal( S32 link )
{
AssertFatal( mVF, "SFXVorbisStream::getInfo() - Stream is closed!" );
return ov_pcm_total( mVF, link );
}
S32 SFXVorbisStream::read( U8 *buffer,
U32 length,
S32 *bitstream )
{
AssertFatal( mVF, "SFXVorbisStream::read() - Stream is closed!" );
mBitstream = *bitstream;
#ifdef TORQUE_BIG_ENDIAN
static const int isBigEndian = 1;
#else
static const int isBigEndian = 0;
#endif
// Vorbis doesn't seem to like reading
// requests longer than this.
const U32 MAXREAD = 4096;
U32 bytesRead = 0;
U32 offset = 0;
U32 bytesToRead = 0;
// Since it only returns the result of one packet
// per call, you generally have to loop to read it all.
while( offset < length )
{
if ( ( length - offset ) < MAXREAD )
bytesToRead = length - offset;
else
bytesToRead = MAXREAD;
bytesRead = ov_read( mVF, (char*)buffer, bytesToRead, isBigEndian, 2, 1, bitstream );
if( bytesRead == 0 ) // EOF
return offset;
else if( bytesRead < 0 )
{
// We got an error... return the result.
return bytesRead;
}
offset += bytesRead;
buffer += bytesRead;
mBytesRead += bytesRead;
}
// Return the total data read.
return offset;
}
bool SFXVorbisStream::isEOS() const
{
return ( Parent::isEOS() || ( mStream && ov_pcm_tell( mVF ) == mSamples ) );
}
void SFXVorbisStream::reset()
{
AssertFatal( mVF, "SFXVorbisStream::reset() - Stream is closed!" );
// There's a bug in libvorbis 1.2.0 that will cause the
// ov_pcm_seek* functions to go into an infinite loop on
// some files (apparently if they contain Theora streams).
// Avoiding to seek when not necessary seems to do the trick
// but if it deadlocks here, that's why.
if( mBytesRead > 0 )
{
ov_pcm_seek_page( mVF, 0 );
mBitstream = 0;
mBytesRead = 0;
}
}
U32 SFXVorbisStream::getPosition() const
{
AssertFatal( mVF, "SFXVorbisStream::getPosition() - Stream is closed!" );
return U32( ov_pcm_tell( mVF ) ) * mFormat.getBytesPerSample();
}
void SFXVorbisStream::setPosition( U32 offset )
{
AssertFatal( mVF, "SFXVorbisStream::setPosition() - Stream is closed!" );
ov_pcm_seek( mVF, offset / mFormat.getBytesPerSample() );
}
U32 SFXVorbisStream::read( U8 *buffer, U32 length )
{
S32 result = read( buffer, length, &mBitstream );
if ( result < 0 )
return 0;
return result;
}
#endif // TORQUE_OGGVORBIS

View file

@ -0,0 +1,112 @@
//-----------------------------------------------------------------------------
// 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 _SFXVORBISSTREAM_H_
#define _SFXVORBISSTREAM_H_
#ifdef TORQUE_OGGVORBIS
#ifndef _SFXFILESTREAM_H_
# include "sfx/sfxFileStream.h"
#endif
#include "core/util/safeDelete.h"
#include "vorbis/vorbisfile.h"
/// An SFXFileStream that loads sample data from a Vorbis file.
class SFXVorbisStream : public SFXFileStream,
public IPositionable< U32 >
{
public:
typedef SFXFileStream Parent;
protected:
/// The vorbis file.
OggVorbis_File *mVF;
/// The current bitstream index.
S32 mBitstream;
/// Total number of bytes read from the Vorbis stream so far.
U32 mBytesRead;
///
bool _openVorbis();
// The ov_callbacks.
static size_t _read_func( void *ptr, size_t size, size_t nmemb, void *datasource );
static int _seek_func( void *datasource, ogg_int64_t offset, int whence );
static long _tell_func( void *datasource );
// SFXStream
virtual bool _readHeader();
virtual void _close();
public:
///
static SFXVorbisStream* create( Stream *stream );
///
SFXVorbisStream();
///
SFXVorbisStream( const SFXVorbisStream& cloneFrom );
virtual ~SFXVorbisStream();
///
const vorbis_info* getInfo( S32 link = -1 );
///
const vorbis_comment* getComment( S32 link = -1 );
///
// TODO: Deal with error cases... like for unseekable streams!
U64 getPcmTotal( S32 link = -1 );
///
S32 read( U8 *buffer,
U32 length,
S32 *bitstream );
// SFXStream
virtual void reset();
virtual U32 read( U8 *buffer, U32 length );
virtual bool isEOS() const;
virtual SFXStream* clone() const
{
SFXVorbisStream* stream = new SFXVorbisStream( *this );
if( !stream->mVF )
SAFE_DELETE( stream );
return stream;
}
// IPositionable
virtual U32 getPosition() const;
virtual void setPosition( U32 offset );
};
#endif // TORQUE_OGGVORBIS
#endif // _SFXVORBISSTREAM_H_

View file

@ -0,0 +1,299 @@
//-----------------------------------------------------------------------------
// 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/media/sfxWavStream.h"
#include "core/stream/stream.h"
#include "core/strings/stringFunctions.h"
/// WAV File-header
struct WAVFileHdr
{
U8 id[4];
U32 size;
U8 type[4];
};
//// WAV Fmt-header
struct WAVFmtHdr
{
U16 format;
U16 channels;
U32 samplesPerSec;
U32 bytesPerSec;
U16 blockAlign;
U16 bitsPerSample;
};
/// WAV FmtEx-header
struct WAVFmtExHdr
{
U16 size;
U16 samplesPerBlock;
};
/// WAV Smpl-header
struct WAVSmplHdr
{
U32 manufacturer;
U32 product;
U32 samplePeriod;
U32 note;
U32 fineTune;
U32 SMPTEFormat;
U32 SMPTEOffest;
U32 loops;
U32 samplerData;
struct
{
U32 identifier;
U32 type;
U32 start;
U32 end;
U32 fraction;
U32 count;
} loop[1];
};
/// WAV Chunk-header
struct WAVChunkHdr
{
U8 id[4];
U32 size;
};
SFXWavStream* SFXWavStream::create( Stream *stream )
{
SFXWavStream *sfxStream = new SFXWavStream();
if ( sfxStream->open( stream, true ) )
return sfxStream;
delete sfxStream;
return NULL;
}
SFXWavStream::SFXWavStream()
{
}
SFXWavStream::SFXWavStream( const SFXWavStream& cloneFrom )
: Parent( cloneFrom ),
mDataStart( cloneFrom.mDataStart )
{
}
SFXWavStream::~SFXWavStream()
{
// We must call close from our own destructor
// and not the base class... as it causes a
// pure virtual runtime assertion.
close();
}
void SFXWavStream::_close()
{
mDataStart = -1;
}
bool SFXWavStream::_readHeader()
{
// We read the wav chunks to gather than header info
// and find the start and end position of the data chunk.
mDataStart = -1;
WAVFileHdr fileHdr;
mStream->read( 4, &fileHdr.id[0] );
mStream->read( &fileHdr.size );
mStream->read( 4, &fileHdr.type[0] );
fileHdr.size=((fileHdr.size+1)&~1)-4;
WAVChunkHdr chunkHdr;
mStream->read( 4, &chunkHdr.id[0] );
mStream->read( &chunkHdr.size );
// Unread chunk data rounded up to nearest WORD.
S32 chunkRemaining = chunkHdr.size + ( chunkHdr.size & 1 );
WAVFmtHdr fmtHdr;
WAVFmtExHdr fmtExHdr;
WAVSmplHdr smplHdr;
dMemset(&fmtHdr, 0, sizeof(fmtHdr));
while ((fileHdr.size!=0) && (mStream->getStatus() != Stream::EOS))
{
// WAV format header chunk.
if ( !dStrncmp( (const char*)chunkHdr.id, "fmt ", 4 ) )
{
mStream->read(&fmtHdr.format);
mStream->read(&fmtHdr.channels);
mStream->read(&fmtHdr.samplesPerSec);
mStream->read(&fmtHdr.bytesPerSec);
mStream->read(&fmtHdr.blockAlign);
mStream->read(&fmtHdr.bitsPerSample);
if ( fmtHdr.format == 0x0001 )
{
mFormat.set( fmtHdr.channels, fmtHdr.bitsPerSample * fmtHdr.channels, fmtHdr.samplesPerSec );
chunkRemaining -= sizeof( WAVFmtHdr );
}
else
{
mStream->read(sizeof(WAVFmtExHdr), &fmtExHdr);
chunkRemaining -= sizeof(WAVFmtExHdr);
}
}
// WAV data chunk
else if (!dStrncmp((const char*)chunkHdr.id,"data",4))
{
// TODO: Handle these other formats in a more graceful manner!
if (fmtHdr.format==0x0001)
{
mDataStart = mStream->getPosition();
mStream->setPosition( mDataStart + chunkHdr.size );
chunkRemaining -= chunkHdr.size;
mSamples = chunkHdr.size / mFormat.getBytesPerSample();
}
else if (fmtHdr.format==0x0011)
{
//IMA ADPCM
}
else if (fmtHdr.format==0x0055)
{
//MP3 WAVE
}
}
// WAV sample header
else if (!dStrncmp((const char*)chunkHdr.id,"smpl",4))
{
// this struct read is NOT endian safe but it is ok because
// we are only testing the loops field against ZERO
mStream->read(sizeof(WAVSmplHdr), &smplHdr);
// This has never been hooked up and its usefulness is
// dubious. Do we really want the audio file overriding
// the SFXDescription setting?
//mLooping = ( smplHdr.loops ? true : false );
chunkRemaining -= sizeof(WAVSmplHdr);
}
// either we have unread chunk data or we found an unknown chunk type
// loop and read up to 1K bytes at a time until we have
// read to the end of this chunk
AssertFatal(chunkRemaining >= 0, "AudioBuffer::readWAV: remaining chunk data should never be less than zero.");
if ( chunkRemaining > 0 )
{
U32 pos = mStream->getPosition();
mStream->setPosition( pos + chunkRemaining );
chunkRemaining = 0;
}
fileHdr.size-=(((chunkHdr.size+1)&~1)+8);
// read next chunk header...
mStream->read(4, &chunkHdr.id[0]);
mStream->read(&chunkHdr.size);
// unread chunk data rounded up to nearest WORD
chunkRemaining = chunkHdr.size + (chunkHdr.size&1);
}
return ( mDataStart != -1 );
}
void SFXWavStream::reset()
{
AssertFatal( mStream, "SFXWavStream::reset() - Stream is null!" );
AssertFatal( mDataStart != -1, "SFXWavStream::seek() - Data start offset is invalid!" );
mStream->setPosition( mDataStart );
}
U32 SFXWavStream::getPosition() const
{
AssertFatal( mStream, "SFXWavStream::getPosition() - Stream is null!" );
return ( mStream->getPosition() - mDataStart );
}
void SFXWavStream::setPosition( U32 offset )
{
AssertFatal( mStream, "SFXWavStream::setPosition() - Stream is null!" );
offset -= offset % mFormat.getBytesPerSample();
const U32 dataLength = mSamples * mFormat.getBytesPerSample();
if( offset > dataLength )
offset = dataLength;
AssertFatal( mDataStart != -1, "SFXWavStream::getPosition() - Data start offset is invalid!" );
U32 byte = mDataStart + offset;
mStream->setPosition( byte );
}
U32 SFXWavStream::read( U8 *buffer, U32 bytes )
{
AssertFatal( mStream, "SFXWavStream::seek() - Stream is null!" );
// Read in even sample chunks.
bytes -= bytes % mFormat.getBytesPerSample();
// Read the data and determine how much we've read.
// FileStreams apparently report positions past
// the actual stream length, so manually cap the
// numbers here.
const U32 oldPosition = mStream->getPosition();
mStream->read( bytes, buffer );
U32 newPosition = mStream->getPosition();
const U32 maxPosition = getDataLength() + mDataStart;
if( newPosition > maxPosition )
newPosition = maxPosition;
const U32 numBytesRead = newPosition - oldPosition;
// TODO: Is it *just* 16 bit samples that needs to
// be flipped? What about 32 bit samples?
#ifdef TORQUE_BIG_ENDIAN
// We need to endian-flip 16-bit data.
if ( getFormat().getBytesPerChannel() == 2 )
{
U16 *ds = (U16*)buffer;
U16 *de = (U16*)(buffer+bytes);
while (ds<de)
{
*ds = convertLEndianToHost(*ds);
ds++;
}
}
#endif
return numBytesRead;
}

View file

@ -0,0 +1,80 @@
//-----------------------------------------------------------------------------
// 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 _SFXWAVSTREAM_H_
#define _SFXWAVSTREAM_H_
#ifndef _SFXFILESTREAM_H_
#include "sfx/sfxFileStream.h"
#endif
#include "core/util/safeDelete.h"
/// An SFXFileStream that loads sample data from a WAV file.
class SFXWavStream : public SFXFileStream,
public IPositionable< U32 >
{
public:
typedef SFXFileStream Parent;
protected:
/// The file position of the start of
/// the PCM data for fast reset().
U32 mDataStart;
// SFXFileStream
virtual bool _readHeader();
virtual void _close();
public:
///
static SFXWavStream* create( Stream *stream );
///
SFXWavStream();
///
SFXWavStream( const SFXWavStream& cloneFrom );
/// Destructor.
virtual ~SFXWavStream();
// SFXStream
virtual void reset();
virtual U32 read( U8 *buffer, U32 length );
virtual SFXStream* clone() const
{
SFXWavStream* stream = new SFXWavStream( *this );
if( !stream->mStream )
SAFE_DELETE( stream );
return stream;
}
// IPositionable
virtual U32 getPosition() const;
virtual void setPosition( U32 offset );
};
#endif // _SFXWAVSTREAM_H_

View file

@ -0,0 +1,43 @@
//-----------------------------------------------------------------------------
// 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/null/sfxNullBuffer.h"
#include "sfx/sfxInternal.h"
SFXNullBuffer::SFXNullBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
: Parent( stream, description, false )
{
mStatus = STATUS_Ready;
}
SFXNullBuffer::~SFXNullBuffer()
{
}
void SFXNullBuffer::write( SFXInternal::SFXStreamPacket* const* packets, U32 num )
{
// Should never really be called, but to be safe...
for( U32 i = 0; i < num; ++ i )
destructSingle( packets[ i ] );
}

View file

@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// 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 _SFXNULLBUFFER_H_
#define _SFXNULLBUFFER_H_
#ifndef _SFXBUFFER_H_
#include "sfx/sfxBuffer.h"
#endif
class SFXNullBuffer : public SFXBuffer
{
friend class SFXNullDevice;
typedef SFXBuffer Parent;
protected:
SFXNullBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
// SFXBuffer.
virtual void write( SFXInternal::SFXStreamPacket* const* packets, U32 num );
virtual void _flush() {}
public:
virtual ~SFXNullBuffer();
};
#endif // _SFXNULLBUFFER_H_

View file

@ -0,0 +1,74 @@
//-----------------------------------------------------------------------------
// 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/null/sfxNullDevice.h"
#include "sfx/null/sfxNullBuffer.h"
#include "sfx/sfxInternal.h"
SFXNullDevice::SFXNullDevice( SFXProvider* provider,
String name,
bool useHardware,
S32 maxBuffers )
: SFXDevice( name, provider, useHardware, maxBuffers )
{
mMaxBuffers = getMax( maxBuffers, 8 );
}
SFXNullDevice::~SFXNullDevice()
{
}
SFXBuffer* SFXNullDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
{
SFXNullBuffer* buffer = new SFXNullBuffer( stream, description );
_addBuffer( buffer );
return buffer;
}
SFXVoice* SFXNullDevice::createVoice( bool is3D, SFXBuffer *buffer )
{
// Don't bother going any further if we've
// exceeded the maximum voices.
if ( mVoices.size() >= mMaxBuffers )
return NULL;
AssertFatal( buffer, "SFXNullDevice::createVoice() - Got null buffer!" );
SFXNullBuffer* nullBuffer = dynamic_cast<SFXNullBuffer*>( buffer );
AssertFatal( nullBuffer, "SFXNullDevice::createVoice() - Got bad buffer!" );
SFXNullVoice* voice = new SFXNullVoice( nullBuffer );
if ( !voice )
return NULL;
_addVoice( voice );
return voice;
}
void SFXNullDevice::update()
{
// Do nothing. Prevent SFXDevice from running
// its thing.
}

View file

@ -0,0 +1,63 @@
//-----------------------------------------------------------------------------
// 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 _SFXNULLDEVICE_H_
#define _SFXNULLDEVICE_H_
class SFXProvider;
#ifndef _SFXDEVICE_H_
#include "sfx/sfxDevice.h"
#endif
#ifndef _SFXPROVIDER_H_
#include "sfx/sfxProvider.h"
#endif
#ifndef _SFXNULLBUFFER_H_
#include "sfx/null/sfxNullBuffer.h"
#endif
#ifndef _SFXNULLVOICE_H_
#include "sfx/null/sfxNullVoice.h"
#endif
class SFXNullDevice : public SFXDevice
{
typedef SFXDevice Parent;
public:
SFXNullDevice( SFXProvider* provider,
String name,
bool useHardware,
S32 maxBuffers );
virtual ~SFXNullDevice();
public:
// SFXDevice.
virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer );
virtual void update();
};
#endif // _SFXNULLDEVICE_H_

View file

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// 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/sfxProvider.h"
#include "sfx/null/sfxNullDevice.h"
#include "core/strings/stringFunctions.h"
#include "core/module.h"
class SFXNullProvider : public SFXProvider
{
public:
SFXNullProvider()
: SFXProvider( "Null" ) {}
virtual ~SFXNullProvider();
protected:
void addDeviceDesc( const String& name, const String& desc );
void init();
public:
SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers );
};
MODULE_BEGIN( SFXNull )
MODULE_INIT_BEFORE( SFX )
MODULE_SHUTDOWN_AFTER( SFX )
SFXNullProvider* mProvider;
MODULE_INIT
{
mProvider = new SFXNullProvider;
}
MODULE_SHUTDOWN
{
delete mProvider;
}
MODULE_END;
void SFXNullProvider::init()
{
regProvider( this );
addDeviceDesc( "Null", "SFX Null Device" );
}
SFXNullProvider::~SFXNullProvider()
{
}
void SFXNullProvider::addDeviceDesc( const String& name, const String& desc )
{
SFXDeviceInfo* info = new SFXDeviceInfo;
info->name = desc;
info->driver = name;
info->hasHardware = false;
info->maxBuffers = 8;
mDeviceInfo.push_back( info );
}
SFXDevice* SFXNullProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers )
{
SFXDeviceInfo* info = _findDeviceInfo( deviceName );
// Do we find one to create?
if ( info )
return new SFXNullDevice( this, info->name, useHardware, maxBuffers );
return NULL;
}

View file

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// 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/null/sfxNullVoice.h"
#include "sfx/null/sfxNullBuffer.h"
#include "sfx/sfxInternal.h"
SFXNullVoice::SFXNullVoice( SFXNullBuffer* buffer )
: Parent( buffer ),
mIsLooping( false )
{
}
SFXNullVoice::~SFXNullVoice()
{
}
SFXStatus SFXNullVoice::_status() const
{
if( !mIsLooping
&& mPlayTimer.isStarted()
&& !mPlayTimer.isPaused()
&& mPlayTimer.getPosition() >= mBuffer->getDuration() )
mPlayTimer.stop();
if( mPlayTimer.isPaused() )
return SFXStatusPaused;
else if( mPlayTimer.isStarted() )
return SFXStatusPlaying;
else
return SFXStatusStopped;
}
void SFXNullVoice::_play()
{
mPlayTimer.start();
}
void SFXNullVoice::_pause()
{
mPlayTimer.pause();
}
void SFXNullVoice::_stop()
{
mPlayTimer.stop();
}
void SFXNullVoice::_seek( U32 sample )
{
const U32 sampleTime = mBuffer->getFormat().getDuration( sample );
mPlayTimer.setPosition( sampleTime );
}
void SFXNullVoice::play( bool looping )
{
mIsLooping = looping;
mPlayTimer.start();
}
U32 SFXNullVoice::_tell() const
{
U32 ms = _getPlayTime();
const SFXFormat& format = mBuffer->getFormat();
return ( format.getDataLength( ms ) / format.getBytesPerSample() );
}
SFXStatus SFXNullVoice::getStatus() const
{
return _status();
}
void SFXNullVoice::setPosition( U32 sample )
{
_seek( sample );
}
void SFXNullVoice::setMinMaxDistance( F32 min, F32 max )
{
}
void SFXNullVoice::setVelocity( const VectorF& velocity )
{
}
void SFXNullVoice::setTransform( const MatrixF& transform )
{
}
void SFXNullVoice::setVolume( F32 volume )
{
}
void SFXNullVoice::setPitch( F32 pitch )
{
}
void SFXNullVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume )
{
}

View file

@ -0,0 +1,86 @@
//-----------------------------------------------------------------------------
// 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 _SFXNULLVOICE_H_
#define _SFXNULLVOICE_H_
#ifndef _SFXVOICE_H_
#include "sfx/sfxVoice.h"
#endif
#ifndef _TIMESOURCE_H_
#include "core/util/timeSource.h"
#endif
class SFXNullBuffer;
class SFXNullVoice : public SFXVoice
{
public:
typedef SFXVoice Parent;
friend class SFXNullDevice;
protected:
typedef GenericTimeSource< VirtualMSTimer > TimeSource;
SFXNullVoice( SFXNullBuffer* buffer );
/// The virtual play timer.
mutable TimeSource mPlayTimer;
///
bool mIsLooping;
// SFXVoice.
virtual SFXStatus _status() const;
virtual void _play();
virtual void _pause();
virtual void _stop();
virtual void _seek( U32 sample );
virtual U32 _tell() const;
///
U32 _getPlayTime() const
{
return mPlayTimer.getPosition();
}
public:
virtual ~SFXNullVoice();
/// SFXVoice
SFXStatus getStatus() const;
void setPosition( U32 sample );
void play( bool looping );
void setMinMaxDistance( F32 min, F32 max );
void setVelocity( const VectorF& velocity );
void setTransform( const MatrixF& transform );
void setVolume( F32 volume );
void setPitch( F32 pitch );
void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
};
#endif // _SFXNULLVOICE_H_

View file

@ -0,0 +1,209 @@
//-----------------------------------------------------------------------------
// 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 _LOADOAL_H_
#define _LOADOAL_H_
#ifndef _PLATFORM_H_
# include "platform/platform.h"
#endif
#if defined(TORQUE_OS_MAC)
# include <OpenAL/al.h>
# include <OpenAL/alc.h>
#else
# include <al/al.h>
# include <al/alc.h>
#endif
#ifndef ALAPIENTRY
#define ALAPIENTRY
#endif
#ifndef ALCAPIENTRY
#define ALCAPIENTRY
#endif
// Open AL Function table definition
#ifndef _OPENALFNTABLE
#define _OPENALFNTABLE
// AL 1.0 did not define the ALchar and ALCchar types, so define them here
// if they don't exist
#ifndef ALchar
#define ALchar char
#endif
#ifndef ALCchar
#define ALCchar char
#endif
// Complete list of functions available in AL 1.0 implementations
typedef void (ALAPIENTRY *LPALENABLE)( ALenum capability );
typedef void (ALAPIENTRY *LPALDISABLE)( ALenum capability );
typedef ALboolean (ALAPIENTRY *LPALISENABLED)( ALenum capability );
typedef const ALchar* (ALAPIENTRY *LPALGETSTRING)( ALenum param );
typedef void (ALAPIENTRY *LPALGETBOOLEANV)( ALenum param, ALboolean* data );
typedef void (ALAPIENTRY *LPALGETINTEGERV)( ALenum param, ALint* data );
typedef void (ALAPIENTRY *LPALGETFLOATV)( ALenum param, ALfloat* data );
typedef void (ALAPIENTRY *LPALGETDOUBLEV)( ALenum param, ALdouble* data );
typedef ALboolean (ALAPIENTRY *LPALGETBOOLEAN)( ALenum param );
typedef ALint (ALAPIENTRY *LPALGETINTEGER)( ALenum param );
typedef ALfloat (ALAPIENTRY *LPALGETFLOAT)( ALenum param );
typedef ALdouble (ALAPIENTRY *LPALGETDOUBLE)( ALenum param );
typedef ALenum (ALAPIENTRY *LPALGETERROR)( void );
typedef ALboolean (ALAPIENTRY *LPALISEXTENSIONPRESENT)(const ALchar* extname );
typedef void* (ALAPIENTRY *LPALGETPROCADDRESS)( const ALchar* fname );
typedef ALenum (ALAPIENTRY *LPALGETENUMVALUE)( const ALchar* ename );
typedef void (ALAPIENTRY *LPALLISTENERF)( ALenum param, ALfloat value );
typedef void (ALAPIENTRY *LPALLISTENER3F)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 );
typedef void (ALAPIENTRY *LPALLISTENERFV)( ALenum param, const ALfloat* values );
typedef void (ALAPIENTRY *LPALLISTENERI)( ALenum param, ALint value );
typedef void (ALAPIENTRY *LPALGETLISTENERF)( ALenum param, ALfloat* value );
typedef void (ALAPIENTRY *LPALGETLISTENER3F)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 );
typedef void (ALAPIENTRY *LPALGETLISTENERFV)( ALenum param, ALfloat* values );
typedef void (ALAPIENTRY *LPALGETLISTENERI)( ALenum param, ALint* value );
typedef void (ALAPIENTRY *LPALGENSOURCES)( ALsizei n, ALuint* sources );
typedef void (ALAPIENTRY *LPALDELETESOURCES)( ALsizei n, const ALuint* sources );
typedef ALboolean (ALAPIENTRY *LPALISSOURCE)( ALuint sid );
typedef void (ALAPIENTRY *LPALSOURCEF)( ALuint sid, ALenum param, ALfloat value);
typedef void (ALAPIENTRY *LPALSOURCE3F)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 );
typedef void (ALAPIENTRY *LPALSOURCEFV)( ALuint sid, ALenum param, const ALfloat* values );
typedef void (ALAPIENTRY *LPALSOURCEI)( ALuint sid, ALenum param, ALint value);
typedef void (ALAPIENTRY *LPALGETSOURCEF)( ALuint sid, ALenum param, ALfloat* value );
typedef void (ALAPIENTRY *LPALGETSOURCE3F)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3);
typedef void (ALAPIENTRY *LPALGETSOURCEFV)( ALuint sid, ALenum param, ALfloat* values );
typedef void (ALAPIENTRY *LPALGETSOURCEI)( ALuint sid, ALenum param, ALint* value );
typedef void (ALAPIENTRY *LPALSOURCEPLAYV)( ALsizei ns, const ALuint *sids );
typedef void (ALAPIENTRY *LPALSOURCESTOPV)( ALsizei ns, const ALuint *sids );
typedef void (ALAPIENTRY *LPALSOURCEREWINDV)( ALsizei ns, const ALuint *sids );
typedef void (ALAPIENTRY *LPALSOURCEPAUSEV)( ALsizei ns, const ALuint *sids );
typedef void (ALAPIENTRY *LPALSOURCEPLAY)( ALuint sid );
typedef void (ALAPIENTRY *LPALSOURCESTOP)( ALuint sid );
typedef void (ALAPIENTRY *LPALSOURCEREWIND)( ALuint sid );
typedef void (ALAPIENTRY *LPALSOURCEPAUSE)( ALuint sid );
typedef void (ALAPIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, const ALuint *bids );
typedef void (ALAPIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, ALuint *bids );
typedef void (ALAPIENTRY *LPALGENBUFFERS)( ALsizei n, ALuint* buffers );
typedef void (ALAPIENTRY *LPALDELETEBUFFERS)( ALsizei n, const ALuint* buffers );
typedef ALboolean (ALAPIENTRY *LPALISBUFFER)( ALuint bid );
typedef void (ALAPIENTRY *LPALBUFFERDATA)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq );
typedef void (ALAPIENTRY *LPALGETBUFFERF)( ALuint bid, ALenum param, ALfloat* value );
typedef void (ALAPIENTRY *LPALGETBUFFERI)( ALuint bid, ALenum param, ALint* value );
typedef void (ALAPIENTRY *LPALDOPPLERFACTOR)( ALfloat value );
typedef void (ALAPIENTRY *LPALDOPPLERVELOCITY)( ALfloat value );
typedef void (ALAPIENTRY *LPALDISTANCEMODEL)( ALenum distanceModel );
typedef ALCcontext * (ALCAPIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist);
typedef ALCboolean (ALCAPIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context );
typedef void (ALCAPIENTRY *LPALCPROCESSCONTEXT)( ALCcontext *context );
typedef void (ALCAPIENTRY *LPALCSUSPENDCONTEXT)( ALCcontext *context );
typedef void (ALCAPIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context );
typedef ALCcontext * (ALCAPIENTRY *LPALCGETCURRENTCONTEXT)( void );
typedef ALCdevice * (ALCAPIENTRY *LPALCGETCONTEXTSDEVICE)( ALCcontext *context );
typedef ALCdevice * (ALCAPIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename );
typedef ALCboolean (ALCAPIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device );
typedef ALCenum (ALCAPIENTRY *LPALCGETERROR)( ALCdevice *device );
typedef ALCboolean (ALCAPIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname );
typedef void * (ALCAPIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname );
typedef ALCenum (ALCAPIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname );
typedef const ALCchar* (ALCAPIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param );
typedef void (ALCAPIENTRY *LPALCGETINTEGERV)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest );
typedef struct
{
LPALENABLE alEnable;
LPALDISABLE alDisable;
LPALISENABLED alIsEnabled;
LPALGETBOOLEAN alGetBoolean;
LPALGETINTEGER alGetInteger;
LPALGETFLOAT alGetFloat;
LPALGETDOUBLE alGetDouble;
LPALGETBOOLEANV alGetBooleanv;
LPALGETINTEGERV alGetIntegerv;
LPALGETFLOATV alGetFloatv;
LPALGETDOUBLEV alGetDoublev;
LPALGETSTRING alGetString;
LPALGETERROR alGetError;
LPALISEXTENSIONPRESENT alIsExtensionPresent;
LPALGETPROCADDRESS alGetProcAddress;
LPALGETENUMVALUE alGetEnumValue;
LPALLISTENERI alListeneri;
LPALLISTENERF alListenerf;
LPALLISTENER3F alListener3f;
LPALLISTENERFV alListenerfv;
LPALGETLISTENERI alGetListeneri;
LPALGETLISTENERF alGetListenerf;
LPALGETLISTENER3F alGetListener3f;
LPALGETLISTENERFV alGetListenerfv;
LPALGENSOURCES alGenSources;
LPALDELETESOURCES alDeleteSources;
LPALISSOURCE alIsSource;
LPALSOURCEI alSourcei;
LPALSOURCEF alSourcef;
LPALSOURCE3F alSource3f;
LPALSOURCEFV alSourcefv;
LPALGETSOURCEI alGetSourcei;
LPALGETSOURCEF alGetSourcef;
LPALGETSOURCEFV alGetSourcefv;
LPALSOURCEPLAYV alSourcePlayv;
LPALSOURCESTOPV alSourceStopv;
LPALSOURCEPLAY alSourcePlay;
LPALSOURCEPAUSE alSourcePause;
LPALSOURCESTOP alSourceStop;
LPALSOURCEREWIND alSourceRewind;
LPALGENBUFFERS alGenBuffers;
LPALDELETEBUFFERS alDeleteBuffers;
LPALISBUFFER alIsBuffer;
LPALBUFFERDATA alBufferData;
LPALGETBUFFERI alGetBufferi;
LPALGETBUFFERF alGetBufferf;
LPALSOURCEQUEUEBUFFERS alSourceQueueBuffers;
LPALSOURCEUNQUEUEBUFFERS alSourceUnqueueBuffers;
LPALDISTANCEMODEL alDistanceModel;
LPALDOPPLERFACTOR alDopplerFactor;
LPALDOPPLERVELOCITY alDopplerVelocity;
LPALCGETSTRING alcGetString;
LPALCGETINTEGERV alcGetIntegerv;
LPALCOPENDEVICE alcOpenDevice;
LPALCCLOSEDEVICE alcCloseDevice;
LPALCCREATECONTEXT alcCreateContext;
LPALCMAKECONTEXTCURRENT alcMakeContextCurrent;
LPALCPROCESSCONTEXT alcProcessContext;
LPALCGETCURRENTCONTEXT alcGetCurrentContext;
LPALCGETCONTEXTSDEVICE alcGetContextsDevice;
LPALCSUSPENDCONTEXT alcSuspendContext;
LPALCDESTROYCONTEXT alcDestroyContext;
LPALCGETERROR alcGetError;
LPALCISEXTENSIONPRESENT alcIsExtensionPresent;
LPALCGETPROCADDRESS alcGetProcAddress;
LPALCGETENUMVALUE alcGetEnumValue;
} OPENALFNTABLE, *LPOPENALFNTABLE;
#endif
ALboolean LoadOAL10Library(char *szOALFullPathName, LPOPENALFNTABLE lpOALFnTable);
ALvoid UnloadOAL10Library();
#endif // _LOADOAL_H_

View file

@ -0,0 +1,314 @@
/*
* Copyright (c) 2006, Creative Labs Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and
* the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
* * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/strings/stringFunctions.h"
#include "aldlist.h"
#if defined(TORQUE_OS_MAC)
#include <OpenAL/alc.h>
#else
#include <al/alc.h>
#endif
/*
* Init call
*/
ALDeviceList::ALDeviceList( const OPENALFNTABLE &oalft )
{
VECTOR_SET_ASSOCIATION( vDeviceInfo );
ALDEVICEINFO ALDeviceInfo;
char *devices;
int index;
const char *defaultDeviceName;
const char *actualDeviceName;
dMemcpy( &ALFunction, &oalft, sizeof( OPENALFNTABLE ) );
// DeviceInfo vector stores, for each enumerated device, it's device name, selection status, spec version #, and extension support
vDeviceInfo.clear();
vDeviceInfo.reserve(10);
defaultDeviceIndex = 0;
// grab function pointers for 1.0-API functions, and if successful proceed to enumerate all devices
if (ALFunction.alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT")) {
devices = (char *)ALFunction.alcGetString(NULL, ALC_DEVICE_SPECIFIER);
defaultDeviceName = (char *)ALFunction.alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
index = 0;
// go through device list (each device terminated with a single NULL, list terminated with double NULL)
while (*devices != 0) {
if (dStrcmp(defaultDeviceName, devices) == 0) {
defaultDeviceIndex = index;
}
ALCdevice *device = ALFunction.alcOpenDevice(devices);
if (device) {
ALCcontext *context = ALFunction.alcCreateContext(device, NULL);
if (context) {
ALFunction.alcMakeContextCurrent(context);
// if new actual device name isn't already in the list, then add it...
actualDeviceName = ALFunction.alcGetString(device, ALC_DEVICE_SPECIFIER);
bool bNewName = true;
for (int i = 0; i < GetNumDevices(); i++) {
if (dStrcmp(GetDeviceName(i), actualDeviceName) == 0) {
bNewName = false;
}
}
if ((bNewName) && (actualDeviceName != NULL) && (dStrlen(actualDeviceName) > 0)) {
dMemset(&ALDeviceInfo, 0, sizeof(ALDEVICEINFO));
ALDeviceInfo.bSelected = true;
dStrncpy(ALDeviceInfo.strDeviceName, actualDeviceName, sizeof(ALDeviceInfo.strDeviceName));
ALFunction.alcGetIntegerv(device, ALC_MAJOR_VERSION, sizeof(int), &ALDeviceInfo.iMajorVersion);
ALFunction.alcGetIntegerv(device, ALC_MINOR_VERSION, sizeof(int), &ALDeviceInfo.iMinorVersion);
ALDeviceInfo.iCapsFlags = 0;
// Check for ALC Extensions
if (ALFunction.alcIsExtensionPresent(device, "ALC_EXT_CAPTURE") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALCapture;
if (ALFunction.alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALEFX;
// Check for AL Extensions
if (ALFunction.alIsExtensionPresent("AL_EXT_OFFSET") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALOffset;
if (ALFunction.alIsExtensionPresent("AL_EXT_LINEAR_DISTANCE") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALLinearDistance;
if (ALFunction.alIsExtensionPresent("AL_EXT_EXPONENT_DISTANCE") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALExponentDistance;
if (ALFunction.alIsExtensionPresent("EAX2.0") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALEAX2;
if (ALFunction.alIsExtensionPresent("EAX3.0") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALEAX3;
if (ALFunction.alIsExtensionPresent("EAX4.0") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALEAX4;
if (ALFunction.alIsExtensionPresent("EAX5.0") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALEAX5;
if (ALFunction.alIsExtensionPresent("EAX-RAM") == AL_TRUE)
ALDeviceInfo.iCapsFlags |= SFXALEAXRAM;
// Get Source Count
ALDeviceInfo.uiSourceCount = GetMaxNumSources();
vDeviceInfo.push_back(ALDeviceInfo);
}
ALFunction.alcMakeContextCurrent(NULL);
ALFunction.alcDestroyContext(context);
}
ALFunction.alcCloseDevice(device);
}
devices += dStrlen(devices) + 1;
index += 1;
}
}
ResetFilters();
}
/*
* Exit call
*/
ALDeviceList::~ALDeviceList()
{
}
/*
* Returns the number of devices in the complete device list
*/
int ALDeviceList::GetNumDevices()
{
return (int)vDeviceInfo.size();
}
/*
* Returns the device name at an index in the complete device list
*/
const char *ALDeviceList::GetDeviceName(int index)
{
if (index < GetNumDevices())
return vDeviceInfo[index].strDeviceName;
else
return NULL;
}
/*
* Returns the major and minor version numbers for a device at a specified index in the complete list
*/
void ALDeviceList::GetDeviceVersion(int index, int *major, int *minor)
{
if (index < GetNumDevices()) {
if (*major)
*major = vDeviceInfo[index].iMajorVersion;
if (*minor)
*minor = vDeviceInfo[index].iMinorVersion;
}
return;
}
/*
* Returns the maximum number of Sources that can be generate on the given device
*/
unsigned int ALDeviceList::GetMaxNumSources(int index)
{
if (index < GetNumDevices())
return vDeviceInfo[index].uiSourceCount;
else
return 0;
}
/*
* Checks if the extension is supported on the given device
*/
bool ALDeviceList::IsExtensionSupported(int index, SFXALCaps cap)
{
bool bReturn = false;
if (index < GetNumDevices())
bReturn = vDeviceInfo[index].iCapsFlags & cap;
return bReturn;
}
/*
* returns the index of the default device in the complete device list
*/
int ALDeviceList::GetDefaultDevice()
{
return defaultDeviceIndex;
}
/*
* Deselects devices which don't have the specified minimum version
*/
void ALDeviceList::FilterDevicesMinVer(int major, int minor)
{
int dMajor, dMinor;
for (unsigned int i = 0; i < vDeviceInfo.size(); i++) {
GetDeviceVersion(i, &dMajor, &dMinor);
if ((dMajor < major) || ((dMajor == major) && (dMinor < minor))) {
vDeviceInfo[i].bSelected = false;
}
}
}
/*
* Deselects devices which don't have the specified maximum version
*/
void ALDeviceList::FilterDevicesMaxVer(int major, int minor)
{
int dMajor, dMinor;
for (unsigned int i = 0; i < vDeviceInfo.size(); i++) {
GetDeviceVersion(i, &dMajor, &dMinor);
if ((dMajor > major) || ((dMajor == major) && (dMinor > minor))) {
vDeviceInfo[i].bSelected = false;
}
}
}
/*
* Deselects device which don't support the given extension name
*/
void ALDeviceList::FilterDevicesExtension(SFXALCaps cap)
{
for (unsigned int i = 0; i < vDeviceInfo.size(); i++)
vDeviceInfo[i].bSelected = vDeviceInfo[i].iCapsFlags & cap;
}
/*
* Resets all filtering, such that all devices are in the list
*/
void ALDeviceList::ResetFilters()
{
for (int i = 0; i < GetNumDevices(); i++) {
vDeviceInfo[i].bSelected = true;
}
filterIndex = 0;
}
/*
* Gets index of first filtered device
*/
int ALDeviceList::GetFirstFilteredDevice()
{
int i;
for (i = 0; i < GetNumDevices(); i++) {
if (vDeviceInfo[i].bSelected == true) {
break;
}
}
filterIndex = i + 1;
return i;
}
/*
* Gets index of next filtered device
*/
int ALDeviceList::GetNextFilteredDevice()
{
int i;
for (i = filterIndex; i < GetNumDevices(); i++) {
if (vDeviceInfo[i].bSelected == true) {
break;
}
}
filterIndex = i + 1;
return i;
}
/*
* Internal function to detemine max number of Sources that can be generated
*/
unsigned int ALDeviceList::GetMaxNumSources()
{
ALuint uiSources[256];
unsigned int iSourceCount = 0;
// Clear AL Error Code
ALFunction.alGetError();
// Generate up to 256 Sources, checking for any errors
for (iSourceCount = 0; iSourceCount < 256; iSourceCount++)
{
ALFunction.alGenSources(1, &uiSources[iSourceCount]);
if (ALFunction.alGetError() != AL_NO_ERROR)
break;
}
// Release the Sources
ALFunction.alDeleteSources(iSourceCount, uiSources);
if (ALFunction.alGetError() != AL_NO_ERROR)
{
for (unsigned int i = 0; i < 256; i++)
{
ALFunction.alDeleteSources(1, &uiSources[i]);
}
}
return iSourceCount;
}

View file

@ -0,0 +1,70 @@
//-----------------------------------------------------------------------------
// 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 ALDEVICELIST_H
#define ALDEVICELIST_H
#pragma warning(disable: 4786) //disable warning "identifier was truncated to '255' characters in the browser information"
#include "core/util/tVector.h"
#include "core/stringTable.h"
#include "sfx/openal/sfxALCaps.h"
#include "LoadOAL.h"
typedef struct
{
char strDeviceName[256];
int iMajorVersion;
int iMinorVersion;
unsigned int uiSourceCount;
int iCapsFlags;
bool bSelected;
} ALDEVICEINFO, *LPALDEVICEINFO;
class ALDeviceList
{
private:
OPENALFNTABLE ALFunction;
Vector<ALDEVICEINFO> vDeviceInfo;
int defaultDeviceIndex;
int filterIndex;
public:
ALDeviceList ( const OPENALFNTABLE &oalft );
~ALDeviceList ();
int GetNumDevices();
const char *GetDeviceName(int index);
void GetDeviceVersion(int index, int *major, int *minor);
unsigned int GetMaxNumSources(int index);
bool IsExtensionSupported(int index, SFXALCaps caps);
int GetDefaultDevice();
void FilterDevicesMinVer(int major, int minor);
void FilterDevicesMaxVer(int major, int minor);
void FilterDevicesExtension(SFXALCaps caps);
void ResetFilters();
int GetFirstFilteredDevice();
int GetNextFilteredDevice();
private:
unsigned int GetMaxNumSources();
};
#endif // ALDEVICELIST_H

View file

@ -0,0 +1,445 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// TODO: Implement OpenAL loading code which is currently stubbed out.
#if defined(__MACOSX__) && !defined(TORQUE_OS_MAC)
#define TORQUE_OS_MAC
#endif
#include <err.h>
#include <string.h>
#include "sfx/openal/LoadOAL.h"
ALboolean LoadOAL10Library(char *szOALFullPathName, LPOPENALFNTABLE lpOALFnTable)
{
// TODO: Implement this.
if (!lpOALFnTable)
return AL_FALSE;
memset(lpOALFnTable, 0, sizeof(OPENALFNTABLE));
lpOALFnTable->alEnable = (LPALENABLE)alEnable;
if (lpOALFnTable->alEnable == NULL)
{
warn("Failed to retrieve 'alEnable' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDisable = (LPALDISABLE)alDisable;
if (lpOALFnTable->alDisable == NULL)
{
warn("Failed to retrieve 'alDisable' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsEnabled = (LPALISENABLED)alIsEnabled;
if (lpOALFnTable->alIsEnabled == NULL)
{
warn("Failed to retrieve 'alIsEnabled' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBoolean = (LPALGETBOOLEAN)alGetBoolean;
if (lpOALFnTable->alGetBoolean == NULL)
{
warn("Failed to retrieve 'alGetBoolean' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetInteger = (LPALGETINTEGER)alGetInteger;
if (lpOALFnTable->alGetInteger == NULL)
{
warn("Failed to retrieve 'alGetInteger' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetFloat = (LPALGETFLOAT)alGetFloat;
if (lpOALFnTable->alGetFloat == NULL)
{
warn("Failed to retrieve 'alGetFloat' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetDouble = (LPALGETDOUBLE)alGetDouble;
if (lpOALFnTable->alGetDouble == NULL)
{
warn("Failed to retrieve 'alGetDouble' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBooleanv = (LPALGETBOOLEANV)alGetBooleanv;
if (lpOALFnTable->alGetBooleanv == NULL)
{
warn("Failed to retrieve 'alGetBooleanv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetIntegerv = (LPALGETINTEGERV)alGetIntegerv;
if (lpOALFnTable->alGetIntegerv == NULL)
{
warn("Failed to retrieve 'alGetIntegerv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetFloatv = (LPALGETFLOATV)alGetFloatv;
if (lpOALFnTable->alGetFloatv == NULL)
{
warn("Failed to retrieve 'alGetFloatv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetDoublev = (LPALGETDOUBLEV)alGetDoublev;
if (lpOALFnTable->alGetDoublev == NULL)
{
warn("Failed to retrieve 'alGetDoublev' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetString = (LPALGETSTRING)alGetString;
if (lpOALFnTable->alGetString == NULL)
{
warn("Failed to retrieve 'alGetString' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetError = (LPALGETERROR)alGetError;
if (lpOALFnTable->alGetError == NULL)
{
warn("Failed to retrieve 'alGetError' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsExtensionPresent = (LPALISEXTENSIONPRESENT)alIsExtensionPresent;
if (lpOALFnTable->alIsExtensionPresent == NULL)
{
warn("Failed to retrieve 'alIsExtensionPresent' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetProcAddress = (LPALGETPROCADDRESS)alGetProcAddress;
if (lpOALFnTable->alGetProcAddress == NULL)
{
warn("Failed to retrieve 'alGetProcAddress' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetEnumValue = (LPALGETENUMVALUE)alGetEnumValue;
if (lpOALFnTable->alGetEnumValue == NULL)
{
warn("Failed to retrieve 'alGetEnumValue' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListeneri = (LPALLISTENERI)alListeneri;
if (lpOALFnTable->alListeneri == NULL)
{
warn("Failed to retrieve 'alListeneri' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListenerf = (LPALLISTENERF)alListenerf;
if (lpOALFnTable->alListenerf == NULL)
{
warn("Failed to retrieve 'alListenerf' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListener3f = (LPALLISTENER3F)alListener3f;
if (lpOALFnTable->alListener3f == NULL)
{
warn("Failed to retrieve 'alListener3f' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListenerfv = (LPALLISTENERFV)alListenerfv;
if (lpOALFnTable->alListenerfv == NULL)
{
warn("Failed to retrieve 'alListenerfv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListeneri = (LPALGETLISTENERI)alGetListeneri;
if (lpOALFnTable->alGetListeneri == NULL)
{
warn("Failed to retrieve 'alGetListeneri' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListenerf =(LPALGETLISTENERF)alGetListenerf;
if (lpOALFnTable->alGetListenerf == NULL)
{
warn("Failed to retrieve 'alGetListenerf' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListener3f = (LPALGETLISTENER3F)alGetListener3f;
if (lpOALFnTable->alGetListener3f == NULL)
{
warn("Failed to retrieve 'alGetListener3f' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListenerfv = (LPALGETLISTENERFV)alGetListenerfv;
if (lpOALFnTable->alGetListenerfv == NULL)
{
warn("Failed to retrieve 'alGetListenerfv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGenSources = (LPALGENSOURCES)alGenSources;
if (lpOALFnTable->alGenSources == NULL)
{
warn("Failed to retrieve 'alGenSources' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDeleteSources = (LPALDELETESOURCES)alDeleteSources;
if (lpOALFnTable->alDeleteSources == NULL)
{
warn("Failed to retrieve 'alDeleteSources' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsSource = (LPALISSOURCE)alIsSource;
if (lpOALFnTable->alIsSource == NULL)
{
warn("Failed to retrieve 'alIsSource' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcei = (LPALSOURCEI)alSourcei;
if (lpOALFnTable->alSourcei == NULL)
{
warn("Failed to retrieve 'alSourcei' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcef = (LPALSOURCEF)alSourcef;
if (lpOALFnTable->alSourcef == NULL)
{
warn("Failed to retrieve 'alSourcef' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSource3f = (LPALSOURCE3F)alSource3f;
if (lpOALFnTable->alSource3f == NULL)
{
warn("Failed to retrieve 'alSource3f' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcefv = (LPALSOURCEFV)alSourcefv;
if (lpOALFnTable->alSourcefv == NULL)
{
warn("Failed to retrieve 'alSourcefv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetSourcei = (LPALGETSOURCEI)alGetSourcei;
if (lpOALFnTable->alGetSourcei == NULL)
{
warn("Failed to retrieve 'alGetSourcei' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetSourcef = (LPALGETSOURCEF)alGetSourcef;
if (lpOALFnTable->alGetSourcef == NULL)
{
warn("Failed to retrieve 'alGetSourcef' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetSourcefv = (LPALGETSOURCEFV)alGetSourcefv;
if (lpOALFnTable->alGetSourcefv == NULL)
{
warn("Failed to retrieve 'alGetSourcefv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcePlayv = (LPALSOURCEPLAYV)alSourcePlayv;
if (lpOALFnTable->alSourcePlayv == NULL)
{
warn("Failed to retrieve 'alSourcePlayv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceStopv = (LPALSOURCESTOPV)alSourceStopv;
if (lpOALFnTable->alSourceStopv == NULL)
{
warn("Failed to retrieve 'alSourceStopv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcePlay = (LPALSOURCEPLAY)alSourcePlay;
if (lpOALFnTable->alSourcePlay == NULL)
{
warn("Failed to retrieve 'alSourcePlay' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcePause = (LPALSOURCEPAUSE)alSourcePause;
if (lpOALFnTable->alSourcePause == NULL)
{
warn("Failed to retrieve 'alSourcePause' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceStop = (LPALSOURCESTOP)alSourceStop;
if (lpOALFnTable->alSourceStop == NULL)
{
warn("Failed to retrieve 'alSourceStop' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceRewind = (LPALSOURCEREWIND)alSourceRewind;
if (lpOALFnTable->alSourceRewind == NULL)
{
warn("Failed to retrieve 'alSourceRewind' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGenBuffers = (LPALGENBUFFERS)alGenBuffers;
if (lpOALFnTable->alGenBuffers == NULL)
{
warn("Failed to retrieve 'alGenBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDeleteBuffers = (LPALDELETEBUFFERS)alDeleteBuffers;
if (lpOALFnTable->alDeleteBuffers == NULL)
{
warn("Failed to retrieve 'alDeleteBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsBuffer = (LPALISBUFFER)alIsBuffer;
if (lpOALFnTable->alIsBuffer == NULL)
{
warn("Failed to retrieve 'alIsBuffer' function address\n");
return AL_FALSE;
}
lpOALFnTable->alBufferData = (LPALBUFFERDATA)alBufferData;
if (lpOALFnTable->alBufferData == NULL)
{
warn("Failed to retrieve 'alBufferData' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBufferi = (LPALGETBUFFERI)alGetBufferi;
if (lpOALFnTable->alGetBufferi == NULL)
{
warn("Failed to retrieve 'alGetBufferi' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBufferf = (LPALGETBUFFERF)alGetBufferf;
if (lpOALFnTable->alGetBufferf == NULL)
{
warn("Failed to retrieve 'alGetBufferf' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)alSourceQueueBuffers;
if (lpOALFnTable->alSourceQueueBuffers == NULL)
{
warn("Failed to retrieve 'alSourceQueueBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)alSourceUnqueueBuffers;
if (lpOALFnTable->alSourceUnqueueBuffers == NULL)
{
warn("Failed to retrieve 'alSourceUnqueueBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDistanceModel = (LPALDISTANCEMODEL)alDistanceModel;
if (lpOALFnTable->alDistanceModel == NULL)
{
warn("Failed to retrieve 'alDistanceModel' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDopplerFactor = (LPALDOPPLERFACTOR)alDopplerFactor;
if (lpOALFnTable->alDopplerFactor == NULL)
{
warn("Failed to retrieve 'alDopplerFactor' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDopplerVelocity = (LPALDOPPLERVELOCITY)alDopplerVelocity;
if (lpOALFnTable->alDopplerVelocity == NULL)
{
warn("Failed to retrieve 'alDopplerVelocity' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetString = (LPALCGETSTRING)alcGetString;
if (lpOALFnTable->alcGetString == NULL)
{
warn("Failed to retrieve 'alcGetString' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetIntegerv = (LPALCGETINTEGERV)alcGetIntegerv;
if (lpOALFnTable->alcGetIntegerv == NULL)
{
warn("Failed to retrieve 'alcGetIntegerv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcOpenDevice = (LPALCOPENDEVICE)alcOpenDevice;
if (lpOALFnTable->alcOpenDevice == NULL)
{
warn("Failed to retrieve 'alcOpenDevice' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcCloseDevice = (LPALCCLOSEDEVICE)alcCloseDevice;
if (lpOALFnTable->alcCloseDevice == NULL)
{
warn("Failed to retrieve 'alcCloseDevice' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcCreateContext = (LPALCCREATECONTEXT)alcCreateContext;
if (lpOALFnTable->alcCreateContext == NULL)
{
warn("Failed to retrieve 'alcCreateContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)alcMakeContextCurrent;
if (lpOALFnTable->alcMakeContextCurrent == NULL)
{
warn("Failed to retrieve 'alcMakeContextCurrent' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcProcessContext = (LPALCPROCESSCONTEXT)alcProcessContext;
if (lpOALFnTable->alcProcessContext == NULL)
{
warn("Failed to retrieve 'alcProcessContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetCurrentContext = (LPALCGETCURRENTCONTEXT)alcGetCurrentContext;
if (lpOALFnTable->alcGetCurrentContext == NULL)
{
warn("Failed to retrieve 'alcGetCurrentContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)alcGetContextsDevice;
if (lpOALFnTable->alcGetContextsDevice == NULL)
{
warn("Failed to retrieve 'alcGetContextsDevice' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcSuspendContext = (LPALCSUSPENDCONTEXT)alcSuspendContext;
if (lpOALFnTable->alcSuspendContext == NULL)
{
warn("Failed to retrieve 'alcSuspendContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcDestroyContext = (LPALCDESTROYCONTEXT)alcDestroyContext;
if (lpOALFnTable->alcDestroyContext == NULL)
{
warn("Failed to retrieve 'alcDestroyContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetError = (LPALCGETERROR)alcGetError;
if (lpOALFnTable->alcGetError == NULL)
{
warn("Failed to retrieve 'alcGetError' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)alcIsExtensionPresent;
if (lpOALFnTable->alcIsExtensionPresent == NULL)
{
warn("Failed to retrieve 'alcIsExtensionPresent' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetProcAddress = (LPALCGETPROCADDRESS)alcGetProcAddress;
if (lpOALFnTable->alcGetProcAddress == NULL)
{
warn("Failed to retrieve 'alcGetProcAddress' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetEnumValue = (LPALCGETENUMVALUE)alcGetEnumValue;
if (lpOALFnTable->alcGetEnumValue == NULL)
{
warn("Failed to retrieve 'alcGetEnumValue' function address\n");
return AL_FALSE;
}
return AL_TRUE;
}
ALvoid UnloadOAL10Library()
{
// TODO: Implement this.
}

View file

@ -0,0 +1,238 @@
//-----------------------------------------------------------------------------
// 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/openal/sfxALBuffer.h"
#include "sfx/openal/sfxALVoice.h"
#include "sfx/openal/sfxALDevice.h"
#include "sfx/sfxDescription.h"
#include "console/console.h"
//#define DEBUG_SPEW
SFXALBuffer* SFXALBuffer::create( const OPENALFNTABLE &oalft,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware )
{
if( !_sfxFormatToALFormat( stream->getFormat() ) )
{
Con::errorf( "SFXALBuffer::create() - SFXFormat not supported by OpenAL" );
return NULL;
}
SFXALBuffer *buffer = new SFXALBuffer( oalft,
stream,
description,
useHardware );
return buffer;
}
SFXALBuffer::SFXALBuffer( const OPENALFNTABLE &oalft,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware )
: Parent( stream, description ),
mOpenAL( oalft ),
mUseHardware( useHardware ),
mIs3d( description->mIs3D )
{
// Set up device buffers.
if( !isStreaming() )
mOpenAL.alGenBuffers( 1, &mALBuffer );
}
SFXALBuffer::~SFXALBuffer()
{
if( _getUniqueVoice() )
_getUniqueVoice()->stop();
// Release buffers.
if ( mOpenAL.alIsBuffer( mALBuffer ))
mOpenAL.alDeleteBuffers( 1, &mALBuffer );
while( mFreeBuffers.size() )
{
ALuint buffer = mFreeBuffers.last();
mOpenAL.alDeleteBuffers( 1, &buffer );
mFreeBuffers.pop_back();
}
}
void SFXALBuffer::write( SFXInternal::SFXStreamPacket* const* packets, U32 num )
{
using namespace SFXInternal;
if( !num )
return;
// If this is not a streaming buffer, just load the data into our single
// static buffer.
if( !isStreaming() )
{
SFXStreamPacket* packet = packets[ num - 1 ];
ALenum alFormat = _sfxFormatToALFormat( getFormat() );
AssertFatal( alFormat != 0, "SFXALBuffer::write() - format unsupported" );
mOpenAL.alBufferData( mALBuffer, alFormat,
packet->data, packet->mSizeActual, getFormat().getSamplesPerSecond() );
destructSingle( packet );
return;
}
MutexHandle mutex;
mutex.lock( &_getUniqueVoice()->mMutex, true );
// Unqueue processed packets.
ALuint source = _getUniqueVoice()->mSourceName;
ALint numProcessed;
mOpenAL.alGetSourcei( source, AL_BUFFERS_PROCESSED, &numProcessed );
for( U32 i = 0; i < numProcessed; ++ i )
{
// Unqueue the buffer.
ALuint buffer;
mOpenAL.alSourceUnqueueBuffers( source, 1, &buffer );
// Update the sample offset on the voice.
ALint size;
mOpenAL.alGetBufferi( buffer, AL_SIZE, &size );
_getUniqueVoice()->mSampleOffset += size / getFormat().getBytesPerSample();
// Push the buffer onto the freelist.
mFreeBuffers.push_back( buffer );
}
// Queue buffers.
for( U32 i = 0; i < num; ++ i )
{
SFXStreamPacket* packet = packets[ i ];
// Allocate a buffer.
ALuint buffer;
if( mFreeBuffers.size() )
{
buffer = mFreeBuffers.last();
mFreeBuffers.pop_back();
}
else
mOpenAL.alGenBuffers( 1, &buffer );
// Upload the data.
ALenum alFormat = _sfxFormatToALFormat( getFormat() );
AssertFatal( alFormat != 0, "SFXALBuffer::write() - format unsupported" );
AssertFatal( mOpenAL.alIsBuffer( buffer ), "SFXALBuffer::write() - buffer invalid" );
mOpenAL.alBufferData( buffer, alFormat,
packet->data, packet->mSizeActual, getFormat().getSamplesPerSecond() );
destructSingle( packet );
// Queue the buffer.
mOpenAL.alSourceQueueBuffers( source, 1, &buffer );
}
}
void SFXALBuffer::_flush()
{
AssertFatal( isStreaming(), "SFXALBuffer::_flush() - not a streaming buffer" );
AssertFatal( SFXInternal::isSFXThread(), "SFXALBuffer::_flush() - not on SFX thread" );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXALBuffer] Flushing buffer" );
#endif
_getUniqueVoice()->_stop();
MutexHandle mutex;
mutex.lock( &_getUniqueVoice()->mMutex, true );
ALuint source = _getUniqueVoice()->mSourceName;
ALint numQueued;
mOpenAL.alGetSourcei( source, AL_BUFFERS_QUEUED, &numQueued );
for( U32 i = 0; i < numQueued; ++ i )
{
ALuint buffer;
mOpenAL.alSourceUnqueueBuffers( source, 1, &buffer );
mFreeBuffers.push_back( buffer );
}
_getUniqueVoice()->mSampleOffset = 0;
//RD: disabling hack for now; rewritten queueing should be able to cope
#if 0 //def TORQUE_OS_MAC
//WORKAROUND: Ugly hack on Mac. Apparently there's a bug in the OpenAL implementation
// that will cause AL_BUFFERS_PROCESSED to not be reset as it should be causing write()
// to fail. Brute-force this and just re-create the source. Let's pray that nobody
// issues any concurrent state changes on the voice resulting in us losing state here.
ALuint newSource;
mOpenAL.alGenSources( 1, &newSource );
#define COPY_F( name ) \
{ \
F32 val; \
mOpenAL.alGetSourcef( source, name, &val ); \
mOpenAL.alSourcef( source, name, val ); \
}
#define COPY_FV( name ) \
{ \
VectorF val; \
mOpenAL.alGetSourcefv( source, name, val ); \
mOpenAL.alSourcefv( source, name, val ); \
}
COPY_F( AL_REFERENCE_DISTANCE );
COPY_F( AL_MAX_DISTANCE );
COPY_F( AL_GAIN );
COPY_F( AL_PITCH );
COPY_F( AL_CONE_INNER_ANGLE );
COPY_F( AL_CONE_OUTER_ANGLE );
COPY_F( AL_CONE_OUTER_GAIN );
COPY_FV( AL_VELOCITY );
COPY_FV( AL_POSITION );
COPY_FV( AL_DIRECTION );
_getUniqueVoice()->mSourceName = newSource;
mOpenAL.alDeleteSources( 1, &source );
#endif
}

View file

@ -0,0 +1,119 @@
//-----------------------------------------------------------------------------
// 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 _SFXALBUFFER_H_
#define _SFXALBUFFER_H_
#ifndef _LOADOAL_H
#include "sfx/openal/LoadOAL.h"
#endif
#ifndef _SFXINTERNAL_H_
#include "sfx/sfxInternal.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class SFXALVoice;
class SFXALBuffer : public SFXBuffer
{
public:
typedef SFXBuffer Parent;
friend class SFXALDevice;
friend class SFXALVoice;
protected:
/// AL buffer in case this is a static, non-streaming buffer.
ALuint mALBuffer;
/// Free buffers for use in queuing in case this is a streaming buffer.
Vector< ALuint > mFreeBuffers;
///
SFXALBuffer( const OPENALFNTABLE &oalft,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware );
///
bool mIs3d;
///
bool mUseHardware;
const OPENALFNTABLE &mOpenAL;
///
ALenum _getALFormat() const
{
return _sfxFormatToALFormat( getFormat() );
}
///
static ALenum _sfxFormatToALFormat( const SFXFormat& format )
{
if( format.getChannels() == 2 )
{
const U32 bps = format.getBitsPerSample();
if( bps == 16 )
return AL_FORMAT_STEREO8;
else if( bps == 32 )
return AL_FORMAT_STEREO16;
}
else if( format.getChannels() == 1 )
{
const U32 bps = format.getBitsPerSample();
if( bps == 8 )
return AL_FORMAT_MONO8;
else if( bps == 16 )
return AL_FORMAT_MONO16;
}
return 0;
}
///
SFXALVoice* _getUniqueVoice() const
{
return ( SFXALVoice* ) mUniqueVoice.getPointer();
}
// SFXBuffer.
virtual void write( SFXInternal::SFXStreamPacket* const* packets, U32 num );
void _flush();
public:
static SFXALBuffer* create( const OPENALFNTABLE &oalft,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description,
bool useHardware );
virtual ~SFXALBuffer();
};
#endif // _SFXALBUFFER_H_

View file

@ -0,0 +1,40 @@
//-----------------------------------------------------------------------------
// 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 _SFXALCAPS_H_
#define _SFXALCAPS_H_
enum SFXALCaps
{
SFXALCapture = 0,
SFXALEFX,
SFXALOffset,
SFXALLinearDistance,
SFXALExponentDistance,
SFXALEAX2,
SFXALEAX3,
SFXALEAX4,
SFXALEAX5,
SFXALEAXRAM
};
#endif

View file

@ -0,0 +1,199 @@
//-----------------------------------------------------------------------------
// 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/openal/sfxALDevice.h"
#include "sfx/openal/sfxALBuffer.h"
#include "platform/async/asyncUpdate.h"
//-----------------------------------------------------------------------------
SFXALDevice::SFXALDevice( SFXProvider *provider,
const OPENALFNTABLE &openal,
String name,
bool useHardware,
S32 maxBuffers )
: Parent( name, provider, useHardware, maxBuffers ),
mOpenAL( openal ),
mDevice( NULL ),
mContext( NULL )
{
mMaxBuffers = getMax( maxBuffers, 8 );
// TODO: The OpenAL device doesn't set the primary buffer
// $pref::SFX::frequency or $pref::SFX::bitrate!
mDevice = mOpenAL.alcOpenDevice( name );
mOpenAL.alcGetError( mDevice );
if( mDevice )
{
mContext = mOpenAL.alcCreateContext( mDevice, NULL );
if( mContext )
mOpenAL.alcMakeContextCurrent( mContext );
U32 err = mOpenAL.alcGetError( mDevice );
if( err != ALC_NO_ERROR )
Con::errorf( "SFXALDevice - Initialization Error: %s", mOpenAL.alcGetString( mDevice, err ) );
}
AssertFatal( mDevice != NULL && mContext != NULL, "Failed to create OpenAL device and/or context!" );
// Start the update thread.
if( !Con::getBoolVariable( "$_forceAllMainThread" ) )
{
SFXInternal::gUpdateThread = new AsyncPeriodicUpdateThread
( "OpenAL Update Thread", SFXInternal::gBufferUpdateList,
Con::getIntVariable( "$pref::SFX::updateInterval", SFXInternal::DEFAULT_UPDATE_INTERVAL ) );
SFXInternal::gUpdateThread->start();
}
}
//-----------------------------------------------------------------------------
SFXALDevice::~SFXALDevice()
{
_releaseAllResources();
mOpenAL.alcMakeContextCurrent( NULL );
mOpenAL.alcDestroyContext( mContext );
mOpenAL.alcCloseDevice( mDevice );
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXALDevice::createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
{
AssertFatal( stream, "SFXALDevice::createBuffer() - Got null stream!" );
AssertFatal( description, "SFXALDevice::createBuffer() - Got null description!" );
SFXALBuffer* buffer = SFXALBuffer::create( mOpenAL,
stream,
description,
mUseHardware );
if ( !buffer )
return NULL;
_addBuffer( buffer );
return buffer;
}
//-----------------------------------------------------------------------------
SFXVoice* SFXALDevice::createVoice( bool is3D, SFXBuffer *buffer )
{
// Don't bother going any further if we've
// exceeded the maximum voices.
if ( mVoices.size() >= mMaxBuffers )
return NULL;
AssertFatal( buffer, "SFXALDevice::createVoice() - Got null buffer!" );
SFXALBuffer* alBuffer = dynamic_cast<SFXALBuffer*>( buffer );
AssertFatal( alBuffer, "SFXALDevice::createVoice() - Got bad buffer!" );
SFXALVoice* voice = SFXALVoice::create( this, alBuffer );
if ( !voice )
return NULL;
_addVoice( voice );
return voice;
}
//-----------------------------------------------------------------------------
void SFXALDevice::setListener( U32 index, const SFXListenerProperties& listener )
{
if( index != 0 )
return;
// Torque and OpenAL are both right handed
// systems, so no coordinate flipping is needed.
const MatrixF &transform = listener.getTransform();
Point3F pos, tupple[2];
transform.getColumn( 3, &pos );
transform.getColumn( 1, &tupple[0] );
transform.getColumn( 2, &tupple[1] );
const VectorF &velocity = listener.getVelocity();
mOpenAL.alListenerfv( AL_POSITION, pos );
mOpenAL.alListenerfv( AL_VELOCITY, velocity );
mOpenAL.alListenerfv( AL_ORIENTATION, (const F32 *)&tupple[0] );
}
//-----------------------------------------------------------------------------
void SFXALDevice::setDistanceModel( SFXDistanceModel model )
{
switch( model )
{
case SFXDistanceModelLinear:
mOpenAL.alDistanceModel( AL_LINEAR_DISTANCE_CLAMPED );
if( mRolloffFactor != 1.0f )
_setRolloffFactor( 1.0f ); // No rolloff on linear.
break;
case SFXDistanceModelLogarithmic:
mOpenAL.alDistanceModel( AL_INVERSE_DISTANCE_CLAMPED );
if( mUserRolloffFactor != mRolloffFactor )
_setRolloffFactor( mUserRolloffFactor );
break;
default:
AssertWarn( false, "SFXALDevice::setDistanceModel - distance model not implemented" );
}
mDistanceModel = model;
}
//-----------------------------------------------------------------------------
void SFXALDevice::setDopplerFactor( F32 factor )
{
mOpenAL.alDopplerFactor( factor );
}
//-----------------------------------------------------------------------------
void SFXALDevice::_setRolloffFactor( F32 factor )
{
mRolloffFactor = factor;
for( U32 i = 0, num = mVoices.size(); i < num; ++ i )
mOpenAL.alSourcef( ( ( SFXALVoice* ) mVoices[ i ] )->mSourceName, AL_ROLLOFF_FACTOR, factor );
}
//-----------------------------------------------------------------------------
void SFXALDevice::setRolloffFactor( F32 factor )
{
if( mDistanceModel == SFXDistanceModelLinear && factor != 1.0f )
Con::errorf( "SFXALDevice::setRolloffFactor - rolloff factor <> 1.0f ignored in linear distance model" );
else
_setRolloffFactor( factor );
mUserRolloffFactor = factor;
}

View file

@ -0,0 +1,90 @@
//-----------------------------------------------------------------------------
// 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 _SFXALDEVICE_H_
#define _SFXALDEVICE_H_
class SFXProvider;
#ifndef _SFXDEVICE_H_
# include "sfx/sfxDevice.h"
#endif
#ifndef _SFXPROVIDER_H_
# include "sfx/sfxProvider.h"
#endif
#ifndef _SFXALBUFFER_H_
# include "sfx/openal/sfxALBuffer.h"
#endif
#ifndef _SFXALVOICE_H_
# include "sfx/openal/sfxALVoice.h"
#endif
#ifndef _OPENALFNTABLE
# include "sfx/openal/LoadOAL.h"
#endif
class SFXALDevice : public SFXDevice
{
public:
typedef SFXDevice Parent;
friend class SFXALVoice; // mDistanceFactor, mRolloffFactor
SFXALDevice( SFXProvider *provider,
const OPENALFNTABLE &openal,
String name,
bool useHardware,
S32 maxBuffers );
virtual ~SFXALDevice();
protected:
OPENALFNTABLE mOpenAL;
ALCcontext *mContext;
ALCdevice *mDevice;
SFXDistanceModel mDistanceModel;
F32 mDistanceFactor;
F32 mRolloffFactor;
F32 mUserRolloffFactor;
void _setRolloffFactor( F32 factor );
public:
// SFXDevice.
virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
virtual SFXVoice* createVoice( bool is3D, SFXBuffer *buffer );
virtual void setListener( U32 index, const SFXListenerProperties& listener );
virtual void setDistanceModel( SFXDistanceModel model );
virtual void setDopplerFactor( F32 factor );
virtual void setRolloffFactor( F32 factor );
};
#endif // _SFXALDEVICE_H_

View file

@ -0,0 +1,146 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/sfxProvider.h"
#include "sfx/openal/sfxALDevice.h"
#include "sfx/openal/aldlist.h"
#include "core/strings/stringFunctions.h"
#include "console/console.h"
#include "core/module.h"
class SFXALProvider : public SFXProvider
{
public:
SFXALProvider()
: SFXProvider( "OpenAL" ) { mALDL = NULL; }
virtual ~SFXALProvider();
protected:
OPENALFNTABLE mOpenAL;
ALDeviceList *mALDL;
struct ALDeviceInfo : SFXDeviceInfo
{
};
void init();
public:
SFXDevice *createDevice( const String& deviceName, bool useHardware, S32 maxBuffers );
};
MODULE_BEGIN( OpenAL )
MODULE_INIT_BEFORE( SFX )
MODULE_SHUTDOWN_AFTER( SFX )
SFXALProvider* mProvider;
MODULE_INIT
{
mProvider = new SFXALProvider;
}
MODULE_SHUTDOWN
{
delete mProvider;
}
MODULE_END;
void SFXALProvider::init()
{
if( LoadOAL10Library( NULL, &mOpenAL ) != AL_TRUE )
{
Con::printf( "SFXALProvider - OpenAL not available." );
return;
}
mALDL = new ALDeviceList( mOpenAL );
// Did we get any devices?
if ( mALDL->GetNumDevices() < 1 )
{
Con::printf( "SFXALProvider - No valid devices found!" );
return;
}
// Cool, loop through them, and caps em
const char *deviceFormat = "OpenAL v%d.%d %s";
char temp[256];
for( int i = 0; i < mALDL->GetNumDevices(); i++ )
{
ALDeviceInfo* info = new ALDeviceInfo;
info->name = String( mALDL->GetDeviceName( i ) );
int major, minor, eax = 0;
mALDL->GetDeviceVersion( i, &major, &minor );
// Apologies for the blatent enum hack -patw
for( int j = SFXALEAX2; j < SFXALEAXRAM; j++ )
eax += (int)mALDL->IsExtensionSupported( i, (SFXALCaps)j );
if( eax > 0 )
{
eax += 2; // EAX support starts at 2.0
dSprintf( temp, sizeof( temp ), "[EAX %d.0] %s", eax, ( mALDL->IsExtensionSupported( i, SFXALEAXRAM ) ? "EAX-RAM" : "" ) );
}
else
dStrcpy( temp, "" );
info->driver = String::ToString( deviceFormat, major, minor, temp );
info->hasHardware = eax > 0;
info->maxBuffers = mALDL->GetMaxNumSources( i );
mDeviceInfo.push_back( info );
}
regProvider( this );
}
SFXALProvider::~SFXALProvider()
{
UnloadOAL10Library();
if (mALDL)
delete mALDL;
}
SFXDevice *SFXALProvider::createDevice( const String& deviceName, bool useHardware, S32 maxBuffers )
{
ALDeviceInfo *info = dynamic_cast< ALDeviceInfo* >
( _findDeviceInfo( deviceName) );
// Do we find one to create?
if ( info )
return new SFXALDevice( this, mOpenAL, info->name, useHardware, maxBuffers );
return NULL;
}

View file

@ -0,0 +1,257 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/openal/sfxALVoice.h"
#include "sfx/openal/sfxALBuffer.h"
#include "sfx/openal/sfxALDevice.h"
#ifdef TORQUE_DEBUG
# define AL_SANITY_CHECK() \
AssertFatal( mOpenAL.alIsSource( mSourceName ), "AL Source Sanity Check Failed!" );
#else
# define AL_SANITY_CHECK()
#endif
//#define DEBUG_SPEW
SFXALVoice* SFXALVoice::create( SFXALDevice* device, SFXALBuffer *buffer )
{
AssertFatal( buffer, "SFXALVoice::create() - Got null buffer!" );
ALuint sourceName;
device->mOpenAL.alGenSources( 1, &sourceName );
AssertFatal( device->mOpenAL.alIsSource( sourceName ), "AL Source Sanity Check Failed!" );
// Is this 3d?
// Okay, this looks odd, but bear with me for a moment. AL_SOURCE_RELATIVE does NOT indicate
// whether or not the volume of the sound should change depending on the position of the listener.
// OpenAL assumes that the volume will ALWAYS depend on the position of the listener. What AL_SOURCE_RELATIVE
// does do is dictate if the position of THIS SOURCE is relative to the listener. If AL_SOURCE_RELATIVE is AL_TRUE
// and the source's position is 0, 0, 0, then the source is directly on top of the listener at all times, which is what
// we want for non-3d sounds.
device->mOpenAL.alSourcei( sourceName, AL_SOURCE_RELATIVE, ( buffer->mIs3d ? AL_FALSE : AL_TRUE ) );
if( buffer->mIs3d )
device->mOpenAL.alSourcef( sourceName, AL_ROLLOFF_FACTOR, device->mRolloffFactor );
SFXALVoice *voice = new SFXALVoice( device->mOpenAL,
buffer,
sourceName );
return voice;
}
SFXALVoice::SFXALVoice( const OPENALFNTABLE &oalft,
SFXALBuffer *buffer,
ALuint sourceName )
: Parent( buffer ),
mOpenAL( oalft ),
mResumeAtSampleOffset( -1.0f ),
mSourceName( sourceName ),
mSampleOffset( 0 )
{
AL_SANITY_CHECK();
}
SFXALVoice::~SFXALVoice()
{
mOpenAL.alDeleteSources( 1, &mSourceName );
}
void SFXALVoice::_lateBindStaticBufferIfNecessary()
{
if( !mBuffer->isStreaming() )
{
ALint bufferId;
mOpenAL.alGetSourcei( mSourceName, AL_BUFFER, &bufferId );
if( !bufferId )
mOpenAL.alSourcei( mSourceName, AL_BUFFER, _getBuffer()->mALBuffer );
}
}
SFXStatus SFXALVoice::_status() const
{
AL_SANITY_CHECK();
ALint state;
mOpenAL.alGetSourcei( mSourceName, AL_SOURCE_STATE, &state );
switch( state )
{
case AL_PLAYING: return SFXStatusPlaying;
case AL_PAUSED: return SFXStatusPaused;
default: return SFXStatusStopped;
}
}
void SFXALVoice::_play()
{
AL_SANITY_CHECK();
_lateBindStaticBufferIfNecessary();
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXALVoice] Starting playback" );
#endif
mOpenAL.alSourcePlay( mSourceName );
//WORKAROUND: Adjust play cursor for buggy OAL when resuming playback. Do this after alSourcePlay
// as it is the play function that will cause the cursor to jump.
if( mResumeAtSampleOffset != -1.0f )
{
mOpenAL.alSourcef( mSourceName, AL_SAMPLE_OFFSET, mResumeAtSampleOffset );
mResumeAtSampleOffset = -1.0f;
}
}
void SFXALVoice::_pause()
{
AL_SANITY_CHECK();
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXALVoice] Pausing playback" );
#endif
mOpenAL.alSourcePause( mSourceName );
//WORKAROUND: Another workaround for buggy OAL. Resuming playback of a paused source will cause the
// play cursor to jump. Save the cursor so we can manually move it into position in _play(). Sigh.
mOpenAL.alGetSourcef( mSourceName, AL_SAMPLE_OFFSET, &mResumeAtSampleOffset );
}
void SFXALVoice::_stop()
{
AL_SANITY_CHECK();
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXALVoice] Stopping playback" );
#endif
mOpenAL.alSourceStop( mSourceName );
mSampleOffset = 0;
mResumeAtSampleOffset = -1.0f;
}
void SFXALVoice::_seek( U32 sample )
{
AL_SANITY_CHECK();
_lateBindStaticBufferIfNecessary();
mOpenAL.alSourcei( mSourceName, AL_SAMPLE_OFFSET, sample );
mResumeAtSampleOffset = -1.0f;
}
U32 SFXALVoice::_tell() const
{
// Flush processed buffers as AL_SAMPLE_OFFSET will snap back to zero as soon
// as the queue is processed in whole.
if( mBuffer->isStreaming() )
mBuffer->write( NULL, 0 );
ALint pos;
mOpenAL.alGetSourcei( mSourceName, AL_SAMPLE_OFFSET, &pos );
return ( pos + mSampleOffset );
}
void SFXALVoice::setMinMaxDistance( F32 min, F32 max )
{
AL_SANITY_CHECK();
mOpenAL.alSourcef( mSourceName, AL_REFERENCE_DISTANCE, min );
mOpenAL.alSourcef( mSourceName, AL_MAX_DISTANCE, max );
}
void SFXALVoice::play( bool looping )
{
AL_SANITY_CHECK();
mOpenAL.alSourceStop( mSourceName );
if( !mBuffer->isStreaming() )
mOpenAL.alSourcei( mSourceName, AL_LOOPING, ( looping ? AL_TRUE : AL_FALSE ) );
Parent::play( looping );
}
void SFXALVoice::setVelocity( const VectorF& velocity )
{
AL_SANITY_CHECK();
// Torque and OpenAL are both right handed
// systems, so no coordinate flipping is needed.
mOpenAL.alSourcefv( mSourceName, AL_VELOCITY, velocity );
}
void SFXALVoice::setTransform( const MatrixF& transform )
{
AL_SANITY_CHECK();
// Torque and OpenAL are both right handed
// systems, so no coordinate flipping is needed.
Point3F pos, dir;
transform.getColumn( 3, &pos );
transform.getColumn( 1, &dir );
mOpenAL.alSourcefv( mSourceName, AL_POSITION, pos );
mOpenAL.alSourcefv( mSourceName, AL_DIRECTION, dir );
}
void SFXALVoice::setVolume( F32 volume )
{
AL_SANITY_CHECK();
mOpenAL.alSourcef( mSourceName, AL_GAIN, volume );
}
void SFXALVoice::setPitch( F32 pitch )
{
AL_SANITY_CHECK();
mOpenAL.alSourcef( mSourceName, AL_PITCH, pitch );
}
void SFXALVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume )
{
AL_SANITY_CHECK();
mOpenAL.alSourcef( mSourceName, AL_CONE_INNER_ANGLE, innerAngle );
mOpenAL.alSourcef( mSourceName, AL_CONE_OUTER_ANGLE, outerAngle );
mOpenAL.alSourcef( mSourceName, AL_CONE_OUTER_GAIN, outerVolume );
}
void SFXALVoice::setRolloffFactor( F32 factor )
{
mOpenAL.alSourcef( mSourceName, AL_ROLLOFF_FACTOR, factor );
}

View file

@ -0,0 +1,107 @@
//-----------------------------------------------------------------------------
// 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 _SFXALVOICE_H_
#define _SFXALVOICE_H_
#ifndef _SFXVOICE_H_
#include "sfx/sfxVoice.h"
#endif
#ifndef _OPENALFNTABLE
#include "sfx/openal/LoadOAL.h"
#endif
#ifndef _PLATFORM_THREADS_MUTEX_H_
#include "platform/threads/mutex.h"
#endif
class SFXALBuffer;
class SFXALDevice;
class SFXALVoice : public SFXVoice
{
public:
typedef SFXVoice Parent;
friend class SFXALDevice;
friend class SFXALBuffer;
protected:
SFXALVoice( const OPENALFNTABLE &oalft,
SFXALBuffer *buffer,
ALuint sourceName );
ALuint mSourceName;
/// Buggy OAL jumps around when pausing. Save playback cursor here.
F32 mResumeAtSampleOffset;
/// Amount by which OAL's reported sample position is offset.
///
/// OAL's sample position is relative to the current queue state,
/// so we manually need to keep track of how far into the total
/// queue we are.
U32 mSampleOffset;
Mutex mMutex;
const OPENALFNTABLE &mOpenAL;
///
SFXALBuffer* _getBuffer() const
{
return ( SFXALBuffer* ) mBuffer.getPointer();
}
/// For non-streaming buffers, late-bind the audio buffer
/// to the source as OAL will not accept writes to buffers
/// already bound.
void _lateBindStaticBufferIfNecessary();
// SFXVoice.
virtual SFXStatus _status() const;
virtual void _play();
virtual void _pause();
virtual void _stop();
virtual void _seek( U32 sample );
virtual U32 _tell() const;
public:
static SFXALVoice* create( SFXALDevice* device,
SFXALBuffer *buffer );
virtual ~SFXALVoice();
/// SFXVoice
void setMinMaxDistance( F32 min, F32 max );
void play( bool looping );
void setVelocity( const VectorF& velocity );
void setTransform( const MatrixF& transform );
void setVolume( F32 volume );
void setPitch( F32 pitch );
void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
void setRolloffFactor( F32 factor );
};
#endif // _SFXALVOICE_H_

View file

@ -0,0 +1,454 @@
/*
* Copyright (c) 2006, Creative Labs Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and
* the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the distribution.
* * Neither the name of Creative Labs Inc. nor the names of its contributors may be used to endorse or
* promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <windows.h>
#include "sfx/openal/LoadOAL.h"
HINSTANCE g_hOpenALDLL = NULL;
ALboolean LoadOAL10Library(char *szOALFullPathName, LPOPENALFNTABLE lpOALFnTable)
{
if (!lpOALFnTable)
return AL_FALSE;
if (szOALFullPathName)
g_hOpenALDLL = LoadLibraryA(szOALFullPathName);
else
g_hOpenALDLL = LoadLibraryA("openal32.dll");
if (!g_hOpenALDLL)
return AL_FALSE;
memset(lpOALFnTable, 0, sizeof(OPENALFNTABLE));
// Get function pointers
lpOALFnTable->alEnable = (LPALENABLE)GetProcAddress(g_hOpenALDLL, "alEnable");
if (lpOALFnTable->alEnable == NULL)
{
OutputDebugStringA("Failed to retrieve 'alEnable' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDisable = (LPALDISABLE)GetProcAddress(g_hOpenALDLL, "alDisable");
if (lpOALFnTable->alDisable == NULL)
{
OutputDebugStringA("Failed to retrieve 'alDisable' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsEnabled = (LPALISENABLED)GetProcAddress(g_hOpenALDLL, "alIsEnabled");
if (lpOALFnTable->alIsEnabled == NULL)
{
OutputDebugStringA("Failed to retrieve 'alIsEnabled' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBoolean = (LPALGETBOOLEAN)GetProcAddress(g_hOpenALDLL, "alGetBoolean");
if (lpOALFnTable->alGetBoolean == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetBoolean' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetInteger = (LPALGETINTEGER)GetProcAddress(g_hOpenALDLL, "alGetInteger");
if (lpOALFnTable->alGetInteger == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetInteger' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetFloat = (LPALGETFLOAT)GetProcAddress(g_hOpenALDLL, "alGetFloat");
if (lpOALFnTable->alGetFloat == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetFloat' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetDouble = (LPALGETDOUBLE)GetProcAddress(g_hOpenALDLL, "alGetDouble");
if (lpOALFnTable->alGetDouble == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetDouble' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBooleanv = (LPALGETBOOLEANV)GetProcAddress(g_hOpenALDLL, "alGetBooleanv");
if (lpOALFnTable->alGetBooleanv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetBooleanv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetIntegerv = (LPALGETINTEGERV)GetProcAddress(g_hOpenALDLL, "alGetIntegerv");
if (lpOALFnTable->alGetIntegerv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetIntegerv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetFloatv = (LPALGETFLOATV)GetProcAddress(g_hOpenALDLL, "alGetFloatv");
if (lpOALFnTable->alGetFloatv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetFloatv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetDoublev = (LPALGETDOUBLEV)GetProcAddress(g_hOpenALDLL, "alGetDoublev");
if (lpOALFnTable->alGetDoublev == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetDoublev' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetString = (LPALGETSTRING)GetProcAddress(g_hOpenALDLL, "alGetString");
if (lpOALFnTable->alGetString == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetString' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetError = (LPALGETERROR)GetProcAddress(g_hOpenALDLL, "alGetError");
if (lpOALFnTable->alGetError == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetError' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsExtensionPresent = (LPALISEXTENSIONPRESENT)GetProcAddress(g_hOpenALDLL, "alIsExtensionPresent");
if (lpOALFnTable->alIsExtensionPresent == NULL)
{
OutputDebugStringA("Failed to retrieve 'alIsExtensionPresent' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetProcAddress = (LPALGETPROCADDRESS)GetProcAddress(g_hOpenALDLL, "alGetProcAddress");
if (lpOALFnTable->alGetProcAddress == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetProcAddress' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetEnumValue = (LPALGETENUMVALUE)GetProcAddress(g_hOpenALDLL, "alGetEnumValue");
if (lpOALFnTable->alGetEnumValue == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetEnumValue' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListeneri = (LPALLISTENERI)GetProcAddress(g_hOpenALDLL, "alListeneri");
if (lpOALFnTable->alListeneri == NULL)
{
OutputDebugStringA("Failed to retrieve 'alListeneri' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListenerf = (LPALLISTENERF)GetProcAddress(g_hOpenALDLL, "alListenerf");
if (lpOALFnTable->alListenerf == NULL)
{
OutputDebugStringA("Failed to retrieve 'alListenerf' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListener3f = (LPALLISTENER3F)GetProcAddress(g_hOpenALDLL, "alListener3f");
if (lpOALFnTable->alListener3f == NULL)
{
OutputDebugStringA("Failed to retrieve 'alListener3f' function address\n");
return AL_FALSE;
}
lpOALFnTable->alListenerfv = (LPALLISTENERFV)GetProcAddress(g_hOpenALDLL, "alListenerfv");
if (lpOALFnTable->alListenerfv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alListenerfv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListeneri = (LPALGETLISTENERI)GetProcAddress(g_hOpenALDLL, "alGetListeneri");
if (lpOALFnTable->alGetListeneri == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetListeneri' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListenerf =(LPALGETLISTENERF)GetProcAddress(g_hOpenALDLL, "alGetListenerf");
if (lpOALFnTable->alGetListenerf == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetListenerf' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListener3f = (LPALGETLISTENER3F)GetProcAddress(g_hOpenALDLL, "alGetListener3f");
if (lpOALFnTable->alGetListener3f == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetListener3f' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetListenerfv = (LPALGETLISTENERFV)GetProcAddress(g_hOpenALDLL, "alGetListenerfv");
if (lpOALFnTable->alGetListenerfv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetListenerfv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGenSources = (LPALGENSOURCES)GetProcAddress(g_hOpenALDLL, "alGenSources");
if (lpOALFnTable->alGenSources == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGenSources' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDeleteSources = (LPALDELETESOURCES)GetProcAddress(g_hOpenALDLL, "alDeleteSources");
if (lpOALFnTable->alDeleteSources == NULL)
{
OutputDebugStringA("Failed to retrieve 'alDeleteSources' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsSource = (LPALISSOURCE)GetProcAddress(g_hOpenALDLL, "alIsSource");
if (lpOALFnTable->alIsSource == NULL)
{
OutputDebugStringA("Failed to retrieve 'alIsSource' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcei = (LPALSOURCEI)GetProcAddress(g_hOpenALDLL, "alSourcei");
if (lpOALFnTable->alSourcei == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourcei' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcef = (LPALSOURCEF)GetProcAddress(g_hOpenALDLL, "alSourcef");
if (lpOALFnTable->alSourcef == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourcef' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSource3f = (LPALSOURCE3F)GetProcAddress(g_hOpenALDLL, "alSource3f");
if (lpOALFnTable->alSource3f == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSource3f' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcefv = (LPALSOURCEFV)GetProcAddress(g_hOpenALDLL, "alSourcefv");
if (lpOALFnTable->alSourcefv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourcefv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetSourcei = (LPALGETSOURCEI)GetProcAddress(g_hOpenALDLL, "alGetSourcei");
if (lpOALFnTable->alGetSourcei == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetSourcei' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetSourcef = (LPALGETSOURCEF)GetProcAddress(g_hOpenALDLL, "alGetSourcef");
if (lpOALFnTable->alGetSourcef == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetSourcef' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetSourcefv = (LPALGETSOURCEFV)GetProcAddress(g_hOpenALDLL, "alGetSourcefv");
if (lpOALFnTable->alGetSourcefv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetSourcefv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcePlayv = (LPALSOURCEPLAYV)GetProcAddress(g_hOpenALDLL, "alSourcePlayv");
if (lpOALFnTable->alSourcePlayv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourcePlayv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceStopv = (LPALSOURCESTOPV)GetProcAddress(g_hOpenALDLL, "alSourceStopv");
if (lpOALFnTable->alSourceStopv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourceStopv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcePlay = (LPALSOURCEPLAY)GetProcAddress(g_hOpenALDLL, "alSourcePlay");
if (lpOALFnTable->alSourcePlay == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourcePlay' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourcePause = (LPALSOURCEPAUSE)GetProcAddress(g_hOpenALDLL, "alSourcePause");
if (lpOALFnTable->alSourcePause == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourcePause' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceStop = (LPALSOURCESTOP)GetProcAddress(g_hOpenALDLL, "alSourceStop");
if (lpOALFnTable->alSourceStop == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourceStop' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceRewind = (LPALSOURCEREWIND)GetProcAddress(g_hOpenALDLL, "alSourceRewind");
if (lpOALFnTable->alSourceRewind == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourceRewind' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGenBuffers = (LPALGENBUFFERS)GetProcAddress(g_hOpenALDLL, "alGenBuffers");
if (lpOALFnTable->alGenBuffers == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGenBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDeleteBuffers = (LPALDELETEBUFFERS)GetProcAddress(g_hOpenALDLL, "alDeleteBuffers");
if (lpOALFnTable->alDeleteBuffers == NULL)
{
OutputDebugStringA("Failed to retrieve 'alDeleteBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alIsBuffer = (LPALISBUFFER)GetProcAddress(g_hOpenALDLL, "alIsBuffer");
if (lpOALFnTable->alIsBuffer == NULL)
{
OutputDebugStringA("Failed to retrieve 'alIsBuffer' function address\n");
return AL_FALSE;
}
lpOALFnTable->alBufferData = (LPALBUFFERDATA)GetProcAddress(g_hOpenALDLL, "alBufferData");
if (lpOALFnTable->alBufferData == NULL)
{
OutputDebugStringA("Failed to retrieve 'alBufferData' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBufferi = (LPALGETBUFFERI)GetProcAddress(g_hOpenALDLL, "alGetBufferi");
if (lpOALFnTable->alGetBufferi == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetBufferi' function address\n");
return AL_FALSE;
}
lpOALFnTable->alGetBufferf = (LPALGETBUFFERF)GetProcAddress(g_hOpenALDLL, "alGetBufferf");
if (lpOALFnTable->alGetBufferf == NULL)
{
OutputDebugStringA("Failed to retrieve 'alGetBufferf' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceQueueBuffers = (LPALSOURCEQUEUEBUFFERS)GetProcAddress(g_hOpenALDLL, "alSourceQueueBuffers");
if (lpOALFnTable->alSourceQueueBuffers == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourceQueueBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alSourceUnqueueBuffers = (LPALSOURCEUNQUEUEBUFFERS)GetProcAddress(g_hOpenALDLL, "alSourceUnqueueBuffers");
if (lpOALFnTable->alSourceUnqueueBuffers == NULL)
{
OutputDebugStringA("Failed to retrieve 'alSourceUnqueueBuffers' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDistanceModel = (LPALDISTANCEMODEL)GetProcAddress(g_hOpenALDLL, "alDistanceModel");
if (lpOALFnTable->alDistanceModel == NULL)
{
OutputDebugStringA("Failed to retrieve 'alDistanceModel' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDopplerFactor = (LPALDOPPLERFACTOR)GetProcAddress(g_hOpenALDLL, "alDopplerFactor");
if (lpOALFnTable->alDopplerFactor == NULL)
{
OutputDebugStringA("Failed to retrieve 'alDopplerFactor' function address\n");
return AL_FALSE;
}
lpOALFnTable->alDopplerVelocity = (LPALDOPPLERVELOCITY)GetProcAddress(g_hOpenALDLL, "alDopplerVelocity");
if (lpOALFnTable->alDopplerVelocity == NULL)
{
OutputDebugStringA("Failed to retrieve 'alDopplerVelocity' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetString = (LPALCGETSTRING)GetProcAddress(g_hOpenALDLL, "alcGetString");
if (lpOALFnTable->alcGetString == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetString' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetIntegerv = (LPALCGETINTEGERV)GetProcAddress(g_hOpenALDLL, "alcGetIntegerv");
if (lpOALFnTable->alcGetIntegerv == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetIntegerv' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcOpenDevice = (LPALCOPENDEVICE)GetProcAddress(g_hOpenALDLL, "alcOpenDevice");
if (lpOALFnTable->alcOpenDevice == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcOpenDevice' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcCloseDevice = (LPALCCLOSEDEVICE)GetProcAddress(g_hOpenALDLL, "alcCloseDevice");
if (lpOALFnTable->alcCloseDevice == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcCloseDevice' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcCreateContext = (LPALCCREATECONTEXT)GetProcAddress(g_hOpenALDLL, "alcCreateContext");
if (lpOALFnTable->alcCreateContext == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcCreateContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcMakeContextCurrent = (LPALCMAKECONTEXTCURRENT)GetProcAddress(g_hOpenALDLL, "alcMakeContextCurrent");
if (lpOALFnTable->alcMakeContextCurrent == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcMakeContextCurrent' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcProcessContext = (LPALCPROCESSCONTEXT)GetProcAddress(g_hOpenALDLL, "alcProcessContext");
if (lpOALFnTable->alcProcessContext == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcProcessContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetCurrentContext = (LPALCGETCURRENTCONTEXT)GetProcAddress(g_hOpenALDLL, "alcGetCurrentContext");
if (lpOALFnTable->alcGetCurrentContext == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetCurrentContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetContextsDevice = (LPALCGETCONTEXTSDEVICE)GetProcAddress(g_hOpenALDLL, "alcGetContextsDevice");
if (lpOALFnTable->alcGetContextsDevice == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetContextsDevice' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcSuspendContext = (LPALCSUSPENDCONTEXT)GetProcAddress(g_hOpenALDLL, "alcSuspendContext");
if (lpOALFnTable->alcSuspendContext == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcSuspendContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcDestroyContext = (LPALCDESTROYCONTEXT)GetProcAddress(g_hOpenALDLL, "alcDestroyContext");
if (lpOALFnTable->alcDestroyContext == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcDestroyContext' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetError = (LPALCGETERROR)GetProcAddress(g_hOpenALDLL, "alcGetError");
if (lpOALFnTable->alcGetError == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetError' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcIsExtensionPresent = (LPALCISEXTENSIONPRESENT)GetProcAddress(g_hOpenALDLL, "alcIsExtensionPresent");
if (lpOALFnTable->alcIsExtensionPresent == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcIsExtensionPresent' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetProcAddress = (LPALCGETPROCADDRESS)GetProcAddress(g_hOpenALDLL, "alcGetProcAddress");
if (lpOALFnTable->alcGetProcAddress == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetProcAddress' function address\n");
return AL_FALSE;
}
lpOALFnTable->alcGetEnumValue = (LPALCGETENUMVALUE)GetProcAddress(g_hOpenALDLL, "alcGetEnumValue");
if (lpOALFnTable->alcGetEnumValue == NULL)
{
OutputDebugStringA("Failed to retrieve 'alcGetEnumValue' function address\n");
return AL_FALSE;
}
return AL_TRUE;
}
ALvoid UnloadOAL10Library()
{
// Unload the dll
if (g_hOpenALDLL)
{
FreeLibrary(g_hOpenALDLL);
g_hOpenALDLL = NULL;
}
}

View file

@ -0,0 +1,212 @@
//-----------------------------------------------------------------------------
// 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/sfxAmbience.h"
#include "sfx/sfxEnvironment.h"
#include "sfx/sfxTrack.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxTypes.h"
#include "math/mathTypes.h"
#include "core/stream/bitStream.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXAmbience );
ConsoleDocClass( SFXAmbience,
"@brief A datablock that describes an ambient sound space.\n\n"
"Each ambience datablock captures the properties of a unique ambient sound space. A sound space is comprised of:\n"
"- an ambient audio track that is played when the listener is inside the space,\n"
"- a reverb environment that is active inside the space, and\n"
"- a number of SFXStates that are activated when entering the space and deactivated when exiting it.\n"
"\n"
"Each of these properties is optional.\n\n"
"An important characteristic of ambient audio spaces is that their unique nature is not determined by their location "
"in space but rather by their SFXAmbience datablock. This means that the same SFXAmbience datablock assigned to "
"multiple locations in a level represents the same unique audio space to the sound system.\n\n"
"This is an important distinction for the ambient sound mixer which will activate a given ambient audio space only "
"once at any one time regardless of how many intersecting audio spaces with the same SFXAmbience datablock assigned "
"the listener may currently be in.\n\n"
"All SFXAmbience instances are automatically added to the global @c SFXAmbienceSet.\n\n"
"At the moment, transitions between reverb environments are not blended and different reverb environments from multiple "
"active SFXAmbiences will not be blended together. This will be added in a future version.\n\n"
"@tsexample\n"
"singleton SFXAmbience( Underwater )\n"
"{\n"
" environment = AudioEnvUnderwater;\n"
" soundTrack = ScubaSoundList;\n"
" states[ 0 ] = AudioLocationUnderwater;\n"
"};\n"
"@endtsexample\n\n"
"@see SFXEnvironment\n"
"@see SFXTrack\n"
"@see SFXState\n"
"@see LevelInfo::soundAmbience\n"
"@see Zone::soundAmbience\n\n"
"@ref Datablock_Networking\n"
"@ingroup SFX\n"
"@ingroup Datablocks\n"
);
SFXAmbience::ChangeSignal SFXAmbience::smChangeSignal;
//-----------------------------------------------------------------------------
SFXAmbience::SFXAmbience()
: mEnvironment( NULL ),
mSoundTrack( NULL ),
mRolloffFactor( 1.f ),
mDopplerFactor( 0.5f )
{
dMemset( mState, 0, sizeof( mState ) );
}
//-----------------------------------------------------------------------------
void SFXAmbience::initPersistFields()
{
addGroup( "Sound" );
addField( "environment", TypeSFXEnvironmentName, Offset( mEnvironment, SFXAmbience ),
"Reverb environment active in the ambience zone.\n"
"@ref SFX_reverb" );
addField( "soundTrack", TypeSFXTrackName, Offset( mSoundTrack, SFXAmbience ),
"Sound track to play in the ambience zone." );
addField( "rolloffFactor", TypeF32, Offset( mRolloffFactor, SFXAmbience ),
"The rolloff factor to apply to distance-based volume attenuation in this space.\n"
"Defaults to 1.0.\n\n"
"@note This applies to the logarithmic distance model only.\n\n"
"@ref SFXSource_volume" );
addField( "dopplerFactor", TypeF32, Offset( mDopplerFactor, SFXAmbience ),
"The factor to apply to the doppler affect in this space.\n"
"Defaults to 0.5.\n\n"
"@ref SFXSource_doppler" );
addField( "states", TypeSFXStateName, Offset( mState, SFXAmbience ),
MaxStates,
"States to activate when the ambient zone is entered.\n"
"When the ambient sound state is entered, all states associated with the state will "
"be activated (given that they are not disabled) and deactivated when the space "
"is exited again." );
endGroup( "Sound" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXAmbience::onAdd()
{
if( !Parent::onAdd() )
return false;
Sim::getSFXAmbienceSet()->addObject( this );
return true;
}
//-----------------------------------------------------------------------------
bool SFXAmbience::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
validate();
// Resolve datablocks on client.
if( !server )
{
if( !sfxResolve( &mEnvironment, errorStr ) )
return false;
if( !sfxResolve( &mSoundTrack, errorStr ) )
return false;
for( U32 i = 0; i < MaxStates; ++ i )
if( !sfxResolve( &mState[ i ], errorStr ) )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
void SFXAmbience::packData( BitStream* stream )
{
Parent::packData( stream );
sfxWrite( stream, mEnvironment );
sfxWrite( stream, mSoundTrack );
stream->write( mRolloffFactor );
stream->write( mDopplerFactor );
for( U32 i = 0; i < MaxStates; ++ i )
sfxWrite( stream, mState[ i ] );
}
//-----------------------------------------------------------------------------
void SFXAmbience::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
sfxRead( stream, &mEnvironment );
sfxRead( stream, &mSoundTrack );
stream->read( &mRolloffFactor );
stream->read( &mDopplerFactor );
for( U32 i = 0; i < MaxStates; ++ i )
sfxRead( stream, &mState[ i ] );
}
//-----------------------------------------------------------------------------
void SFXAmbience::inspectPostApply()
{
Parent::inspectPostApply();
validate();
smChangeSignal.trigger( this );
}
//-----------------------------------------------------------------------------
void SFXAmbience::validate()
{
}

View file

@ -0,0 +1,120 @@
//-----------------------------------------------------------------------------
// 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 _SFXAMBIENCE_H_
#define _SFXAMBIENCE_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
class SFXEnvironment;
class SFXTrack;
class SFXState;
/// Datablock for describing an ambient audio space.
///
class SFXAmbience : public SimDataBlock
{
public:
typedef SimDataBlock Parent;
typedef Signal< void( SFXAmbience* ) > ChangeSignal;
enum
{
/// Maximum number of states that can be tied to an ambient space.
MaxStates = 4
};
protected:
/// Doppler shift factor for this space.
F32 mDopplerFactor;
/// Rolloff factor for this space. Only applies to logarithmic distance model.
F32 mRolloffFactor;
/// Sound track to play when inside the ambient space.
SFXTrack* mSoundTrack;
/// Reverb environment to apply when inside the ambient space.
SFXEnvironment* mEnvironment;
/// SFXStates to activate when in this ambient space.
SFXState* mState[ MaxStates ];
///
static ChangeSignal smChangeSignal;
public:
SFXAmbience();
/// Ensure all properties of this ambience adhere to their value contraints.
void validate();
/// Return the rolloff factor to apply to distance-based volume attenuation in this space (logarithmic distance model only).
F32 getRolloffFactor() const { return mRolloffFactor; }
/// Return the doppler shift factor to apply in this space.
F32 getDopplerFactor() const { return mDopplerFactor; }
/// Return the reverb environment of the ambient space.
SFXEnvironment* getEnvironment() const { return mEnvironment; }
/// Return the ambient soundtrack of this ambient space.
SFXTrack* getSoundTrack() const { return mSoundTrack; }
/// Return the given state bound to this ambient space.
SFXState* getState( U32 i ) const
{
AssertFatal( i < MaxStates, "SFXState::getState() - index out of range" );
return mState[ i ];
}
///
static ChangeSignal& getChangeSignal() { return smChangeSignal; }
// SimDataBlock.
virtual bool onAdd();
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
virtual bool preload( bool server, String& errorStr );
virtual void inspectPostApply();
static void initPersistFields();
DECLARE_CONOBJECT( SFXAmbience );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "An ambient sound environment." );
};
#endif // !_SFXAMBIENCE_H_

View file

@ -0,0 +1,291 @@
//-----------------------------------------------------------------------------
// 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 );
}

View file

@ -0,0 +1,236 @@
//-----------------------------------------------------------------------------
// 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 _SFXBUFFER_H_
#define _SFXBUFFER_H_
#ifndef _REFBASE_H_
#include "core/util/refBase.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _THREADSAFEREFCOUNT_H_
#include "platform/threads/threadSafeRefCount.h"
#endif
class SFXStream;
class SFXDescription;
class SFXVoice;
namespace SFXInternal {
class SFXStreamPacket;
class SFXAsyncStream;
class SFXAsyncQueue;
void PurgeDeadBuffers();
}
/// The buffer interface hides the details of how the device holds sound data for playback.
///
/// A sound buffer may either be loaded once completely and then played as needed or it may
/// be progressively streamed from an SFXStream. In the latter case, there can only be a single
/// voice tied to the buffer.
///
/// @note SFXDevice is the last instance when it comes to ownership
/// of SFXBuffers. If the SFXDevice goes away, it will take all
/// SFXBuffers with it, regardless of whether there are still strong
/// refs to it. Use StrongWeakRefPtrs to keep pointers to
/// SFXBuffers!
///
/// @see SFXStream
class SFXBuffer : public StrongRefBase,
public IPolled, // SFXBuffers are periodically updated on the SFX thread.
public IOutputStream< SFXInternal::SFXStreamPacket* > // Interface for writing sound data to the buffer.
{
friend class SFXVoice; // mUniqueVoice
friend void SFXInternal::PurgeDeadBuffers(); // dtor
public:
typedef void Parent;
/// Status indicators for sound buffers.
enum Status
{
STATUS_Null, ///< Initial state.
STATUS_Loading, ///< Buffer has requested data and is waiting for queue to fill up.
STATUS_Ready, ///< Playback queue is fed and ready (non-stream buffers will stop at this state).
STATUS_Blocked, ///< Queue is starved and playback thus held until further data is available (streaming buffers only).
STATUS_AtEnd, ///< Buffer has read all its streaming data (streaming buffers only).
};
/// This signal is triggered from SFXBuffer's destructor so the sound system
/// can keep track of buffers being released on the device.
static Signal< void( SFXBuffer* ) > smBufferDestroyedSignal;
protected:
typedef ThreadSafeRef< SFXInternal::SFXAsyncStream > SFXAsyncStreamPtr;
typedef SFXInternal::SFXAsyncQueue* SFXAsyncQueuePtr;
/// Encapsulates the async I/O state of the sound buffer.
struct AsyncState : public ThreadSafeRefCount< AsyncState >
{
/// The sound packet stream.
SFXAsyncStreamPtr mStream;
/// The packet queue that feeds into the actual device buffer.
/// Only used for streaming buffers; non-streaming buffers directly receive
/// and upload sound packets without queuing.
SFXAsyncQueuePtr mQueue;
AsyncState();
AsyncState( SFXInternal::SFXAsyncStream* stream );
~AsyncState();
};
typedef ThreadSafeRef< AsyncState > AsyncStatePtr;
/// Create a new buffer from @a stream using the parameters in @a description.
///
/// @param stream Sound stream from which to read sound data into the buffer.
/// @param description Sound setup description.
/// @param createAsyncState If true, the asynchronous loading state for the buffer will be set up
/// in the constructor. This is mainly useful for the null device which creates dummy buffers that
/// do not need the async state to be in place. All other buffers do.
SFXBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description, bool createAsyncState = true );
/// Create a buffer with just a description. This is used by devices who fully take over loading
/// and streaming.
SFXBuffer( SFXDescription* description );
virtual ~SFXBuffer();
/// The buffer readiness status.
Status mStatus;
/// The sound sample format used by the buffer.
SFXFormat mFormat;
/// Total playback time of the associated sound stream in milliseconds.
/// @note For streaming buffers, this will not correspond to the actual
/// playtime of the device buffer.
U32 mDuration;
/// If true, this is a continuously streaming buffer.
bool mIsStreaming;
/// For streaming buffers, tells whether the source stream loops.
bool mIsLooping;
/// If true, this buffer can only have a single SFXVoice attached.
bool mIsUnique;
/// If true, the buffer is dead and will be deleted. Can't be in status
/// for synchronization reasons.
bool mIsDead;
/// Pointer to structure keeping the asynchronous I/O state of the buffer.
/// For non-streaming buffers, this is released as soon as all data is loaded.
///
/// To allow seeking in streaming buffers even after playback has ended,
/// we do not release the async state of these buffers until the buffer is
/// actually released itself. This allows to always access the associated
/// input stream.
///
/// @note For devices that handle loading/streaming on their own, this will
/// not be set.
AsyncStatePtr mAsyncState;
/// If this is a unique buffer (i.e. a streaming buffer), then this holds
/// the reference to the unique voice.
StrongWeakRefPtr< SFXVoice > mUniqueVoice;
/// Set the buffer status and trigger mOnStatusChange if the status changes.
/// @note Called on both the SFX update thread and the main thread.
void _setStatus( Status status );
/// Flush all queue state for this buffer on the device.
/// @note Called on the SFX update thread.
virtual void _flush() = 0;
public:
/// Signal that is triggered when the buffer status changes.
/// @note This signal is triggered on the same thread that the buffer update
/// code runs on.
Signal< void( SFXBuffer* buffer, Status newStatus ) > mOnStatusChange;
/// @return The current buffer loading/queue status.
Status getStatus() const { return mStatus; }
/// @return The sound sample format used by the buffer.
const SFXFormat& getFormat() const { return mFormat; }
/// @return The total playback time of the buffer in milliseconds.
U32 getDuration() const { return mDuration; }
/// @return The total number of samples in the buffer.
U32 getNumSamples() const { return getFormat().getSampleCount( mDuration ); }
/// @return The number of bytes consumed by this sound buffer.
virtual U32 getMemoryUsed() const { return 0; }
/// @return True if the buffer does continuous sound streaming.
bool isStreaming() const { return mIsStreaming; }
/// @return True if the buffer is pending deletion.
bool isDead() const { return mIsDead; }
/// @return True if the buffer's packet queue is loaded and ready for playback.
bool isReady() const { return ( getStatus() == STATUS_Ready ); }
/// @return True if the buffer's packet queue is still loading.
bool isLoading() const { return ( getStatus() == STATUS_Loading ); }
/// @return True if the buffer's packet queue has been starved and is waiting for data.
bool isBlocked() const { return ( getStatus() == STATUS_Blocked ); }
/// @return True if the buffer has exhausted its source stream
bool isAtEnd() const { return ( getStatus() == STATUS_AtEnd ); }
/// @return True if the buffer can only have a single SFXVoice attached to it.
bool isUnique() const { return mIsUnique; }
/// Start the async request chain for the buffer.
void load();
// IPolled.
virtual bool update();
// WeakRefBase.
virtual void destroySelf();
};
#endif // _SFXBUFFER_H_

View file

@ -0,0 +1,594 @@
//-----------------------------------------------------------------------------
// 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 _SFXCOMMON_H_
#define _SFXCOMMON_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _MMATHFN_H_
#include "math/mMathFn.h"
#endif
#ifndef _MRANDOM_H_
#include "math/mRandom.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _TYPETRAITS_H_
#include "platform/typetraits.h"
#endif
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
class SFXEnvironment;
class SFXPlayList;
//-----------------------------------------------------------------------------
// SFXStatus.
//-----------------------------------------------------------------------------
/// The sound playback state.
enum SFXStatus
{
/// Initial state; no operation yet performed on sound.
SFXStatusNull,
/// Sound is playing.
SFXStatusPlaying,
/// Sound has been stopped.
SFXStatusStopped,
/// Sound is paused.
SFXStatusPaused,
/// Sound stream is starved and playback blocked.
SFXStatusBlocked,
/// Temporary state while transitioning to another state. This is used when multiple
/// threads concurrently maintain a status and need to perform a sequence of actions before
/// being able to fully go from a previous to a new current state. In this case, the
/// transition state marks the status as being under update on another thread.
///
/// @note Not all places that use SFXStatus actually use this state.
SFXStatusTransition,
};
DefineEnumType( SFXStatus );
inline const char* SFXStatusToString( SFXStatus status )
{
switch ( status )
{
case SFXStatusPlaying: return "playing";
case SFXStatusStopped: return "stopped";
case SFXStatusPaused: return "paused";
case SFXStatusBlocked: return "blocked";
case SFXStatusTransition: return "transition";
case SFXStatusNull:
default: ;
}
return "null";
}
//-----------------------------------------------------------------------------
// SFXChannel.
//-----------------------------------------------------------------------------
/// Animatable channels in the SFX system.
enum SFXChannel
{
SFXChannelVolume,
SFXChannelPitch,
SFXChannelPriority,
SFXChannelPositionX,
SFXChannelPositionY,
SFXChannelPositionZ,
SFXChannelRotationX,
SFXChannelRotationY,
SFXChannelRotationZ,
SFXChannelVelocityX,
SFXChannelVelocityY,
SFXChannelVelocityZ,
SFXChannelMinDistance,
SFXChannelMaxDistance,
SFXChannelConeInsideAngle,
SFXChannelConeOutsideAngle,
SFXChannelConeOutsideVolume,
SFXChannelCursor,
SFXChannelStatus,
SFXChannelUser0,
SFXChannelUser1,
SFXChannelUser2,
SFXChannelUser3,
/// Total number of animatable channels.
SFX_NUM_CHANNELS
};
DefineEnumType( SFXChannel );
//-----------------------------------------------------------------------------
// SFXDistanceModel.
//-----------------------------------------------------------------------------
/// Rolloff curve used for distance volume attenuation of 3D sounds.
enum SFXDistanceModel
{
SFXDistanceModelLinear, ///< Volume decreases linearly from min to max where it reaches zero.
SFXDistanceModelLogarithmic, ///< Volume halves every min distance steps starting from min distance; attenuation stops at max distance.
};
DefineEnumType( SFXDistanceModel );
/// Compute the distance attenuation based on the given distance model.
///
/// @param minDistance Reference distance; attenuation starts here.
/// @param maxDistance
/// @param distance Actual distance of sound from listener.
/// @param volume Unattenuated volume.
/// @param rolloffFactor Rolloff curve scale factor.
///
/// @return The attenuated volume.
inline F32 SFXDistanceAttenuation( SFXDistanceModel model, F32 minDistance, F32 maxDistance, F32 distance, F32 volume, F32 rolloffFactor )
{
F32 gain = 1.0f;
switch( model )
{
case SFXDistanceModelLinear:
distance = getMax( distance, minDistance );
distance = getMin( distance, maxDistance );
gain = ( 1 - ( distance - minDistance ) / ( maxDistance - minDistance ) );
break;
case SFXDistanceModelLogarithmic:
distance = getMax( distance, minDistance );
distance = getMin( distance, maxDistance );
gain = minDistance / ( minDistance + rolloffFactor * ( distance - minDistance ) );
break;
}
return ( volume * gain );
}
//-----------------------------------------------------------------------------
// SFXFormat.
//-----------------------------------------------------------------------------
/// This class defines the various types of sound data that may be
/// used in the sound system.
///
/// Unlike with most sound APIs, we consider each sample point to comprise
/// all channels in a sound stream rather than only one value for a single
/// channel.
class SFXFormat
{
protected:
/// The number of sound channels in the data.
U8 mChannels;
/// The number of bits per sound sample.
U8 mBitsPerSample;
/// The frequency in samples per second.
U32 mSamplesPerSecond;
public:
SFXFormat( U8 channels = 0,
U8 bitsPerSample = 0,
U32 samplesPerSecond = 0 )
: mChannels( channels ),
mSamplesPerSecond( samplesPerSecond ),
mBitsPerSample( bitsPerSample )
{}
/// Copy constructor.
SFXFormat( const SFXFormat &format )
: mChannels( format.mChannels ),
mBitsPerSample( format.mBitsPerSample ),
mSamplesPerSecond( format.mSamplesPerSecond )
{}
public:
/// Sets the format.
void set( U8 channels,
U8 bitsPerSample,
U32 samplesPerSecond )
{
mChannels = channels;
mBitsPerSample = bitsPerSample;
mSamplesPerSecond = samplesPerSecond;
}
/// Comparision between formats.
bool operator == ( const SFXFormat& format ) const
{
return mChannels == format.mChannels &&
mBitsPerSample == format.mBitsPerSample &&
mSamplesPerSecond == format.mSamplesPerSecond;
}
/// Returns the number of sound channels.
U8 getChannels() const { return mChannels; }
/// Returns true if there is a single sound channel.
bool isMono() const { return mChannels == 1; }
/// Is true if there are two sound channels.
bool isStereo() const { return mChannels == 2; }
/// Is true if there are more than two sound channels.
bool isMultiChannel() const { return mChannels > 2; }
///
U32 getSamplesPerSecond() const { return mSamplesPerSecond; }
/// The bits of data per channel.
U8 getBitsPerChannel() const { return mBitsPerSample / mChannels; }
/// The number of bytes of data per channel.
U8 getBytesPerChannel() const { return getBitsPerChannel() / 8; }
/// The number of bits per sound sample.
U8 getBitsPerSample() const { return mBitsPerSample; }
/// The number of bytes of data per sample.
/// @note Be aware that this comprises all channels.
U8 getBytesPerSample() const { return mBitsPerSample / 8; }
/// Returns the duration from the sample count.
U32 getDuration( U32 samples ) const
{
// Use 64bit types to avoid overflow during division.
return ( (U64)samples * (U64)1000 ) / (U64)mSamplesPerSecond;
}
///
U32 getSampleCount( U32 ms ) const
{
return U64( mSamplesPerSecond ) * U64( ms ) / U64( 1000 );
}
/// Returns the data length in bytes.
U32 getDataLength( U32 ms ) const
{
U32 bytes = ( ( (U64)ms * (U64)mSamplesPerSecond ) * (U64)getBytesPerSample() ) / (U64)1000;
return bytes;
}
};
//-----------------------------------------------------------------------------
// SFXReverb.
//-----------------------------------------------------------------------------
/// Reverb environment properties.
///
/// @note A given device may not implement all properties.
class SFXReverbProperties
{
public:
typedef void Parent;
F32 mEnvSize;
F32 mEnvDiffusion;
S32 mRoom;
S32 mRoomHF;
S32 mRoomLF;
F32 mDecayTime;
F32 mDecayHFRatio;
F32 mDecayLFRatio;
S32 mReflections;
F32 mReflectionsDelay;
F32 mReflectionsPan[ 3 ];
S32 mReverb;
F32 mReverbDelay;
F32 mReverbPan[ 3 ];
F32 mEchoTime;
F32 mEchoDepth;
F32 mModulationTime;
F32 mModulationDepth;
F32 mAirAbsorptionHF;
F32 mHFReference;
F32 mLFReference;
F32 mRoomRolloffFactor;
F32 mDiffusion;
F32 mDensity;
S32 mFlags;
SFXReverbProperties()
: mEnvSize( 7.5f ),
mEnvDiffusion( 1.0f ),
mRoom( -1000 ),
mRoomHF( -100 ),
mRoomLF( 0 ),
mDecayTime( 1.49f ),
mDecayHFRatio( 0.83f ),
mDecayLFRatio( 1.0f ),
mReflections( -2602 ),
mReflectionsDelay( 0.007f ),
mReverb( 200 ),
mReverbDelay( 0.011f ),
mEchoTime( 0.25f ),
mEchoDepth( 0.0f ),
mModulationTime( 0.25f ),
mModulationDepth( 0.0f ),
mAirAbsorptionHF( -5.0f ),
mHFReference( 5000.0f ),
mLFReference( 250.0f ),
mRoomRolloffFactor( 0.0f ),
mDiffusion( 100.0f ),
mDensity( 100.0f ),
mFlags( 0 )
{
mReflectionsPan[ 0 ] = 0.0f;
mReflectionsPan[ 1 ] = 0.0f;
mReflectionsPan[ 2 ] = 0.0f;
mReverbPan[ 0 ] = 0.0f;
mReverbPan[ 1 ] = 0.0f;
mReverbPan[ 2 ] = 0.0f;
}
void validate()
{
mEnvSize = mClampF( mEnvSize, 1.0f, 100.0f );
mEnvDiffusion = mClampF( mEnvDiffusion, 0.0f, 1.0f );
mRoom = mClamp( mRoom, -10000, 0 );
mRoomHF = mClamp( mRoomHF, -10000, 0 );
mRoomLF = mClamp( mRoomLF, -10000, 0 );
mDecayTime = mClampF( mDecayTime, 0.1f, 20.0f );
mDecayHFRatio = mClampF( mDecayHFRatio, 0.1f, 2.0f );
mDecayLFRatio = mClampF( mDecayLFRatio, 0.1f, 2.0f );
mReflections = mClamp( mReflections, -10000, 1000 );
mReflectionsDelay = mClampF( mReflectionsDelay, 0.0f, 0.3f );
mReverb = mClamp( mReverb, -10000, 2000 );
mReverbDelay = mClampF( mReverbDelay, 0.0f, 0.1f );
mEchoTime = mClampF( mEchoTime, 0.075f, 0.25f );
mEchoDepth = mClampF( mEchoDepth, 0.0f, 1.0f );
mModulationTime = mClampF( mModulationTime, 0.04f, 4.0f );
mModulationDepth = mClampF( mModulationDepth, 0.0f, 1.0f );
mAirAbsorptionHF = mClampF( mAirAbsorptionHF, -100.0f, 0.0f );
mHFReference = mClampF( mHFReference, 1000.0f, 20000.0f );
mLFReference = mClampF( mLFReference, 20.0f, 1000.0f );
mRoomRolloffFactor = mClampF( mRoomRolloffFactor, 0.0f, 10.0f );
mDiffusion = mClampF( mDiffusion, 0.0f, 100.0f );
mDensity = mClampF( mDensity, 0.0f, 100.0f );
}
};
//-----------------------------------------------------------------------------
// SFXSoundReverbProperties.
//-----------------------------------------------------------------------------
/// Sound reverb properties.
///
/// @note A given SFX device may not implement all properties.
class SFXSoundReverbProperties
{
public:
typedef void Parent;
S32 mDirect;
S32 mDirectHF;
S32 mRoom;
S32 mRoomHF;
S32 mObstruction;
F32 mObstructionLFRatio;
S32 mOcclusion;
F32 mOcclusionLFRatio;
F32 mOcclusionRoomRatio;
F32 mOcclusionDirectRatio;
S32 mExclusion;
F32 mExclusionLFRatio;
S32 mOutsideVolumeHF;
F32 mDopplerFactor;
F32 mRolloffFactor;
F32 mRoomRolloffFactor;
F32 mAirAbsorptionFactor;
S32 mFlags;
SFXSoundReverbProperties()
: mDirect( 0 ),
mDirectHF( 0 ),
mRoom( 0 ),
mRoomHF( 0 ),
mObstruction( 0 ),
mObstructionLFRatio( 0.0f ),
mOcclusion( 0 ),
mOcclusionLFRatio( 0.25f ),
mOcclusionRoomRatio( 1.5f ),
mOcclusionDirectRatio( 1.0f ),
mExclusion( 0 ),
mExclusionLFRatio( 1.0f ),
mOutsideVolumeHF( 0 ),
mDopplerFactor( 0.0f ),
mRolloffFactor( 0.0f ),
mRoomRolloffFactor( 0.0f ),
mAirAbsorptionFactor( 1.0f ),
mFlags( 0 )
{
}
void validate()
{
mDirect = mClamp( mDirect, -10000, 1000 );
mDirectHF = mClamp( mDirectHF, -10000, 0 );
mRoom = mClamp( mRoom, -10000, 1000 );
mRoomHF = mClamp( mRoomHF, -10000, 0 );
mObstruction = mClamp( mObstruction, -10000, 0 );
mObstructionLFRatio = mClampF( mObstructionLFRatio, 0.0f, 1.0f );
mOcclusion = mClamp( mOcclusion, -10000, 0 );
mOcclusionLFRatio = mClampF( mOcclusionLFRatio, 0.0f, 1.0f );
mOcclusionRoomRatio = mClampF( mOcclusionRoomRatio, 0.0f, 10.0f );
mOcclusionDirectRatio= mClampF( mOcclusionDirectRatio, 0.0f, 10.0f );
mExclusion = mClamp( mExclusion, -10000, 0 );
mExclusionLFRatio = mClampF( mExclusionLFRatio, 0.0f, 1.0f );
mOutsideVolumeHF = mClamp( mOutsideVolumeHF, -10000, 0 );
mDopplerFactor = mClampF( mDopplerFactor, 0.0f, 10.0f );
mRolloffFactor = mClampF( mRolloffFactor, 0.0f, 10.0f );
mRoomRolloffFactor = mClampF( mRoomRolloffFactor, 0.0f, 10.0f );
mAirAbsorptionFactor = mClampF( mAirAbsorptionFactor, 0.0f, 10.0f );
}
};
//-----------------------------------------------------------------------------
// SFXListenerProperties.
//-----------------------------------------------------------------------------
///
class SFXListenerProperties
{
public:
typedef void Parent;
/// Position and orientation of the listener.
MatrixF mTransform;
///
Point3F mVelocity;
SFXListenerProperties()
: mTransform( true ),
mVelocity( 0.0f, 0.0f, 0.0f ) {}
SFXListenerProperties( const MatrixF& transform, const Point3F& velocity )
: mTransform( transform ),
mVelocity( velocity ) {}
///
const MatrixF& getTransform() const { return mTransform; }
MatrixF& getTransform() { return mTransform; }
///
const Point3F& getVelocity() const { return mVelocity; }
Point3F& getVelocity() { return mVelocity; }
};
//-----------------------------------------------------------------------------
// SFXMaterialProperties.
//-----------------------------------------------------------------------------
///
class SFXMaterialProperties
{
public:
typedef void Parent;
///
bool mDoubleSided;
///
F32 mDirectOcclusion;
///
F32 mReverbOcclusion;
SFXMaterialProperties()
: mDoubleSided( false ),
mDirectOcclusion( 0.5f ),
mReverbOcclusion( 0.5f ) {}
void validate()
{
mDirectOcclusion = mClampF( mDirectOcclusion, 0.0f, 1.0f );
mReverbOcclusion = mClampF( mReverbOcclusion, 0.0f, 1.0f );
}
};
//-----------------------------------------------------------------------------
// SFXVariantFloat.
//-----------------------------------------------------------------------------
/// An array of float values with optional random variances.
template< int NUM_VALUES >
struct SFXVariantFloat
{
/// Base value.
F32 mValue[ NUM_VALUES ];
/// Variance of value. Final value will be
///
/// mClampF( randF( mValue + mVariance[ 0 ], mValue + mVariance[ 1 ] ), min, max )
///
/// with min and max being dependent on the context of the value.
F32 mVariance[ NUM_VALUES ][ 2 ];
F32 getValue( U32 index = 0, F32 min = TypeTraits< F32 >::MIN, F32 max = TypeTraits< F32 >::MAX ) const
{
AssertFatal( index < NUM_VALUES, "SFXVariantFloat::getValue() - index out of range!" );
return mClampF( gRandGen.randF( mValue[ index ] + mVariance[ index ][ 0 ],
mValue[ index ] + mVariance[ index ][ 1 ] ),
min, max );
}
void validate()
{
for( U32 i = 0; i < NUM_VALUES; ++ i )
mVariance[ i ][ 0 ] = getMin( mVariance[ i ][ 0 ], mVariance[ i ][ 1 ] );
}
};
#endif // _SFXCOMMON_H_

View file

@ -0,0 +1,943 @@
//-----------------------------------------------------------------------------
// 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/sfxController.h"
#include "sfx/sfxPlayList.h"
#include "sfx/sfxProfile.h"
#include "sfx/sfxSource.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxState.h"
#include "sfx/sfxDescription.h"
#include "console/engineAPI.h"
#include "math/mRandom.h"
IMPLEMENT_CONOBJECT( SFXController );
ConsoleDocClass( SFXController,
"@brief A sound source that drives multi-source playback.\n\n"
"This class acts as an interpreter for SFXPlayLists. It goes through the slots of the playlist it is "
"attached to and performs the actions described by each of the slots in turn.\n"
"As SFXControllers are created implicitly by the SFX system when instantiating a source for a play list it is "
"in most cases not necessary to directly deal with the class.\n"
"The following example demonstrates how a controller would commonly be created.\n"
"@tsexample\n"
"// Create a play list from two SFXProfiles.\n"
"%playList = new SFXPlayList()\n"
"{\n"
" // Use a looped description so the list playback will loop.\n"
" description = AudioMusicLoop2D;\n"
"\n"
" track[ 0 ] = Profile1;\n"
" track[ 1 ] = Profile2;\n"
"};\n"
"\n"
"// Play the list. This will implicitly create a controller.\n"
"sfxPlayOnce( %playList );\n"
"@endtsexample\n\n"
"@note Play lists are updated at regular intervals by the sound system. This processing determines the granularity at "
"which playlist action timing takes place.\n"
"@note This class cannot be instantiated directly. Use sfxPlayOnce() or sfxCreateSource() with the playlist "
"you want to play to create an instance of this class.\n"
"@see SFXPlayList\n"
"@ingroup SFX\n"
);
//-----------------------------------------------------------------------------
SFXController::SFXController( SFXPlayList* playList )
: Parent( playList ),
mTrace( playList->trace() )
{
VECTOR_SET_ASSOCIATION( mInsns );
VECTOR_SET_ASSOCIATION( mSources );
VECTOR_SET_ASSOCIATION( mParameters );
_compileList( playList );
}
//-----------------------------------------------------------------------------
SFXController::~SFXController()
{
}
//-----------------------------------------------------------------------------
void SFXController::initPersistFields()
{
addGroup( "Debug" );
addField( "trace", TypeBool, Offset( mTrace, SFXController ),
"If true, the controller logs its operation to the console.\n"
"This is a non-networked field that will work locally only." );
endGroup( "Debug" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
SFXController* SFXController::_create( SFXPlayList* playList )
{
AssertFatal( playList != NULL, "SFXController::_create() - got a NULL playlist!" );
SFXController* controller = new SFXController( playList );
controller->registerObject();
return controller;
}
//-----------------------------------------------------------------------------
void SFXController::_compileList( SFXPlayList* playList )
{
mInsns.clear();
const bool isLooping = playList->getDescription()->mIsLooping;
// Create a slot list that determines the order the slots will be
// played in.
U32 slotList[ SFXPlayList::NUM_SLOTS ];
bool isOrderedRandom = false;
switch( playList->getRandomMode() )
{
case SFXPlayList::RANDOM_OrderedRandom:
isOrderedRandom = true;
/* fallthrough */
case SFXPlayList::RANDOM_NotRandom:
// Generate sequence 1-NUM_SLOTS.
for( U32 i = 0; i < SFXPlayList::NUM_SLOTS; ++ i )
slotList[ i ] = i;
if( isOrderedRandom )
{
// Randomly exchange slots in the list.
for( U32 i = 0; i < SFXPlayList::NUM_SLOTS; ++ i )
swap( slotList[ gRandGen.randI( 0, SFXPlayList::NUM_SLOTS - 1 ) ], slotList[ i ] );
}
break;
case SFXPlayList::RANDOM_StrictRandom:
// Randomly generate NUM_SLOTS slot indices.
for( U32 i = 0; i < SFXPlayList::NUM_SLOTS; ++ i )
slotList[ i ] = gRandGen.randI( 0, SFXPlayList::NUM_SLOTS - 1 );
break;
}
// Generate the instruction list.
U32 slotCount = 0;
for( U32 i = 0; i < SFXPlayList::NUM_SLOTS; ++ i )
{
const U32 slotIndex = slotList[ i ];
const U32 slotStartIp = mInsns.size();
SFXState* state = playList->getSlots().mState[ slotIndex ];
// If there's no track in this slot, ignore it.
if( !playList->getSlots().mTrack[ slotIndex ] )
continue;
// If this is a looped slot and the list is not set to loop
// indefinitly on single slots, start a loop.
S32 loopStartIp = -1;
if( playList->getSlots().mRepeatCount[ slotIndex ] > 0
&& ( !isLooping || playList->getLoopMode() != SFXPlayList::LOOP_Single ) )
{
Insn insn( OP_LoopBegin, slotIndex, state );
insn.mArg.mLoopCount = playList->getSlots().mRepeatCount[ slotIndex ];
mInsns.push_back( insn );
loopStartIp = mInsns.size();
}
// Add in-delay, if any.
if( playList->getSlots().mDelayTimeIn.mValue[ slotIndex ] > 0.0f )
{
Insn insn( OP_Delay, slotIndex, state );
insn.mArg.mDelayTime.mValue[ 0 ] = playList->getSlots().mDelayTimeIn.mValue[ slotIndex ];
insn.mArg.mDelayTime.mVariance[ 0 ][ 0 ] = playList->getSlots().mDelayTimeIn.mVariance[ slotIndex ][ 0 ];
insn.mArg.mDelayTime.mVariance[ 0 ][ 1 ] = playList->getSlots().mDelayTimeIn.mVariance[ slotIndex ][ 1 ];
mInsns.push_back( insn );
}
// Add the in-transition.
const SFXPlayList::ETransitionMode transitionIn = playList->getSlots().mTransitionIn[ slotIndex ];
if( transitionIn != SFXPlayList::TRANSITION_None )
{
Insn insn( slotIndex, state );
_genTransition( insn, transitionIn );
mInsns.push_back( insn );
}
// Add the play instruction.
{
Insn insn( OP_Play, slotIndex, state );
mInsns.push_back( insn );
}
// Add out-delay, if any.
if( playList->getSlots().mDelayTimeOut.mValue[ slotIndex ] > 0.0f )
{
Insn insn( OP_Delay, slotIndex, state );
insn.mArg.mDelayTime.mValue[ 0 ] = playList->getSlots().mDelayTimeOut.mValue[ slotIndex ];
insn.mArg.mDelayTime.mVariance[ 0 ][ 0 ] = playList->getSlots().mDelayTimeOut.mVariance[ slotIndex ][ 0 ];
insn.mArg.mDelayTime.mVariance[ 0 ][ 1 ] = playList->getSlots().mDelayTimeOut.mVariance[ slotIndex ][ 1 ];
mInsns.push_back( insn );
}
// Add the out-transition.
const SFXPlayList::ETransitionMode transitionOut = playList->getSlots().mTransitionOut[ slotIndex ];
if( transitionOut != SFXPlayList::TRANSITION_None )
{
Insn insn( slotIndex, state );
_genTransition( insn, transitionOut );
mInsns.push_back( insn );
}
// Loop, if necessary.
if( loopStartIp != -1 )
{
Insn insn( OP_LoopEnd, slotIndex, state );
insn.mArg.mJumpIp = loopStartIp;
mInsns.push_back( insn );
}
// If the list is on repeat-single, unconditionally
// loop over the instruction sequence of each slot.
if( isLooping && playList->getLoopMode() == SFXPlayList::LOOP_Single )
{
Insn insn( OP_Jump, slotIndex, state );
insn.mArg.mJumpIp = slotStartIp;
mInsns.push_back( insn );
}
// If we have reached the limit of slots to play,
// stop generating.
slotCount ++;
if( playList->getNumSlotsToPlay() == slotCount )
break;
}
// Set up for execution.
mIp = 0;
if( !mInsns.empty() )
_initInsn();
}
//-----------------------------------------------------------------------------
void SFXController::_genTransition( Insn& insn, SFXPlayList::ETransitionMode transition )
{
switch( transition )
{
case SFXPlayList::TRANSITION_Wait:
insn.mOpcode = OP_WaitSingle;
break;
case SFXPlayList::TRANSITION_WaitAll:
insn.mOpcode = OP_WaitAll;
break;
case SFXPlayList::TRANSITION_Stop:
insn.mOpcode = OP_StopSingle;
break;
case SFXPlayList::TRANSITION_StopAll:
insn.mOpcode = OP_StopAll;
break;
default:
AssertFatal( false, "SFXController::_addTransition() - should not reach here" );
}
}
//-----------------------------------------------------------------------------
void SFXController::_initInsn()
{
Insn& insn = mInsns[ mIp ];
switch( insn.mOpcode )
{
case OP_Delay:
mDelayEndTime = Platform::getVirtualMilliseconds()
+ U32( insn.mArg.mDelayTime.getValue( 0, 0.0f ) * 1000.f );
break;
default:
break;
}
if( mTrace )
_printInsn(insn );
}
//-----------------------------------------------------------------------------
void SFXController::_printInsn( Insn& insn)
{
switch( insn.mOpcode )
{
case OP_Delay:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: Delay %f:%f:%f",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "",
insn.mArg.mDelayTime.mValue[ 0 ],
insn.mArg.mDelayTime.mVariance[ 0 ],
insn.mArg.mDelayTime.mVariance[ 1 ]
);
break;
case OP_WaitSingle:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: WaitSingle",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "" );
break;
case OP_WaitAll:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: WaitAll",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "" );
break;
case OP_StopSingle:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: StopSingle",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "" );
break;
case OP_StopAll:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: StopAll",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "" );
break;
case OP_Play:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: Play",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "" );
break;
case OP_Jump:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: Jump %i",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "", insn.mArg.mJumpIp );
break;
case OP_LoopBegin:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: LoopBegin %i",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "", insn.mArg.mLoopCount );
break;
case OP_LoopEnd:
Con::printf( "[SFXController] ip=%d: slot=%d: state=%s: LoopEnd",
mIp, insn.mSlotIndex, insn.mState ? insn.mState->getName() : "" );
break;
}
}
//-----------------------------------------------------------------------------
bool SFXController::_execInsn()
{
bool endUpdate = false;
Insn& insn = mInsns[ mIp ];
switch( insn.mOpcode )
{
case OP_Delay:
{
if( Platform::getVirtualMilliseconds() < mDelayEndTime )
endUpdate = true;
else
_advanceIp();
break;
}
case OP_Play:
{
SFXPlayList* playList = getPlayList();
SFXTrack* track = playList->getSlots().mTrack[ insn.mSlotIndex ];
// Handle existing sources playing on this slot and find
// whether we need to start a new source.
//
// Go through the list top-down so we can push sources we re-use
// to the top of the list. A side-effect of doing it this way is
// that the order of the sources that are preserved gets reversed,
// i.e. older sources will end up higher up the stack.
bool startNew = true;
SFXPlayList::EReplayMode replayMode = playList->getSlots().mReplayMode[ insn.mSlotIndex ];
if( replayMode != SFXPlayList::REPLAY_IgnorePlaying )
for( S32 i = mSources.size() - 1; i >= 0; -- i )
{
Source& source = mSources[ i ];
if( source.mSlotIndex != insn.mSlotIndex )
continue;
// If the play-once source has expired, remove the entry
// and go on.
if( source.mPtr == NULL )
{
mSources.erase( i );
++ i;
continue;
}
// Decide what to do with the still-playing source.
if( replayMode == SFXPlayList::REPLAY_RestartPlaying
|| replayMode == SFXPlayList::REPLAY_KeepPlaying )
{
// Restart the source or keep playing it.
// Either way, move it to the top of the stack.
startNew = false;
Source src = mSources[ i ];
mSources.erase( i );
//RDTODO: add a method to restart cleanly in the presence of fades; this here
// just cuts the current playback short
if( replayMode == SFXPlayList::REPLAY_RestartPlaying )
src.mPtr->stop( 0.f );
src.mPtr->play();
// Move the source to the top of the stack.
mSources.increment();
mSources.last() = src;
}
else if( replayMode == SFXPlayList::REPLAY_StartNew )
{
// Kill off existing source.
source.mPtr->stop();
mSources.erase( i );
++ i;
}
else if( replayMode == SFXPlayList::REPLAY_SkipIfPlaying )
{
startNew = false;
break;
}
}
if( startNew )
{
// Create a new source.
SFXSource* source = SFX->createSource(
track,
&getTransform(),
&getVelocity()
);
// Append the source to the list of playing sources.
if( source )
{
mSources.increment();
Source& src = mSources.last();
// Determine fade times.
F32 fadeInTime = -1;
F32 fadeOutTime = -1;
if( playList->getSlots().mFadeTimeIn.mValue[ insn.mSlotIndex ] != -1 )
fadeInTime = playList->getSlots().mFadeTimeIn.getValue( insn.mSlotIndex, 0.f );
if( playList->getSlots().mFadeTimeOut.mValue[ insn.mSlotIndex ] != -1 )
fadeOutTime = playList->getSlots().mFadeTimeOut.getValue( insn.mSlotIndex, 0.f );
if( fadeInTime != -1 || fadeOutTime != -1 )
source->setFadeTimes( fadeInTime, fadeOutTime );
// Set up source record.
src.mPtr = source;
src.mSlotIndex = insn.mSlotIndex;
src.mVolumeScale = playList->getSlots().mVolumeScale.getValue( insn.mSlotIndex, 0.f, 1.f );
src.mPitchScale = playList->getSlots().mPitchScale.getValue( insn.mSlotIndex );
src.mFadeInTime = fadeInTime;
src.mFadeOutTime = fadeOutTime;
SFXPlayList::EStateMode stateMode = playList->getSlots().mStateMode[ insn.mSlotIndex ];
if( stateMode != SFXPlayList::STATE_IgnoreInactive )
src.mState = insn.mState;
// Set the source's volume and pitch. Either is scaled by our own
// assigned value and the scale factors from the playlist slot.
source->setModulativeVolume( mAttenuatedVolume * src.mVolumeScale );
source->setModulativePitch( mEffectivePitch * src.mPitchScale );
// Set min and max range.
const SFXPlayList::VariantFloat& minDistance = playList->getSlots().mMinDistance;
const SFXPlayList::VariantFloat& maxDistance = playList->getSlots().mMaxDistance;
if( minDistance.mValue[ insn.mSlotIndex ] >= 0.f
&& maxDistance.mValue[ insn.mSlotIndex ] >= 0.f )
source->setMinMaxDistance(
minDistance.getValue( insn.mSlotIndex, 0.f ),
maxDistance.getValue( insn.mSlotIndex, 0.f )
);
// Start the source.
source->play();
SFX->deleteWhenStopped( source );
}
}
_advanceIp();
break;
}
case OP_WaitSingle:
{
if( !mSources.empty() && mSources.last().mPtr != NULL && mSources.last().mPtr->isPlaying() )
endUpdate = true;
else
{
if( !mSources.empty() )
mSources.decrement();
_advanceIp();
}
break;
}
case OP_WaitAll:
{
for( U32 i = 0; i < mSources.size(); ++ i )
if( mSources[ i ].mPtr != NULL && mSources[ i ].mPtr->isStopped() )
{
mSources.erase( i );
-- i;
}
if( !mSources.empty() )
endUpdate = true;
else
_advanceIp();
break;
}
case OP_StopSingle:
{
if( !mSources.empty() )
{
if( mSources.last().mPtr != NULL )
mSources.last().mPtr->stop();
mSources.decrement();
}
_advanceIp();
break;
}
case OP_StopAll:
{
while( !mSources.empty() )
{
if( mSources.last().mPtr != NULL )
mSources.last().mPtr->stop();
mSources.decrement();
}
_advanceIp();
break;
}
case OP_Jump:
{
mIp = insn.mArg.mJumpIp;
_initInsn();
break;
}
case OP_LoopBegin:
{
mLoopCounter = insn.mArg.mLoopCount;
_advanceIp();
break;
}
case OP_LoopEnd:
{
-- mLoopCounter;
if( mLoopCounter > 0 )
{
mIp = insn.mArg.mJumpIp;
_initInsn();
}
else
_advanceIp();
break;
}
}
return endUpdate;
}
//-----------------------------------------------------------------------------
void SFXController::_advanceIp()
{
mIp ++;
if( mIp < mInsns.size() )
_initInsn();
}
//-----------------------------------------------------------------------------
void SFXController::_onParameterEvent( SFXParameter* parameter, SFXParameterEvent event )
{
Parent::_onParameterEvent( parameter, event );
// Implement cursor semantic.
if( event == SFXParameterEvent_ValueChanged
&& parameter->getChannel() == SFXChannelCursor )
{
U32 slot = U32( mFloor( parameter->getValue() ) );
if( slot != getCurrentSlot() )
setCurrentSlot( slot );
}
}
//-----------------------------------------------------------------------------
SFXPlayList* SFXController::getPlayList() const
{
return static_cast< SFXPlayList* >( mTrack.getPointer() );
}
//-----------------------------------------------------------------------------
U32 SFXController::getCurrentSlot() const
{
if( mIp >= mInsns.size() )
return 0;
else
return mInsns[ mIp ].mSlotIndex;
}
//-----------------------------------------------------------------------------
void SFXController::setCurrentSlot( U32 index )
{
mIp = 0;
while( mIp < mInsns.size() && mInsns[ mIp ].mSlotIndex != index )
++ mIp;
if( mIp >= mInsns.size() )
mIp = 0;
if( !mInsns.empty() )
_initInsn();
}
//-----------------------------------------------------------------------------
void SFXController::_play()
{
Parent::_play();
// Unpause sources, if we are paused.
if( mStatus == SFXStatusPaused )
for( U32 i = 0; i < mSources.size(); ++ i )
if( mSources[ i ].mPtr != NULL )
mSources[ i ].mPtr->play( 0.f ); // We want our fade values to take effect.
else
{
mSources.erase( i );
-- i;
}
}
//-----------------------------------------------------------------------------
void SFXController::_pause()
{
Parent::_pause();
// Pause all playing sources.
for( U32 i = 0; i < mSources.size(); ++ i )
if( mSources[ i ].mPtr != NULL )
mSources[ i ].mPtr->pause( 0.f ); // We want our fade values to take effect.
else
{
mSources.erase( i );
-- i;
}
}
//-----------------------------------------------------------------------------
void SFXController::_stop()
{
Parent::_stop();
// Stop all playing sources.
while( !mSources.empty() )
{
if( mSources.last().mPtr != NULL )
mSources.last().mPtr->stop( 0.f ); // We want our fade values to take effect.
mSources.decrement();
}
// Reset execution.
mIp = 0;
if( !mInsns.empty() )
_initInsn();
}
//-----------------------------------------------------------------------------
void SFXController::_updateVolume( const MatrixF& listener )
{
F32 oldAttenuatedVolume = mAttenuatedVolume;
Parent::_updateVolume( listener );
// If the attenuated volume has changed, pass it off
// as the modulative volume to all our sources.
if( oldAttenuatedVolume != mAttenuatedVolume )
for( U32 i = 0; i < mSources.size(); ++ i )
{
Source& source = mSources[ i ];
if( source.mPtr != NULL )
source.mPtr->setModulativeVolume( mAttenuatedVolume * source.mVolumeScale );
else
{
mSources.erase( i );
-- i;
}
}
}
//-----------------------------------------------------------------------------
void SFXController::_updatePitch()
{
F32 oldEffectivePitch = mEffectivePitch;
Parent::_updatePitch();
if( mEffectivePitch != oldEffectivePitch )
for( U32 i = 0; i < mSources.size(); ++ i )
{
Source& source = mSources[ i ];
if( source.mPtr != NULL )
source.mPtr->setModulativePitch( mEffectivePitch * source.mPitchScale );
else
{
mSources.erase( i );
-- i;
}
}
}
//-----------------------------------------------------------------------------
void SFXController::_updatePriority()
{
F32 oldEffectivePriority = mEffectivePriority;
Parent::_updatePriority();
if( mEffectivePriority != oldEffectivePriority )
for( U32 i = 0; i < mSources.size(); ++ i )
{
Source& source = mSources[ i ];
if( source.mPtr != NULL )
source.mPtr->setModulativePriority( mEffectivePriority );
else
{
mSources.erase( i );
-- i;
}
}
}
//-----------------------------------------------------------------------------
void SFXController::_update()
{
Parent::_update();
SFXPlayList* playList = getPlayList();
// Check all sources against the current state setup and
// take appropriate actions.
for( U32 i = 0; i < mSources.size(); ++ i )
{
Source& source = mSources[ i ];
// If the source has already stopped playing,
// remove it.
if( !source.mPtr )
{
mSources.erase( i );
-- i;
continue;
}
if( !source.mState )
continue;
SFXPlayList::EStateMode stateMode = playList->getSlots().mStateMode[ mSources[ i ].mSlotIndex ];
if( !source.mState->isActive() )
{
if( source.mPtr->isPlaying() )
{
// The source is playing in an incompatible state.
if( stateMode == SFXPlayList::STATE_PauseInactive )
source.mPtr->pause();
else if( stateMode == SFXPlayList::STATE_StopInactive )
{
source.mPtr->stop();
mSources.erase( i );
-- i;
}
}
}
else
{
// Unpause a source that had its state become active again.
if( source.mPtr->isPaused() && stateMode == SFXPlayList::STATE_PauseInactive )
source.mPtr->play();
}
}
// Update interpreter.
bool endUpdate = false;
while( !endUpdate )
{
if( mIp >= mInsns.size() )
{
// End of list reached.
if( playList->getDescription()->mIsLooping &&
playList->getLoopMode() == SFXPlayList::LOOP_All )
{
// The play list is set to repeat-all.
// If it is also random, generate a new instruction list
// so we get a new playing order. Otherwise just reset.
if( playList->getRandomMode() != SFXPlayList::RANDOM_NotRandom )
_compileList( playList );
else
{
mIp = 0;
if( !mInsns.empty() )
_initInsn();
}
// Reset play timer.
mPlayTimer.reset();
mPlayTimer.start();
}
else
{
// Moved to stopped state.
mPlayTimer.stop();
_setStatus( SFXStatusStopped );
mIp = 0;
}
// End this update. This limits playlist to at most one complete
// cycle per update.
break;
}
Insn& insn = mInsns[ mIp ];
if( insn.mState && !insn.mState->isActive() )
{
// The state associated with the slot is inactive. Skip
// the instructions.
_advanceIp();
}
else
endUpdate = _execInsn();
}
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXController, getCurrentSlot, S32, (),,
"Get the index of the playlist slot currently processed by the controller.\n"
"@return The slot index currently being played.\n"
"@see SFXPlayList" )
{
return object->getCurrentSlot();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXController, setCurrentSlot, void, ( S32 index ),,
"Set the index of the playlist slot to play by the controller. This can be used to seek in the playlist.\n"
"@param index Index of the playlist slot." )
{
object->setCurrentSlot( index );
}

View file

@ -0,0 +1,220 @@
//-----------------------------------------------------------------------------
// 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 _SFXCONTROLLER_H_
#define _SFXCONTROLLER_H_
#ifndef _SFXSOURCE_H_
#include "sfx/sfxSource.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _SFXSOURCE_H_
#include "sfx/sfxSource.h"
#endif
#ifndef _SFXPLAYLIST_H_
#include "sfx/sfxPlayList.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class SFXTrack;
class SFXProfile;
class SFXState;
/// SFXSource that drives multi-source playback.
///
/// Basically, this class is an interpreter for the instruction slots in
/// SFXPlayLists.
///
/// Controllers can be switched between states. When no state is set, all
/// tracks from playlists that do not have a state set will be played. When
/// setting a state, only tracks with the given state will be played. If
/// currently tracks with a different state are playing, the respective
/// controllers will transition out of their respective slots.
///
class SFXController : public SFXSource
{
public:
typedef SFXSource Parent;
friend class SFXSystem; // _create
protected:
typedef SFXVariantFloat< 1 > VariantFloat;
enum EOp
{
OP_Delay,
OP_WaitSingle,
OP_WaitAll,
OP_StopSingle,
OP_StopAll,
OP_Play,
OP_Jump,
OP_LoopBegin,
OP_LoopEnd,
};
struct Insn
{
EOp mOpcode;
U32 mSlotIndex;
SFXState* mState;
union
{
VariantFloat mDelayTime;
U32 mJumpIp;
U32 mLoopCount;
} mArg;
Insn() {}
Insn( EOp opcode )
: mOpcode( opcode ), mSlotIndex( U32_MAX ), mState( NULL ) {}
Insn( U32 slotIndex, SFXState* state )
: mSlotIndex( slotIndex ), mState( state ) {}
Insn( EOp opcode, U32 slotIndex, SFXState* state )
: mOpcode( opcode ), mSlotIndex( slotIndex ), mState( state ) {}
};
///
struct Source
{
/// The play-once source.
SimObjectPtr< SFXSource > mPtr;
/// The state to which the source is tied. Only taken over from
/// the instruction if the state mode is not set to ignored.
SFXState* mState;
/// Index of slot in playlist that this source was spawned on.
U32 mSlotIndex;
/// Volume scale factor to apply to the source. Saved as it may have been
/// randomly generated.
F32 mVolumeScale;
/// Pitch scale factor to apply to the source. Saved as it may have been
/// randomly generated.
F32 mPitchScale;
///
F32 mFadeInTime;
///
F32 mFadeOutTime;
Source()
: mState( 0 ) {}
};
/// The current instruction in "mInsns".
U32 mIp;
/// The instruction list. This is compiled from the playlist and then executed
/// in the controller's update.
Vector< Insn > mInsns;
/// The stack of currently playing sources.
///
/// All sources on this list are play-once sources so we can leave their lifetime
/// management to the SFX system. This is especially convenient in combination
/// with fade-outs where a source cannot be immediately deleted.
Vector< Source > mSources;
///
bool mTrace;
///
U32 mDelayEndTime;
///
U32 mLoopCounter;
///
SFXController( SFXPlayList* playList );
///
void _printInsn( Insn& insn );
///
void _compileList( SFXPlayList* playList );
///
void _genTransition( Insn& insn, SFXPlayList::ETransitionMode transition );
///
void _dumpInsns();
///
void _initInsn();
///
bool _execInsn();
///
void _advanceIp();
///
static SFXController* _create( SFXPlayList* playList );
// SFXSource.
virtual void _play();
virtual void _pause();
virtual void _stop();
virtual void _onParameterEvent( SFXParameter* parameter, SFXParameterEvent event );
virtual void _updateVolume( const MatrixF& listener );
virtual void _updatePitch();
virtual void _updatePriority();
virtual void _update();
public:
~SFXController();
/// Constructor for the sake of ConsoleObject.
explicit SFXController() {}
/// Return the playlist being played back by the controller.
SFXPlayList* getPlayList() const;
/// Return the index of the playlist slot being processed by the controller.
U32 getCurrentSlot() const;
/// Set the index of the playlist slot to process.
void setCurrentSlot( U32 index );
// SFXSource.
static void initPersistFields();
DECLARE_CONOBJECT( SFXController );
DECLARE_DESCRIPTION( "Controls the playback of an SFXPlayList." );
};
#endif // !_SFXCONTROLLER_H_

View file

@ -0,0 +1,654 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxInternal.h"
#include "sfx/sfxSource.h"
#include "sfx/sfxTypes.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "core/stringTable.h"
#include "core/module.h"
#include "math/mathIO.h"
#include "math/mathTypes.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXDescription );
ConsoleDocClass( SFXDescription,
"@brief A description for how a sound should be played.\n\n"
"SFXDescriptions are used by the sound system to collect all parameters needed to set up a given "
"sound for playback. This includes information like its volume level, its pitch shift, etc. as well "
"as more complex information like its fade behavior, 3D properties, and per-sound reverb properties.\n\n"
"Any sound playback will require a valid SFXDescription.\n\n"
"As datablocks, SFXDescriptions can be set up as either networked datablocks or non-networked datablocks, "
"though it generally makes sense to keep all descriptions non-networked since they will be used exclusively "
"by clients.\n\n"
"@tsexample\n"
"// A description for a 3D sound with a reasonable default range setting.\n"
"// The description is set up to assign sounds to the AudioChannelEffects source group\n"
"// (defined in the core scripts). An alternative means to achieve this is to use the\n"
"// AudioEffects description as a copy source (\": AudioEffects\").\n"
"\n"
"singleton SFXDescription( Audio3DSound )\n"
"{\n"
" sourceGroup = AudioChannelEffects;\n"
" is3D = true;\n"
" referenceDistance = 20.0;\n"
" maxDistance = 100.0;\n"
"};\n"
"@endtsexample\n\n"
"@ingroup SFX\n"
"@ingroup Datablocks\n"
);
// Constants.
static const U32 sReverbSoundFlagDirectHFAuto = 0x01;
static const U32 sReverbSoundFlagRoomAuto = 0x02;
static const U32 sReverbSoundFlagRoomHFAuto = 0x04;
static const U32 sReverbSoundFlagInstance0 = 0x10;
static const U32 sReverbSoundFlagInstance1 = 0x20;
static const U32 sReverbSoundFlagInstance2 = 0x40;
static const U32 sReverbSoundFlagInstance3 = 0x80;
AFTER_MODULE_INIT( SFX )
{
Con::addConstant( "SFXDescription::REVERB_DIRECTHFAUTO", TypeS32, &sReverbSoundFlagDirectHFAuto,
"Automatic setting of SFXDescription::reverbDirect due to distance to listener.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
Con::addConstant( "SFXDescription::REVERB_ROOMAUTO", TypeS32, &sReverbSoundFlagRoomAuto,
"Automatic setting of SFXDescription::reverbRoom due to distance to listener.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
Con::addConstant( "SFXDescription::REVERB_ROOMHFAUTO", TypeS32, &sReverbSoundFlagRoomHFAuto,
"Automatic setting of SFXDescription::reverbRoomHF due to distance to listener.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
Con::addConstant( "SFXDescription::REVERB_INSTANCE0", TypeS32, &sReverbSoundFlagInstance0,
"EAX4/SFX/GameCube/Wii: Specify channel to target reverb instance 0. Default target.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
Con::addConstant( "SFXDescription::REVERB_INSTANCE1", TypeS32, &sReverbSoundFlagInstance1,
"EAX4/SFX/GameCube/Wii: Specify channel to target reverb instance 1.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
Con::addConstant( "SFXDescription::REVERB_INSTANCE2", TypeS32, &sReverbSoundFlagInstance2,
"EAX4/SFX/GameCube/Wii: Specify channel to target reverb instance 2.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
Con::addConstant( "SFXDescription::REVERB_INSTANCE3", TypeS32, &sReverbSoundFlagInstance3,
"EAX4/SFX/GameCube/Wii: Specify channel to target reverb instance 3.\n"
"@see SFXDescription::flags\n\n"
"@ingroup SFXDescription" );
}
//-----------------------------------------------------------------------------
SFXDescription::SFXDescription()
: SimDataBlock(),
mVolume( 1 ),
mPitch( 1 ),
mIsLooping( false ),
mIsStreaming( false ),
mIs3D( false ),
mUseHardware( false ),
mUseReverb( false ),
mMinDistance( 1 ),
mMaxDistance( 100 ),
mConeInsideAngle( 360 ),
mConeOutsideAngle( 360 ),
mConeOutsideVolume( 1 ),
mRolloffFactor( -1.f ), // Deactivated
mFadeInTime( 0.0f ),
mFadeOutTime( 0.0f ),
mFadeLoops( false ),
mStreamPacketSize( SFXInternal::SFXAsyncStream::DEFAULT_STREAM_PACKET_LENGTH ),
mStreamReadAhead( SFXInternal::SFXAsyncStream::DEFAULT_STREAM_LOOKAHEAD ),
mPriority( 1.0f ),
mScatterDistance( 0.f, 0.f, 0.f ),
mSourceGroup( NULL )
{
dMemset( mParameters, 0, sizeof( mParameters ) );
}
//-----------------------------------------------------------------------------
SFXDescription::SFXDescription( const SFXDescription& desc )
: SimDataBlock(),
mVolume( desc.mVolume ),
mPitch( desc.mPitch ),
mIsLooping( desc.mIsLooping ),
mIsStreaming( desc.mIsStreaming ),
mIs3D( desc.mIs3D ),
mUseHardware( desc.mUseHardware ),
mMinDistance( desc.mMinDistance ),
mMaxDistance( desc.mMaxDistance ),
mConeInsideAngle( desc.mConeInsideAngle ),
mConeOutsideAngle( desc.mConeOutsideAngle ),
mConeOutsideVolume( desc.mConeOutsideVolume ),
mRolloffFactor( desc.mRolloffFactor ),
mSourceGroup( desc.mSourceGroup ),
mFadeInTime( desc.mFadeInTime ),
mFadeOutTime( desc.mFadeOutTime ),
mFadeInEase( desc.mFadeInEase ),
mFadeOutEase( desc.mFadeOutEase ),
mFadeLoops( desc.mFadeLoops ),
mStreamPacketSize( desc.mStreamPacketSize ),
mStreamReadAhead( desc.mStreamReadAhead ),
mUseReverb( desc.mUseReverb ),
mReverb( desc.mReverb ),
mPriority( desc.mPriority ),
mScatterDistance( desc.mScatterDistance )
{
for( U32 i = 0; i < MaxNumParameters; ++ i )
mParameters[ i ] = desc.mParameters[ i ];
}
//-----------------------------------------------------------------------------
void SFXDescription::initPersistFields()
{
addGroup( "Playback" );
addField( "sourceGroup", TypeSFXSourceName, Offset( mSourceGroup, SFXDescription ),
"Group that sources playing with this description should be put into.\n\n"
"When a sound source is allocated, it will be made a child of the source group that is listed in its \n"
"description. This group will then modulate several properties of the sound as it is played.\n\n"
"For example, one use of groups is to segregate sounds so that volume levels of different sound "
"groups such as interface audio and game audio can be controlled independently.\n\n"
"@ref SFXSource_hierarchies" );
addField( "volume", TypeF32, Offset( mVolume, SFXDescription ),
"Base volume level for the sound.\n\n"
"This will be the starting point for volume attenuation on the sound. The final effective volume of "
"a sound will be dependent on a number of parameters.\n\n"
"Must be between 0 (mute) and 1 (full volume). Default is 1.\n\n"
"@ref SFXSource_volume" );
addField( "pitch", TypeF32, Offset( mPitch, SFXDescription ),
"Pitch shift to apply to playback.\n\n"
"The pitch assigned to a sound determines the speed at which it is played back. A pitch shift of 1 plays the "
"sound at its default speed. A greater shift factor speeds up playback and a smaller shift factor slows it down.\n\n"
"Must be >0. Default is 1." );
addField( "isLooping", TypeBool, Offset( mIsLooping, SFXDescription ),
"If true, the sound will be played in an endless loop.\n\n"
"Default is false." );
addField( "priority", TypeF32, Offset( mPriority, SFXDescription ),
"Priority level for virtualization of sounds (1 = base level).\n"
"When there are more concurrently active sounds than supported by the audio mixer, some of the sounds "
"need to be culled. Which sounds are culled first depends primarily on total audibility of individual sounds. "
"However, the priority of invidual sounds may be decreased or decreased through this field.\n\n"
"@ref SFXSound_virtualization" );
addField( "useHardware", TypeBool, Offset( mUseHardware, SFXDescription ),
"Whether the sound is allowed to be mixed in hardware.\n"
"If true, the sound system will try to allocate the voice for the sound directly "
"on the sound hardware for mixing by the hardware mixer. Be aware that a hardware mixer "
"may not provide all features available to sounds mixed in software.\n\n"
"@note This flag currently only takes effect when using FMOD.\n\n"
"@note Generally, it is preferable to let sounds be mixed in software.\n\n" );
addField( "parameters", TypeSFXParameterName, Offset( mParameters, SFXDescription ), MaxNumParameters,
"Names of the parameters to which sources using this description will automatically be linked.\n\n"
"Individual parameters are identified by their #internalName.\n\n"
"@ref SFX_interactive" );
endGroup( "Playback" );
addGroup( "Fading" );
addField( "fadeInTime", TypeF32, Offset( mFadeInTime, SFXDescription ),
"Number of seconds to gradually fade in volume from zero when playback starts.\n"
"Must be >= 0.\n\n"
"@ref SFXSource_fades" );
addField( "fadeOutTime", TypeF32, Offset( mFadeOutTime, SFXDescription ),
"Number of seconds to gradually fade out volume down to zero when playback is stopped or paused.\n"
"Must be >=0.\n\n"
"@ref SFXSource_fades" );
addField( "fadeInEase", TypeEaseF, Offset( mFadeInEase, SFXDescription ),
"Easing curve for fade-in transition.\n"
"Volume fade-ins will interpolate volume along this curve.\n\n"
"@ref SFXSource_fades" );
addField( "fadeOutEase", TypeEaseF, Offset( mFadeOutEase, SFXDescription ),
"Easing curve for fade-out transition.\n"
"Volume fade-outs will interpolate volume along this curve.\n\n"
"@ref SFXSource_fades" );
addField( "fadeLoops", TypeBool, Offset( mFadeLoops, SFXDescription ),
"Fade each cycle of a loop in and/or out; otherwise only fade-in first cycle.\n"
"By default, volume fading is applied to the beginning and end of the playback range, i.e. a fade-in segment "
"is placed at the beginning of the sound and a fade-out segment is paced at the end of a sound. However, "
"when looping playback, this may be undesirable as each iteration of the sound will then have a fade-in and "
"fade-out effect.\n\n"
"To set up looping sounds such that a fade-in is applied only when the sound is first started (or playback resumed) "
"and a fade-out is only applied when the sound is explicitly paused or stopped, set this field to true.\n\n"
"Default is false.\n\n"
"@ref SFXSource_fades" );
endGroup( "Fading" );
addGroup( "3D" );
addField( "is3D", TypeBool, Offset( mIs3D, SFXDescription ),
"If true, sounds played with this description will have a position and orientation in space.\n"
"Unlike a non-positional sound, a 3D sound will have its volume attenuated depending on the distance to the "
"listener in space. The farther the sound moves away from the listener, the less audible it will be.\n\n"
"Non-positional sounds, in contrast, will remain at their original volume regardless of where the listener is.\n\n"
"@note Whether a sound is positional or non-positional cannot be changed once the sound was created so this field "
"determines up front which is the case for a given sound.\n\n"
"@ref SFX_3d\n"
"@ref SFXSource_volume" );
addField( "referenceDistance", TypeF32, Offset( mMinDistance, SFXDescription ),
"Distance at which volume attenuation begins.\n"
"Up to this distance, the sound retains its base volume.\n\n"
"In the linear distance model, the volume will linearly from this distance onwards up to maxDistance where it "
"reaches zero.\n\n"
"In the logarithmic distance model, the reference distance determine how fast the sound volume decreases "
"with distance. Each referenceDistance steps (scaled by the rolloff factor), the volume halves.\n\n"
"A rule of thumb is that for sounds that require you to be close to hear them in the real world, set the reference "
"distance to small values whereas for sounds that are widely audible set it to larger values.\n\n"
"Only applies to 3D sounds.\n"
"@see LevelInfo::soundDistanceModel\n\n"
"@ref SFX_3d\n"
"@ref SFXSource_volume" );
addField( "maxDistance", TypeF32, Offset( mMaxDistance, SFXDescription ),
"The distance at which attenuation stops.\n"
"In the linear distance model, the attenuated volume will be zero at this distance.\n\n"
"In the logarithmic model, attenuation will simply stop at this distance and the sound will keep its attenuated "
"volume from there on out. As such, it primarily functions as a cutoff factor to exponential distance attentuation "
"to limit the number of voices relevant to updates.\n\n"
"Only applies to 3D sounds.\n"
"@see LevelInfo::soundDistanceModel\n\n"
"@ref SFX_3d\n"
"@ref SFXSource_volume" );
addField( "scatterDistance", TypePoint3F,Offset( mScatterDistance, SFXDescription ),
"Bounds on random displacement of 3D sound positions.\n"
"When a 3D sound is created and given its initial position in space, this field is used to determine "
"the amount of randomization applied to the actual position given to the sound system.\n\n"
"The randomization uses the following scheme:"
"@verbatim\n"
"x += rand( - scatterDistance[ 0 ], scatterDistance[ 0 ] );\n"
"y += rand( - scatterDistance[ 1 ], scatterDistance[ 1 ] );\n"
"z += rand( - scatterDistance[ 2 ], scatterDistance[ 2 ] );\n"
"@endverbatim\n" );
addField( "coneInsideAngle", TypeS32, Offset( mConeInsideAngle, SFXDescription ),
"Inner sound cone angle in degrees.\n"
"This value determines the angle of the inner volume cone that protrudes out in the direction "
"of a sound. Within this cone, the sound source retains full volume that is unaffected by sound cone "
"settings (though still affected by distance attenuation.)\n\n"
"Valid values are from 0 to 360. Must be less than coneOutsideAngle. Default is 360. Only for 3D sounds.\n\n"
"@ref SFXSource_cones" );
addField( "coneOutsideAngle", TypeS32, Offset( mConeOutsideAngle, SFXDescription ),
"Outer sound cone angle in degrees.\n"
"This value determines the angle of the outer volume cone that protrudes out in the direction of a sound "
"and surrounds the inner volume cone. Within this cone, volume will linearly interpolate from the outer cone "
"hull inwards to the inner coner hull starting with the base volume scaled by coneOutsideVolume and ramping "
"up/down to the full base volume.\n\n"
"Valid values are from 0 to 360. Must be >= coneInsideAngle. Default is 360. Only for 3D sounds.\n\n"
"@ref SFXSource_cones" );
addField( "coneOutsideVolume", TypeF32, Offset( mConeOutsideVolume, SFXDescription ),
"Determines the volume scale factor applied the a source's base volume level outside of the outer cone.\n"
"In the outer cone, starting from outside the inner cone, the scale factor smoothly interpolates from 1.0 (within the inner cone) "
"to this value. At the moment, the allowed range is 0.0 (silence) to 1.0 (no attenuation) as amplification is only supported on "
"XAudio2 but not on the other devices.\n\n"
"Only for 3D sound.\n\n"
"@ref SFXSource_cones" );
addField( "rolloffFactor", TypeF32, Offset( mRolloffFactor, SFXDescription ),
"Scale factor to apply to logarithmic distance attenuation curve. If -1, the global rolloff setting is used.\n\n"
"@note Per-sound rolloff is only supported on OpenAL and FMOD at the moment. With other divices, the global rolloff setting "
"is used for all sounds.\n"
"@see LevelInfo::soundDistanceModel" );
endGroup( "3D" );
addGroup( "Streaming" );
addField( "isStreaming", TypeBool, Offset( mIsStreaming, SFXDescription ),
"If true, incrementally stream sounds; otherwise sounds are loaded in full.\n\n"
"@ref SFX_streaming" );
addField( "streamPacketSize", TypeS32, Offset( mStreamPacketSize, SFXDescription ),
"Number of seconds of sample data per single streaming packet.\n"
"This field allows to fine-tune streaming for individual sounds. The streaming system "
"processes streamed sounds in batches called packets. Each packet will contain a set amount "
"of sample data determined by this field. The greater its value, the more sample data each "
"packet contains, the more work is done per packet.\n\n"
"@note This field only takes effect when Torque's own sound system performs the streaming. "
"When FMOD is used, this field is ignored and streaming is performed by FMOD.\n\n"
"@ref SFX_streaming" );
addField( "streamReadAhead", TypeS32, Offset( mStreamReadAhead, SFXDescription ),
"Number of sample packets to read and buffer in advance.\n"
"This field determines the number of packets that the streaming system will try to keep buffered "
"in advance. As such it determines the number of packets that can be consumed by the sound "
"device before the playback queue is running dry. Greater values thus allow for more lag "
"in the streaming pipeline.\n\n"
"@note This field only takes effect when Torque's own sound system performs the streaming. "
"When FMOD is used, this field is ignored and streaming is performed by FMOD.\n\n"
"@ref SFX_streaming" );
endGroup( "Streaming" );
addGroup( "Reverb" );
addField( "useCustomReverb", TypeBool, Offset( mUseReverb, SFXDescription ),
"If true, use the reverb properties defined here on sounds.\n"
"By default, sounds will be assigned a generic reverb profile. By setting this flag to true, "
"a custom reverb setup can be defined using the \"Reverb\" properties that will then be assigned "
"to sounds playing with the description.\n\n"
"@ref SFX_reverb" );
addField( "reverbDirect", TypeS32, Offset( mReverb.mDirect, SFXDescription ),
"Direct path level (at low and mid frequencies).\n"
"@note SUPPORTED: EAX/I3DL2/FMODSFX\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbDirectHF", TypeS32, Offset( mReverb.mDirectHF, SFXDescription ),
"Relative direct path level at high frequencies.\n"
"@note SUPPORTED: EAX/I3DL2\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbRoom", TypeS32, Offset( mReverb.mRoom, SFXDescription ),
"Room effect level (at low and mid frequencies).\n"
"@note SUPPORTED: EAX/I3DL2/FMODSFX\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbRoomHF", TypeS32, Offset( mReverb.mRoomHF, SFXDescription ),
"Relative room effect level at high frequencies.\n"
"@note SUPPORTED: EAX/I3DL2\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbObstruction", TypeS32, Offset( mReverb.mObstruction, SFXDescription ),
"Main obstruction control (attenuation at high frequencies).\n"
"@note SUPPORTED: EAX/I3DL2\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbObstructionLFRatio", TypeF32, Offset( mReverb.mObstructionLFRatio, SFXDescription ),
"Obstruction low-frequency level re. main control.\n"
"@note SUPPORTED: EAX/I3DL2\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbOcclusion", TypeS32, Offset( mReverb.mOcclusion, SFXDescription ),
"Main occlusion control (attenuation at high frequencies)."
"@note SUPPORTED: EAX/I3DL2\n\n"
"\n@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbOcclusionLFRatio", TypeF32, Offset( mReverb.mOcclusionLFRatio, SFXDescription ),
"Occlusion low-frequency level re. main control.\n"
"@note SUPPORTED: EAX/I3DL2\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbOcclusionRoomRatio", TypeF32, Offset( mReverb.mOcclusionRoomRatio, SFXDescription ),
"Relative occlusion control for room effect.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbOcclusionDirectRatio",TypeF32, Offset( mReverb.mOcclusionDirectRatio, SFXDescription ),
"Relative occlusion control for direct path.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbExclusion", TypeS32, Offset( mReverb.mExclusion, SFXDescription ),
"Main exclusion control (attenuation at high frequencies).\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbExclusionLFRatio", TypeF32, Offset( mReverb.mExclusionLFRatio, SFXDescription ),
"Exclusion low-frequency level re. main control.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbOutsideVolumeHF", TypeS32, Offset( mReverb.mOutsideVolumeHF, SFXDescription ),
"Outside sound cone level at high frequencies.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbDopplerFactor", TypeF32, Offset( mReverb.mDopplerFactor, SFXDescription ),
"Per-source doppler factor.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbReverbRolloffFactor", TypeF32, Offset( mReverb.mRolloffFactor, SFXDescription ),
"Per-source logarithmic falloff factor.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbRoomRolloffFactor", TypeF32, Offset( mReverb.mRoomRolloffFactor, SFXDescription ),
"Room effect falloff factor.\n"
"@note SUPPORTED: EAX/I3DL2\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbAirAbsorptionFactor", TypeF32, Offset( mReverb.mAirAbsorptionFactor, SFXDescription ),
"Multiplies SFXEnvironment::airAbsorptionHR.\n"
"@note SUPPORTED: EAX Only\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf" );
addField( "reverbFlags", TypeS32, Offset( mReverb.mFlags, SFXDescription ),
"Bitfield combination of per-sound reverb flags.\n"
"@see REVERB_DIRECTHFAUTO\n"
"@see REVERB_ROOMAUTO\n"
"@see REVERB_ROOMHFAUTO\n"
"@see REVERB_INSTANCE0\n"
"@see REVERB_INSTANCE1\n"
"@see REVERB_INSTANCE2\n"
"@see REVERB_INSTANCE3\n" );
endGroup( "Reverb" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXDescription::onAdd()
{
if ( !Parent::onAdd() )
return false;
Sim::getSFXDescriptionSet()->addObject( this );
// Convert a legacy 'channel' field, if we have one.
static const char* sChannel = StringTable->insert( "channel" );
const char* channelValue = getDataField( sChannel, NULL );
if( channelValue && channelValue[ 0 ] )
{
const char* group = Con::evaluatef( "return sfxOldChannelToGroup( %s );", channelValue );
if( !Sim::findObject( group, mSourceGroup ) )
Con::errorf( "SFXDescription::onAdd - could not resolve channel '%s' to SFXSource", channelValue );
}
// Validate the data we'll be passing to
// the audio layer.
validate();
return true;
}
//-----------------------------------------------------------------------------
void SFXDescription::validate()
{
// Validate the data we'll be passing to the audio layer.
mVolume = mClampF( mVolume, 0, 1 );
if( mPitch <= 0.0f )
mPitch = 1.0f;
if( mFadeInTime < 0.0f )
mFadeInTime = 0.0f;
if( mFadeOutTime < 0.0f )
mFadeOutTime = 0.0f;
if( mRolloffFactor < 0.f )
mRolloffFactor = -1.f;
if( mMinDistance < 0.f )
mMinDistance = 0.f;
if( mMaxDistance <= mMinDistance )
mMaxDistance = mMinDistance + 0.01f;
mConeInsideAngle = mClamp( mConeInsideAngle, 0, 360 );
mConeOutsideAngle = mClamp( mConeOutsideAngle, mConeInsideAngle, 360 );
mConeOutsideVolume = mClampF( mConeOutsideVolume, 0, 1 );
if( !mIs3D )
mUseReverb = false;
mReverb.validate();
}
//-----------------------------------------------------------------------------
void SFXDescription::packData( BitStream *stream )
{
Parent::packData( stream );
stream->writeFloat( mVolume, 6 );
stream->writeFloat( mPitch, 6 );
stream->writeFloat( mPriority, 6 );
stream->writeFlag( mIsLooping );
stream->writeFlag( mFadeLoops );
stream->writeFlag( mIsStreaming );
stream->writeFlag( mIs3D );
stream->writeFlag( mUseReverb );
stream->writeFlag( mUseHardware );
sfxWrite( stream, mSourceGroup );
if( mIs3D )
{
stream->write( mMinDistance );
stream->write( mMaxDistance );
stream->write( mRolloffFactor );
mathWrite( *stream, mScatterDistance );
stream->writeInt( mConeInsideAngle, 9 );
stream->writeInt( mConeOutsideAngle, 9 );
stream->writeFloat( mConeOutsideVolume, 6 );
if( mUseReverb )
{
stream->writeRangedS32( mReverb.mDirect, -10000, 1000 );
stream->writeRangedS32( mReverb.mDirectHF, -10000, 0 );
stream->writeRangedS32( mReverb.mRoom, -10000, 1000 );
stream->writeRangedS32( mReverb.mRoomHF, -10000, 0 );
stream->writeRangedS32( mReverb.mObstruction, -10000, 0 );
stream->writeRangedF32( mReverb.mObstructionLFRatio, 0.0, 1.0, 7 );
stream->writeRangedS32( mReverb.mOcclusion, -10000, 0 );
stream->writeRangedF32( mReverb.mOcclusionLFRatio, 0.0, 1.0, 7 );
stream->writeRangedF32( mReverb.mOcclusionRoomRatio, 0.0, 10.0, 7 );
stream->writeRangedF32( mReverb.mOcclusionDirectRatio, 0.0, 10.0, 7 );
stream->writeRangedS32( mReverb.mExclusion, -10000, 0 );
stream->writeRangedF32( mReverb.mExclusionLFRatio, 0.0, 1.0, 7 );
stream->writeRangedS32( mReverb.mOutsideVolumeHF, -10000, 0 );
stream->writeRangedF32( mReverb.mDopplerFactor, 0.0, 10.0, 7 );
stream->writeRangedF32( mReverb.mRolloffFactor, 0.0, 10.0, 7 );
stream->writeRangedF32( mReverb.mRoomRolloffFactor, 0.0, 10.0, 7 );
stream->writeRangedF32( mReverb.mAirAbsorptionFactor, 0.0, 10.0, 7 );
stream->writeInt( mReverb.mFlags, 6 );
}
}
stream->write( mFadeInTime );
stream->write( mFadeOutTime );
stream->writeInt( mStreamPacketSize, 8 );
stream->writeInt( mStreamReadAhead, 8 );
mathWrite( *stream, mFadeInEase );
mathWrite( *stream, mFadeOutEase );
for( U32 i = 0; i < MaxNumParameters; ++ i )
if( stream->writeFlag( mParameters[ i ] ) )
stream->writeString( mParameters[ i ] );
}
//-----------------------------------------------------------------------------
void SFXDescription::unpackData( BitStream *stream )
{
Parent::unpackData( stream );
mVolume = stream->readFloat( 6 );
mPitch = stream->readFloat( 6 );
mPriority = stream->readFloat( 6 );
mIsLooping = stream->readFlag();
mFadeLoops = stream->readFlag();
mIsStreaming = stream->readFlag();
mIs3D = stream->readFlag();
mUseReverb = stream->readFlag();
mUseHardware = stream->readFlag();
String errorStr;
if( !sfxReadAndResolve( stream, &mSourceGroup, errorStr ) )
Con::errorf( "SFXDescription::unpackData: %s", errorStr.c_str() );
if( mIs3D )
{
stream->read( &mMinDistance );
stream->read( &mMaxDistance );
stream->read( &mRolloffFactor );
mathRead( *stream, &mScatterDistance );
mConeInsideAngle = stream->readInt( 9 );
mConeOutsideAngle = stream->readInt( 9 );
mConeOutsideVolume = stream->readFloat( 6 );
if( mUseReverb )
{
mReverb.mDirect = stream->readRangedS32( -10000, 1000 );
mReverb.mDirectHF = stream->readRangedS32( -10000, 0 );
mReverb.mRoom = stream->readRangedS32( -10000, 1000 );
mReverb.mRoomHF = stream->readRangedS32( -10000, 0 );
mReverb.mObstruction = stream->readRangedS32( -10000, 0 );
mReverb.mObstructionLFRatio = stream->readRangedF32( 0.0, 1.0, 7 );
mReverb.mOcclusion = stream->readRangedS32( -10000, 0 );
mReverb.mOcclusionLFRatio = stream->readRangedF32( 0.0, 1.0, 7 );
mReverb.mOcclusionRoomRatio = stream->readRangedF32( 0.0, 10.0, 7 );
mReverb.mOcclusionDirectRatio = stream->readRangedF32( 0.0, 10.0, 7 );
mReverb.mExclusion = stream->readRangedS32( -10000, 0 );
mReverb.mExclusionLFRatio = stream->readRangedF32( 0.0, 1.0, 7 );
mReverb.mOutsideVolumeHF = stream->readRangedS32( -10000, 0 );
mReverb.mDopplerFactor = stream->readRangedF32( 0.0, 10.0, 7 );
mReverb.mRolloffFactor = stream->readRangedF32( 0.0, 10.0, 7 );
mReverb.mRoomRolloffFactor = stream->readRangedF32( 0.0, 10.0, 7 );
mReverb.mAirAbsorptionFactor = stream->readRangedF32( 0.0, 10.0, 7 );
mReverb.mFlags = stream->readInt( 6 );
}
}
stream->read( &mFadeInTime );
stream->read( &mFadeOutTime );
mStreamPacketSize = stream->readInt( 8 );
mStreamReadAhead = stream->readInt( 8 );
mathRead( *stream, &mFadeInEase );
mathRead( *stream, &mFadeOutEase );
for( U32 i = 0; i < MaxNumParameters; ++ i )
if( stream->readFlag() )
mParameters[ i ] = stream->readSTString();
else
mParameters[ i ] = NULL;
}
//-----------------------------------------------------------------------------
void SFXDescription::inspectPostApply()
{
Parent::inspectPostApply();
validate();
if( SFX )
SFX->notifyDescriptionChanged( this );
}

View file

@ -0,0 +1,198 @@
//-----------------------------------------------------------------------------
// 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 _SFXDESCRIPTION_H_
#define _SFXDESCRIPTION_H_
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _MEASE_H_
#include "math/mEase.h"
#endif
class SFXSource;
/// The SFXDescription defines how a sound should be played.
///
/// If mConeInsideAngle and mConeOutsideAngle are not both
/// 360 then the sound will be directional and facing out
/// the Y axis. To reorient the cones, reorient the emitter
/// object.
///
/// A few tips:
///
/// Make sure that server SFXDescription are defined with the
/// datablock keyword, and that client SFXDescription are defined
/// with the 'new' or 'singleton' keyword.
///
class SFXDescription : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
enum
{
MaxNumParameters = 8,
};
/// The 0 to 1 volume scale.
F32 mVolume;
/// The pitch scale.
F32 mPitch;
/// If true the sound will loop.
bool mIsLooping;
/// If true the sound data will be streamed from
/// disk and not loaded completely into memory.
bool mIsStreaming;
/// If true the sound will be 3D positional.
bool mIs3D;
/// If this sound is allowed to be mixed in hardware.
bool mUseHardware;
/// If true the sound uses custom reverb properties.
bool mUseReverb;
/// The distance from the emitter at which the
/// sound volume is unchanged. Beyond this distance
/// the volume begins to falloff.
///
/// This is only valid for 3D sounds.
F32 mMinDistance;
/// The distance from the emitter at which the
/// sound volume becomes zero.
///
/// This is only valid for 3D sounds.
F32 mMaxDistance;
/// The angle in degrees of the inner part of
/// the cone. It must be within 0 to 360.
///
/// This is only valid for 3D sounds.
U32 mConeInsideAngle;
/// The angle in degrees of the outer part of
/// the cone. It must be greater than mConeInsideAngle
/// and less than to 360.
///
/// This is only valid for 3D sounds.
U32 mConeOutsideAngle;
/// The volume scalar for on/beyond the outside angle.
///
/// This is only valid for 3D sounds.
F32 mConeOutsideVolume;
/// Local logarithmic distance attenuation rolloff factor. -1 to use global setting.
/// Per-sound rolloff settings only supported with some SFX providers.
F32 mRolloffFactor;
/// Max distance in both directions along each axis by which the
/// sound position of a 3D sound will be randomly scattered.
///
/// Example: if you set x=5, then the final x coordinate of the 3D
/// sound will be (OriginalX + randF( -5, +5 )).
Point3F mScatterDistance;
/// The source to which sources playing with this description will
/// be added.
SFXSource* mSourceGroup;
/// Number of seconds until playback reaches full volume after starting/resuming.
/// Zero to deactivate (default).
F32 mFadeInTime;
/// Number of seconds to fade out fading before stopping/pausing.
/// Zero to deactivate (default).
F32 mFadeOutTime;
/// Easing curve for fade-in.
EaseF mFadeInEase;
/// Easing curve for fade-out.
EaseF mFadeOutEase;
/// When mIsLooping is true, the fades will apply to each cycle. Otherwise, only
/// the first cycle will have a fade-in applied and no fade-out happens when a cycle
/// ends.
///
/// This also affects the playtime that is used to place fades. If mFadeLoops is
/// false, the total playtime so far will be used rather than the playtime of the
/// current cycle. This makes it possible to apply fades that extend across two or
/// more loops of the sound, i.e. are longer than the actual sound duration.
bool mFadeLoops;
/// The number of seconds of sound data to read per streaming
/// packet. Only relevant if "isStreaming" is true.
U32 mStreamPacketSize;
/// The number of streaming packets to read and buffer in advance.
/// Only relevant if "isStreaming" is true.
U32 mStreamReadAhead;
/// Reverb properties for sound playback.
SFXSoundReverbProperties mReverb;
/// Parameters to which sources playing with this description should automatically
/// connect when created.
StringTableEntry mParameters[ MaxNumParameters ];
/// Priority level for sounds. Higher priority sounds are less likely to be
/// culled.
F32 mPriority;
SFXDescription();
SFXDescription( const SFXDescription& desc );
DECLARE_CONOBJECT( SFXDescription );
static void initPersistFields();
// SimDataBlock.
virtual bool onAdd();
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
virtual void inspectPostApply();
/// Validates the description fixing any
/// parameters that are out of range.
void validate();
};
#endif // _SFXDESCRIPTION_H_

View file

@ -0,0 +1,210 @@
//-----------------------------------------------------------------------------
// 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/sfxDevice.h"
#include "sfx/sfxBuffer.h"
#include "sfx/sfxVoice.h"
#include "sfx/sfxInternal.h"
#include "core/tAlgorithm.h"
#include "console/console.h"
#include "console/consoleTypes.h"
//-----------------------------------------------------------------------------
SFXDevice::SFXDevice( const String& name, SFXProvider* provider, bool useHardware, S32 maxBuffers )
: mName( name ),
mProvider( provider ),
mUseHardware( useHardware ),
mMaxBuffers( maxBuffers ),
mStatNumBufferBytes( 0 ),
mStatNumBuffers( 0 ),
mStatNumVoices( 0 ),
mCaps( 0 )
{
AssertFatal( provider, "We must have a provider pointer on device creation!" );
VECTOR_SET_ASSOCIATION( mBuffers );
VECTOR_SET_ASSOCIATION( mVoices );
SFXBuffer::smBufferDestroyedSignal.notify( this, &SFXDevice::_removeBuffer );
SFXVoice::smVoiceDestroyedSignal.notify( this, &SFXDevice::_removeVoice );
Con::addVariable( "SFX::Device::numBuffers", TypeS32, &mStatNumBuffers );
Con::addVariable( "SFX::Device::numVoices", TypeS32, &mStatNumVoices );
Con::addVariable( "SFX::Device::numBufferBytes", TypeS32, &mStatNumBufferBytes );
}
//-----------------------------------------------------------------------------
SFXDevice::~SFXDevice()
{
Con::removeVariable( "SFX::Device::numBuffers" );
Con::removeVariable( "SFX::Device::numVoices" );
Con::removeVariable( "SFX::Device::numBufferBytes" );
_releaseAllResources();
}
//-----------------------------------------------------------------------------
void SFXDevice::_releaseAllResources()
{
using namespace SFXInternal;
// Kill the update thread, if there is one.
// Do this first so that further buffer processing
// can be done synchronously by us.
ThreadSafeRef< SFXUpdateThread > sfxThread = UPDATE_THREAD();
if( sfxThread != NULL )
{
gUpdateThread = NULL; // Kill the global reference.
sfxThread->stop();
sfxThread->triggerUpdate();
sfxThread->join();
sfxThread = NULL;
}
// Clean up voices. Do this before cleaning up buffers so that
// resources held by voices that are tied to resources held by buffers
// get released properly.
SFXVoice::smVoiceDestroyedSignal.remove( this, &SFXDevice::_removeVoice );
for( VoiceIterator voice = mVoices.begin();
voice != mVoices.end(); voice++ )
( *voice )->destroySelf();
mVoices.clear();
// Clean up buffers.
SFXBuffer::smBufferDestroyedSignal.remove( this, &SFXDevice::_removeBuffer );
for( BufferIterator buffer = mBuffers.begin();
buffer != mBuffers.end(); ++ buffer )
if( !( *buffer )->isDead() )
( *buffer )->destroySelf();
mBuffers.clear();
// Flush all asynchronous requests.
THREAD_POOL().flushWorkItems();
// Clean out the buffer update list and kill
// all buffers that surfaced on the dead list.
// Now the sound buffers are really gone.
UPDATE_LIST().process();
PurgeDeadBuffers();
// Clean out stats.
mStatNumBuffers = 0;
mStatNumVoices = 0;
mStatNumBufferBytes = 0;
}
//-----------------------------------------------------------------------------
void SFXDevice::update()
{
using namespace SFXInternal;
// If we don't have an update thread, do the
// updates now on the main thread.
if( !UPDATE_THREAD() )
UPDATE_LIST().process( MAIN_THREAD_PROCESS_TIMEOUT );
// Clean out buffers that have surfaced on the dead
// buffer list.
PurgeDeadBuffers();
}
//-----------------------------------------------------------------------------
void SFXDevice::_addBuffer( SFXBuffer* buffer )
{
AssertFatal( buffer, "SFXDevice::_addBuffer() - Got a null buffer!" );
// Register the buffer.
mBuffers.push_back( buffer );
mStatNumBuffers ++;
mStatNumBufferBytes += buffer->getMemoryUsed();
// Start loading the buffer.
buffer->load();
}
//-----------------------------------------------------------------------------
void SFXDevice::_removeBuffer( SFXBuffer* buffer )
{
AssertFatal( buffer, "SFXDevice::_removeBuffer() - Got a null buffer!" );
BufferIterator iter = find( mBuffers.begin(), mBuffers.end(), buffer );
if( iter != mBuffers.end() )
{
SFXBuffer* buffer = *iter;
mStatNumBufferBytes -= buffer->getMemoryUsed();
mStatNumBuffers --;
mBuffers.erase( iter );
}
}
//-----------------------------------------------------------------------------
void SFXDevice::_addVoice( SFXVoice* voice )
{
AssertFatal( voice, "SFXDevice::_addVoice() - Got a null voice!" );
using namespace SFXInternal;
// Bind the voice to its buffer. This is deferred up to here in order
// to only bind voices that have been successfully constructed.
voice->_attachToBuffer();
// Register the voice.
mVoices.push_back( voice );
mStatNumVoices ++;
}
//-----------------------------------------------------------------------------
void SFXDevice::_removeVoice( SFXVoice* voice )
{
AssertFatal( voice, "SFXDevice::_removeVoice() - Got null voice!" );
VoiceIterator iter = find( mVoices.begin(), mVoices.end(), voice );
if( iter != mVoices.end() )
{
mStatNumVoices --;
mVoices.erase( iter );
}
}

View file

@ -0,0 +1,211 @@
//-----------------------------------------------------------------------------
// 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 _SFXDEVICE_H_
#define _SFXDEVICE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _THREADSAFEREF_H_
#include "platform/threads/threadSafeRefCount.h"
#endif
class SFXProvider;
class SFXListener;
class SFXBuffer;
class SFXVoice;
class SFXProfile;
class SFXDevice;
class SFXStream;
class SFXDescription;
/// Abstract base class for back-end sound API implementations.
class SFXDevice
{
public:
typedef void Parent;
/// Device capability flags.
enum ECaps
{
CAPS_Reverb = BIT( 0 ), ///< Device supports reverb environments.
CAPS_VoiceManagement = BIT( 1 ), ///< Device manages voices on its own; deactivates virtualization code in SFX system.
CAPS_Occlusion = BIT( 2 ), ///< Device has its own sound occlusion handling (SFXOcclusionManager).
CAPS_DSPEffects = BIT( 3 ), ///< Device implements DSP effects (SFXDSPManager).
CAPS_MultiListener = BIT( 4 ), ///< Device supports multiple listeners.
CAPS_FMODDesigner = BIT( 5 ), ///< FMOD Designer support.
};
protected:
typedef Vector< SFXBuffer* > BufferVector;
typedef Vector< SFXVoice* > VoiceVector;
typedef BufferVector::iterator BufferIterator;
typedef VoiceVector::iterator VoiceIterator;
SFXDevice( const String& name, SFXProvider* provider, bool useHardware, S32 maxBuffers );
/// The name of this device.
String mName;
/// The provider which created this device.
SFXProvider* mProvider;
/// Should the device try to use hardware processing.
bool mUseHardware;
/// The maximum playback buffers this device will use.
S32 mMaxBuffers;
/// Current set of sound buffers.
BufferVector mBuffers;
/// Current set of voices.
VoiceVector mVoices;
/// Device capabilities.
U32 mCaps;
/// Current number of buffers. Reflected in $SFX::Device::numBuffers.
U32 mStatNumBuffers;
/// Current number of voices. Reflected in $SFX::Device::numVoices.
U32 mStatNumVoices;
/// Current total memory size of sound buffers. Reflected in $SFX::Device::numBufferBytes.
U32 mStatNumBufferBytes;
/// Register a buffer with the device.
/// This also triggers the buffer's stream packet request chain.
void _addBuffer( SFXBuffer* buffer );
/// Unregister the given buffer.
void _removeBuffer( SFXBuffer* buffer );
/// Register a voice with the device.
void _addVoice( SFXVoice* voice );
/// Unregister the given voice.
virtual void _removeVoice( SFXVoice* buffer );
/// Release all resources tied to the device. Can be called repeatedly
/// without harm. It is meant for device destructors that will severe
/// the connection to the sound API and thus need all resources freed
/// before the base destructor is called.
void _releaseAllResources();
public:
virtual ~SFXDevice();
/// Returns the provider which created this device.
SFXProvider* getProvider() const { return mProvider; }
/// Is the device set to use hardware processing.
bool getUseHardware() const { return mUseHardware; }
/// The maximum number of playback buffers this device will use.
S32 getMaxBuffers() const { return mMaxBuffers; }
/// Returns the name of this device.
const String& getName() const { return mName; }
/// Return the device capability flags.
U32 getCaps() const { return mCaps; }
/// Tries to create a new sound buffer. If creation fails
/// freeing another buffer will usually allow a new one to
/// be created.
///
/// @param stream The sound data stream.
/// @param description The playback configuration.
///
/// @return Returns a new buffer or NULL if one cannot be created.
///
virtual SFXBuffer* createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description ) = 0;
/// Create a sound buffer directly for a file. This is for
/// devices that implemented their own custom file loading.
///
/// @note Only implemented on specific SFXDevices.
/// @return Return a new buffer or NULL.
virtual SFXBuffer* createBuffer( const String& fileName, SFXDescription* description ) { return NULL; }
/// Tries to create a new voice.
///
/// @param is3d True if the voice should have 3D sound enabled.
/// @param buffer The sound data to play by the voice.
///
/// @return Returns a new voice or NULL if one cannot be created.
virtual SFXVoice* createVoice( bool is3D, SFXBuffer* buffer ) = 0;
/// Set the rolloff curve to be used by distance attenuation of 3D sounds.
virtual void setDistanceModel( SFXDistanceModel model ) {}
/// Set the scale factor to use for doppler effects on 3D sounds.
virtual void setDopplerFactor( F32 factor ) {}
/// Set the rolloff scale factor for distance attenuation of 3D sounds.
virtual void setRolloffFactor( F32 factor ) {}
/// Set the global reverb environment.
virtual void setReverb( const SFXReverbProperties& reverb ) {}
/// Reset the global reverb environment to its default.
virtual void resetReverb() {}
/// Set the number of concurrent listeners on the device.
///
/// @note On devices that do not support multiple listeners, any value
/// other than 1 will be ignored.
virtual void setNumListeners( U32 num ) {}
/// Set the properties of the given listener.
///
/// @note On devices that do not support multiple listeners, only setting
/// the properties on index=0 will have a effect.
virtual void setListener( U32 index, const SFXListenerProperties& listener ) {}
/// Return the current total number of sound buffers.
U32 getBufferCount() const { return mBuffers.size(); }
/// Return the current total number of voices.
U32 getVoiceCount() const { return mVoices.size(); }
/// Called from SFXSystem to do any updates the device may need to make.
virtual void update();
};
#endif // _SFXDEVICE_H_

View file

@ -0,0 +1,326 @@
//-----------------------------------------------------------------------------
// 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/sfxEnvironment.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "core/module.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXEnvironment );
// Reverb flags.
static const U32 sReverbFlagDecayTimeScale = 0x001;
static const U32 sReverbFlagReflectionsScale = 0x002;
static const U32 sReverbFlagReflectionsDelayScale = 0x004;
static const U32 sReverbFlagReverbScale = 0x008;
static const U32 sReverbFlagReverbDelayScale = 0x010;
static const U32 sReverbFlagDecayHFLimit = 0x020;
static const U32 sReverbFlagEchoTimeScale = 0x040;
static const U32 sReverbFlagModulationTimeScale = 0x080;
static const U32 sReverbFlagCore0 = 0x100;
static const U32 sReverbFlagCore1 = 0x200;
static const U32 sReverbFlagHighQualityReverb = 0x400;
static const U32 sReverbFlagHighQualityDPL2Reverb = 0x800;
AFTER_MODULE_INIT( SFX )
{
Con::addConstant( "SFXEnvironment::REVERB_DECAYTIMESCALE", TypeS32, &sReverbFlagDecayTimeScale,
"SFXEnvironment::envSize affects reverberation decay time.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_REFLECTIONSSCALE", TypeS32, &sReverbFlagReflectionsScale,
"SFXEnvironment::envSize affects reflection level.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_REFLECTIONSDELAYSCALE", TypeS32, &sReverbFlagReflectionsScale,
"SFXEnvironment::envSize affects initial reflection delay time.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_REVERBSCALE", TypeS32, &sReverbFlagReverbScale,
"SFXEnvironment::envSize affects reflections level.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_REVERBDELAYSCALE", TypeS32, &sReverbFlagReverbDelayScale,
"SFXEnvironment::envSize affects late reverberation delay time.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_DECAYHFLIMIT", TypeS32, &sReverbFlagDecayHFLimit,
"SFXEnvironment::airAbsorptionHF affects SFXEnvironment::decayHFRatio.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_ECHOTIMESCALE", TypeS32, &sReverbFlagEchoTimeScale,
"SFXEnvironment::envSize affects echo time.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_MODULATIONTIMESCALE", TypeS32, &sReverbFlagModulationTimeScale,
"SFXEnvironment::envSize affects modulation time.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_CORE0", TypeS32, &sReverbFlagCore0,
"PS2 Only - Reverb is applied to CORE0 (hw voices 0-23).\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_CORE1", TypeS32, &sReverbFlagCore1,
"PS2 Only - Reverb is applied to CORE1 (hw voices 24-47).\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_HIGHQUALITYREVERB", TypeS32, &sReverbFlagHighQualityReverb,
"GameCube/Wii Only - Use high-quality reverb.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
Con::addConstant( "SFXEnvironment::REVERB_HIGHQUALITYDPL2REVERB", TypeS32, &sReverbFlagHighQualityDPL2Reverb,
"GameCube/Wii Only - Use high-quality DPL2 reverb.\n"
"@see SFXEnvironment::flags\n\n"
"@ingroup SFXEnvironment" );
}
ConsoleDocClass( SFXEnvironment,
"@brief Description of a reverb environment.\n\n"
"A reverb environment specifies how the audio mixer should render advanced environmental audio "
"effects. \n\n"
"To use reverb environments in your level, set up one or more ambient audio spaces, assign "
"reverb environments appropriately, and then attach the SFXAmbiences to your LevelInfo (taking effect "
"globally) or Zone objects (taking effect locally).\n\n"
"To define your own custom reverb environments, it is usually easiest to adapt one of the pre-existing "
"reverb definitions:\n"
"@tsexample_nopar\n"
"singleton SFXEnvironment( AudioEnvCustomUnderwater : AudioEnvUnderwater )\n"
"{\n"
" // Override select properties from AudioEnvUnderwater here.\n"
"};\n"
"@endtsexample\n\n"
"In the Datablock Editor, this can be done by selecting an existing environment to copy from when creating "
"the SFXEnvironment datablock.\n\n"
"For a precise description of reverb audio and the properties of this class, please consult the EAX "
"documentation.\n\n"
"All SFXEnvironment instances are automatically added to the global @c SFXEnvironmentSet.\n\n"
"@see http://www.atc.creative.com/algorithms/eax20.pdf\n"
"@see http://connect.creativelabs.com/developer/Gaming/Forms/AllItems.aspx\n"
"@see SFXAmbience::environment\n\n"
"@ref SFX_reverb\n"
"@ingroup SFX\n"
);
//-----------------------------------------------------------------------------
SFXEnvironment::SFXEnvironment()
{
}
//-----------------------------------------------------------------------------
void SFXEnvironment::initPersistFields()
{
addGroup( "Reverb" );
addField( "envSize", TypeF32, Offset( mReverb.mEnvSize, SFXEnvironment ),
"Environment size in meters." );
addField( "envDiffusion", TypeF32, Offset( mReverb.mEnvDiffusion, SFXEnvironment ),
"Environment diffusion." );
addField( "room", TypeS32, Offset( mReverb.mRoom, SFXEnvironment ),
"Room effect level at mid-frequencies." );
addField( "roomHF", TypeS32, Offset( mReverb.mRoomHF, SFXEnvironment ),
"Relative room effect level at high frequencies." );
addField( "roomLF", TypeS32, Offset( mReverb.mRoomLF, SFXEnvironment ),
"Relative room effect level at low frequencies." );
addField( "decayTime", TypeF32, Offset( mReverb.mDecayTime, SFXEnvironment ),
"Reverberation decay time at mid frequencies." );
addField( "decayHFRatio", TypeF32, Offset( mReverb.mDecayHFRatio, SFXEnvironment ),
"High-frequency to mid-frequency decay time ratio." );
addField( "decayLFRatio", TypeF32, Offset( mReverb.mDecayLFRatio, SFXEnvironment ),
"Low-frequency to mid-frequency decay time ratio." );
addField( "reflections", TypeS32, Offset( mReverb.mReflections, SFXEnvironment ),
"Early reflections level relative to room effect." );
addField( "reflectionsDelay", TypeF32, Offset( mReverb.mReflectionsDelay, SFXEnvironment ),
"Initial reflection delay time." );
addField( "reflectionsPan", TypeF32, Offset( mReverb.mReflectionsPan, SFXEnvironment ), 3,
"Early reflections panning vector." );
addField( "reverb", TypeS32, Offset( mReverb.mReverb, SFXEnvironment ),
"Late reverberation level relative to room effect." );
addField( "reverbDelay", TypeF32, Offset( mReverb.mReverbDelay, SFXEnvironment ),
"Late reverberation delay time relative to initial reflection." );
addField( "reverbPan", TypeF32, Offset( mReverb.mReverbPan, SFXEnvironment ), 3,
"Late reverberation panning vector." );
addField( "echoTime", TypeF32, Offset( mReverb.mEchoTime, SFXEnvironment ),
"Echo time." );
addField( "echoDepth", TypeF32, Offset( mReverb.mEchoDepth, SFXEnvironment ),
"Echo depth." );
addField( "modulationTime", TypeF32, Offset( mReverb.mModulationTime, SFXEnvironment ),
"Modulation time." );
addField( "modulationDepth", TypeF32, Offset( mReverb.mModulationDepth, SFXEnvironment ),
"Modulation depth." );
addField( "airAbsorptionHF", TypeF32, Offset( mReverb.mAirAbsorptionHF, SFXEnvironment ),
"Change in level per meter at high frequencies." );
addField( "HFReference", TypeF32, Offset( mReverb.mHFReference, SFXEnvironment ),
"Reference high frequency in Hertz." );
addField( "LFReference", TypeF32, Offset( mReverb.mLFReference, SFXEnvironment ),
"Reference low frequency in Hertz." );
addField( "roomRolloffFactor", TypeF32, Offset( mReverb.mRoomRolloffFactor, SFXEnvironment ),
"Logarithmic distance attenuation rolloff scale factor for reverb room size effect." );
addField( "diffusion", TypeF32, Offset( mReverb.mDiffusion, SFXEnvironment ),
"Value that controls the echo density in the late reverberation decay." );
addField( "density", TypeF32, Offset( mReverb.mDensity, SFXEnvironment ),
"Value that controls the modal density in the late reverberation decay." );
addField( "flags", TypeS32, Offset( mReverb.mFlags, SFXEnvironment ),
"A bitfield of reverb flags.\n"
"@see REVERB_DECAYTIMESCALE\n"
"@see REVERB_REFLECTIONSSCALE\n"
"@see REVERB_REFLECTIONSDELAYSCALE\n"
"@see REVERB_REVERBSCALE\n"
"@see REVERB_REVERBDELAYSCALE\n"
"@see REVERB_DECAYHFLIMIT\n"
"@see REVERB_ECHOTIMESCALE\n"
"@see REVERB_MODULATIONTIMESCALE\n"
"@see REVERB_CORE0\n"
"@see REVERB_CORE1\n"
"@see REVERB_HIGHQUALITYREVERB\n"
"@see REVERB_HIGHQUALITYDPL2REVERB\n" );
endGroup( "Reverb" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXEnvironment::onAdd()
{
if( !Parent::onAdd() )
return false;
Sim::getSFXEnvironmentSet()->addObject( this );
return true;
}
//-----------------------------------------------------------------------------
bool SFXEnvironment::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
validate();
return true;
}
//-----------------------------------------------------------------------------
void SFXEnvironment::inspectPostApply()
{
Parent::inspectPostApply();
validate();
}
//-----------------------------------------------------------------------------
void SFXEnvironment::validate()
{
mReverb.validate();
}
//-----------------------------------------------------------------------------
void SFXEnvironment::packData( BitStream* stream )
{
Parent::packData( stream );
stream->write( mReverb.mEnvSize );
stream->write( mReverb.mEnvDiffusion );
stream->write( mReverb.mRoom );
stream->write( mReverb.mRoomHF );
stream->write( mReverb.mRoomLF );
stream->write( mReverb.mDecayTime );
stream->write( mReverb.mDecayHFRatio );
stream->write( mReverb.mDecayLFRatio );
stream->write( mReverb.mReflections );
stream->write( mReverb.mReflectionsDelay );
stream->write( mReverb.mReflectionsPan[ 0 ] );
stream->write( mReverb.mReflectionsPan[ 1 ] );
stream->write( mReverb.mReflectionsPan[ 2 ] );
stream->write( mReverb.mReverb );
stream->write( mReverb.mReverbDelay );
stream->write( mReverb.mReverbPan[ 0 ] );
stream->write( mReverb.mReverbPan[ 1 ] );
stream->write( mReverb.mReverbPan[ 2 ] );
stream->write( mReverb.mEchoTime );
stream->write( mReverb.mEchoDepth );
stream->write( mReverb.mModulationTime );
stream->write( mReverb.mModulationDepth );
stream->write( mReverb.mAirAbsorptionHF );
stream->write( mReverb.mHFReference );
stream->write( mReverb.mLFReference );
stream->write( mReverb.mRoomRolloffFactor );
stream->write( mReverb.mDiffusion );
stream->write( mReverb.mDensity );
stream->write( mReverb.mFlags );
}
//-----------------------------------------------------------------------------
void SFXEnvironment::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
stream->read( &mReverb.mEnvSize );
stream->read( &mReverb.mEnvDiffusion );
stream->read( &mReverb.mRoom );
stream->read( &mReverb.mRoomHF );
stream->read( &mReverb.mRoomLF );
stream->read( &mReverb.mDecayTime );
stream->read( &mReverb.mDecayHFRatio );
stream->read( &mReverb.mDecayLFRatio );
stream->read( &mReverb.mReflections );
stream->read( &mReverb.mReflectionsDelay );
stream->read( &mReverb.mReflectionsPan[ 0 ] );
stream->read( &mReverb.mReflectionsPan[ 1 ] );
stream->read( &mReverb.mReflectionsPan[ 2 ] );
stream->read( &mReverb.mReverb );
stream->read( &mReverb.mReverbDelay );
stream->read( &mReverb.mReverbPan[ 0 ] );
stream->read( &mReverb.mReverbPan[ 1 ] );
stream->read( &mReverb.mReverbPan[ 2 ] );
stream->read( &mReverb.mEchoTime );
stream->read( &mReverb.mEchoDepth );
stream->read( &mReverb.mModulationTime );
stream->read( &mReverb.mModulationDepth );
stream->read( &mReverb.mAirAbsorptionHF );
stream->read( &mReverb.mHFReference );
stream->read( &mReverb.mLFReference );
stream->read( &mReverb.mRoomRolloffFactor );
stream->read( &mReverb.mDiffusion );
stream->read( &mReverb.mDensity );
stream->read( &mReverb.mFlags );
}

View file

@ -0,0 +1,73 @@
//-----------------------------------------------------------------------------
// 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 _SFXENVIRONMENT_H_
#define _SFXENVIRONMENT_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
/// A datablock that defines a reverb environment.
class SFXEnvironment : public SimDataBlock
{
public:
typedef SimDataBlock Parent;
protected:
/// The reverb properties.
SFXReverbProperties mReverb;
public:
SFXEnvironment();
///
void validate();
DECLARE_CONOBJECT( SFXEnvironment );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "A reverb environment." );
static void initPersistFields();
virtual bool onAdd();
virtual bool preload( bool server, String& errorStr );
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
virtual void inspectPostApply();
/// @return The reverb properties of the sound environment.
const SFXReverbProperties& getReverb() const { return mReverb; }
};
#endif // _SFXENVIRONMENT_H_

View file

@ -0,0 +1,173 @@
//-----------------------------------------------------------------------------
// 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/sfxFileStream.h"
#include "core/stream/fileStream.h"
#include "console/console.h"
#include "core/util/safeDelete.h"
SFXFileStream::ExtensionsVector SFXFileStream::smExtensions( __FILE__, __LINE__ );
SFXFileStream::CreateFnsVector SFXFileStream::smCreateFns( __FILE__, __LINE__ );
void SFXFileStream::registerExtension( String ext, SFXFILESTREAM_CREATE_FN create_fn )
{
// Register the stream creation first.
smExtensions.push_back( ext );
smCreateFns.push_back( create_fn );
}
void SFXFileStream::unregisterExtension( String ext )
{
for( ExtensionsVector::iterator iter = smExtensions.begin();
iter != smExtensions.end(); ++ iter )
if( ( *iter ).equal( ext, String::NoCase ) )
{
smExtensions.erase( iter );
return;
}
}
SFXFileStream* SFXFileStream::create( String filename )
{
//RDTODO: if original file has an extension, we should try that first
// First strip off our current extension (validating
// against a list of known extensions so that we don't
// strip off the last part of a file name with a dot in it.
String noExtension = Platform::stripExtension( filename, smExtensions );
SFXFileStream *sfxStream = NULL;
for( U32 i = 0; i < smExtensions.size(); i++ )
{
String testName = noExtension + smExtensions[ i ];
Stream *stream = FileStream::createAndOpen( testName, Torque::FS::File::Read );
if ( !stream )
continue;
// Note that the creation function swallows up the
// resource stream and will take care of deleting it.
sfxStream = smCreateFns[i]( stream );
if ( sfxStream )
return sfxStream;
}
return NULL;
}
bool SFXFileStream::exists( String filename )
{
// First strip off our current extension (validating
// against a list of known extensions so that we don't
// strip off the last part of a file name with a dot in it.
String noExtension = Platform::stripExtension( filename, smExtensions );
for( U32 i = 0; i < smExtensions.size(); i++ )
{
String testName = noExtension + smExtensions[ i ];
if( Torque::FS::IsFile( testName ) )
return true;
}
return false;
}
SFXFileStream::SFXFileStream()
: mStream( NULL ),
mOwnStream( false ),
mFormat( 0, 0, 0 ),
mSamples( 0 )
{
}
SFXFileStream::SFXFileStream( const SFXFileStream& cloneFrom )
{
mStream = cloneFrom.mStream->clone();
if( !mStream )
{
Con::errorf( "SFXFileStream::SFXFileStream() - Failed to clone source stream" );
return;
}
mOwnStream = true;
mFormat = cloneFrom.mFormat;
mSamples = cloneFrom.mSamples;
}
SFXFileStream::~SFXFileStream()
{
// If the stream is still open, close it now. _close()
// should usually be called by the destructor of derived classes,
// but it their constructor fails, these won't even run.
if( mStream && mOwnStream )
SAFE_DELETE( mStream );
}
bool SFXFileStream::open( Stream *stream, bool ownStream )
{
AssertFatal( stream, "SFXFileStream::open() - Got null stream!" );
close();
mStream = stream;
mOwnStream = ownStream;
if( _readHeader() )
{
reset(); // Make sure we're set to read sample data.
return true;
}
else
return false;
}
void SFXFileStream::close()
{
if ( !mStream )
return;
// Let the overloaded class cleanup.
_close();
// We only close it if we own it.
if ( mOwnStream )
SAFE_DELETE( mStream );
// Reset these to make it easier to detect bugs.
mFormat.set( 0, 0, 0 );
mSamples = 0;
}
bool SFXFileStream::isEOS() const
{
if ( !mStream )
return true;
return mStream->getStatus() != Stream::Ok;
}

View file

@ -0,0 +1,115 @@
//-----------------------------------------------------------------------------
// 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 _SFXFILESTREAM_H_
#define _SFXFILESTREAM_H_
#ifndef _SFXSTREAM_H_
# include "sfx/sfxStream.h"
#endif
#ifndef _TVECTOR_H_
# include "core/util/tVector.h"
#endif
#ifndef _TORQUE_STRING_H_
# include "core/util/str.h"
#endif
class Stream;
class SFXFileStream;
///
typedef SFXFileStream* ( *SFXFILESTREAM_CREATE_FN )( Stream *stream );
/// An SFXStream that streams from a file.
class SFXFileStream : public SFXStream
{
protected:
typedef Vector< String > ExtensionsVector;
typedef Vector< SFXFILESTREAM_CREATE_FN > CreateFnsVector;
static ExtensionsVector smExtensions;
static CreateFnsVector smCreateFns;
/// The file stream we're reading from.
Stream *mStream;
/// If true then we're responsible for closing the stream.
bool mOwnStream;
/// The format of the data in the stream.
SFXFormat mFormat;
/// The number of samples in the data stream.
U32 mSamples;
/// Constructs the stream in an uninitilized state.
SFXFileStream();
///
SFXFileStream( const SFXFileStream& cloneFrom );
/// Overloaded in the derived classes to read
/// the file header. It should initialize
/// mFormat and mSamples.
virtual bool _readHeader() = 0;
/// Overloaded for cleanup of file format
/// specific structures.
virtual void _close() = 0;
public:
///
static void registerExtension( String ext, SFXFILESTREAM_CREATE_FN create_fn );
///
static void unregisterExtension( String ext );
/// This is a helper function used to create an appropriate SFXStream
/// for the requested sound file.
///
/// @param filename The sound file path with or without extension.
///
static SFXFileStream* create( String filename );
///
static bool exists( String filename );
/// Destructor.
virtual ~SFXFileStream();
/// Opens and optionally takes ownership of the stream.
bool open( Stream *stream, bool ownStream = false );
/// Closes the stream.
void close();
// SFXStream.
const SFXFormat& getFormat() const { return mFormat; }
U32 getSampleCount() const { return mSamples; }
U32 getDataLength() const { return mSamples * mFormat.getBytesPerSample(); }
U32 getDuration() const { return mFormat.getDuration( mSamples ); }
bool isEOS() const;
};
#endif // _SFXFILESTREAM_H_

View file

@ -0,0 +1,200 @@
//-----------------------------------------------------------------------------
// 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/sfxInternal.h"
#include "sfx/sfxDescription.h"
#include "core/util/safeDelete.h"
#include "platform/threads/threadPoolAsyncIO.h"
/// @file
/// Implementation of async sound I/O.
//#define DEBUG_SPEW
namespace SFXInternal {
ThreadSafeRef< SFXUpdateThread > gUpdateThread;
ThreadSafeRef< SFXBufferProcessList > gBufferUpdateList = new SFXBufferProcessList;
ThreadSafeDeque< SFXBuffer* > gDeadBufferList;
//==========================================================================
// SFXAsyncStream implementation.
//==========================================================================
//--------------------------------------------------------------------------
SFXAsyncStream::SFXAsyncStream( const SFXStreamRef& stream,
bool isIncremental,
U32 streamPacketLength,
U32 numReadAhead,
bool isLooping )
: Parent( stream,
isIncremental
? streamPacketLength
* stream->getFormat().getSamplesPerSecond()
* stream->getFormat().getBytesPerSample() // Streamed buffer; read in incremental packets.
: stream->getDataLength(), // Non-streamed buffer; read entire stream in one packet.
stream->getDataLength() // Read all remaining data in stream.
- ( dynamic_cast< IPositionable< U32 >* >( stream.ptr() )
? dynamic_cast< IPositionable< U32 >* >( stream.ptr() )->getPosition()
: 0 ),
numReadAhead,
isLooping,
&THREAD_POOL() ),
mReadSilenceAtEnd( false )
{
}
//--------------------------------------------------------------------------
void SFXAsyncStream::_onArrival( SFXStreamPacket* const& packet )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXAsyncStream] Packet arrived" );
#endif
Parent::_onArrival( packet );
// Some buffer may be waiting for this data so trigger
// an update.
if( !mIsStopped )
TriggerUpdate();
}
//--------------------------------------------------------------------------
void SFXAsyncStream::_requestNext()
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXAsyncStream] Next packet requested" );
#endif
if( !mNumRemainingSourceElements && mReadSilenceAtEnd )
{
// Push an artificial packet of silence.
SFXStreamPacket* packet = _newPacket( mPacketSize );
packet->mIndex = mNextPacketIndex;
mNextPacketIndex ++;
mReadSilenceAtEnd = false;
dMemset( packet->data, 0, packet->size );
packet->mIsLast = true;
_onArrival( packet );
}
else
Parent::_requestNext();
}
//==========================================================================
// SFXWrapAroundBuffer implementation.
//==========================================================================
//--------------------------------------------------------------------------
SFXWrapAroundBuffer::SFXWrapAroundBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description )
: Parent( stream, description ),
mWriteOffset( 0 )
{
// Determine the device buffer metrics.
const U32 maxQueuedPackets = isStreaming() ? SFXAsyncQueue::DEFAULT_STREAM_QUEUE_LENGTH : 1;
const U32 packetSize = mAsyncState->mStream->getPacketSize();
mBufferSize = maxQueuedPackets * packetSize;
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXWrapAroundBuffer] size=%i, packets=%i",
mBufferSize, maxQueuedPackets );
#endif
// For streaming buffers that are not looping, add a packet of silence to the
// source stream.
if( isStreaming() && !description->mIsLooping )
mAsyncState->mStream->setReadSilenceAtEnd( true );
}
//--------------------------------------------------------------------------
void SFXWrapAroundBuffer::write( SFXStreamPacket* const* packets, U32 num )
{
AssertFatal( SFXInternal::isSFXThread(), "SFXWrapAroundBuffer::write() - not on SFX thread" );
for( U32 i = 0; i < num; ++ i )
{
const SFXStreamPacket* packet = packets[ i ];
// Determine where in the buffer to copy the data to. In case we are crossing over
// the wrap-around point, we need to copy in two slices.
U32 offset1 = 0;
U32 offset2 = 0;
U32 numBytes1 = 0;
U32 numBytes2 = 0;
offset1 = mWriteOffset % mBufferSize;
numBytes1 = packet->size;
if( offset1 + numBytes1 > mBufferSize )
{
// Crossing wrap-around point.
numBytes1 = mBufferSize - offset1;
numBytes2 = packet->size - numBytes1;
}
offset2 = offset1 + numBytes1;
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXWrapAroundBuffer] writing %i bytes from packet #%i at %i (stream offset: %i)",
numBytes1, packet->mIndex, offset1, mWriteOffset );
#endif
// Copy the packet data.
_copyData( offset1, packet->data, numBytes1 );
if( numBytes2 > 0 )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXWrapAroundBuffer] writing %i more bytes at %i",
numBytes2, offset2 );
#endif
_copyData( offset2, &packet->data[ numBytes1 ], numBytes2 );
}
dFetchAndAdd( mWriteOffset, packet->size );
// Free the packet.
destructSingle( packet );
}
}
} // namespace SFXInternal

View file

@ -0,0 +1,456 @@
//-----------------------------------------------------------------------------
// 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 <em>not</em> 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 <em>not</em> 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 ) {}
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();
U32 threadId;
if( sfxThread != NULL )
threadId = sfxThread->getId();
else
threadId = ThreadManager::getMainThreadId();
return ThreadManager::compare( ThreadManager::getCurrentThreadId(), threadId );
}
} // namespace SFXInternal
#endif // !_SFXINTERNAL_H_

View file

@ -0,0 +1,117 @@
//-----------------------------------------------------------------------------
// 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/sfxMemoryStream.h"
#include "platform/typetraits.h"
#include "console/console.h"
SFXMemoryStream::SFXMemoryStream( const SFXFormat& format,
SourceStreamType* stream,
U32 numSamples )
: IInputStreamFilter< U8, SourceStreamType* >( stream ),
mFormat( format ),
mNumSamplesTotal( numSamples ),
mNumSamplesLeft( numSamples ),
mCurrentPacket( NULL ),
mCurrentPacketOffset( 0 )
{
}
void SFXMemoryStream::reset()
{
if( dynamic_cast< IResettable* >( getSourceStream() ) )
{
reinterpret_cast< IResettable* >( getSourceStream() )->reset();
if( mCurrentPacket )
destructSingle( mCurrentPacket );
mCurrentPacket = NULL;
mCurrentPacketOffset = 0;
mNumSamplesLeft = mNumSamplesTotal;
}
else
Con::errorf( "SFXMemoryStream - cannot reset source stream" );
}
U32 SFXMemoryStream::read( U8* buffer, U32 length )
{
U32 bufferOffset = 0;
// Determine how much we're supposed to read.
U32 numBytesToCopy = length;
if( mNumSamplesLeft != U32_MAX )
numBytesToCopy = getMin( length, mNumSamplesLeft * mFormat.getBytesPerSample() );
numBytesToCopy -= numBytesToCopy % mFormat.getBytesPerSample();
// Copy the data.
U32 numBytesLeftToCopy = numBytesToCopy;
while( numBytesLeftToCopy )
{
// If we have a current packet, use its data.
if( mCurrentPacket )
{
U32 numBytesLeftInCurrentPacket = mCurrentPacket->size - mCurrentPacketOffset;
// Copy data.
if( numBytesLeftInCurrentPacket )
{
const U32 numBytesToCopy = getMin( numBytesLeftInCurrentPacket, numBytesLeftToCopy );
dMemcpy( &buffer[ bufferOffset ], &mCurrentPacket->data[ mCurrentPacketOffset ], numBytesToCopy );
bufferOffset += numBytesToCopy;
mCurrentPacketOffset += numBytesToCopy;
numBytesLeftInCurrentPacket -= numBytesToCopy;
numBytesLeftToCopy -= numBytesToCopy;
}
// Discard the packet if there's no data left.
if( !numBytesLeftInCurrentPacket )
{
destructSingle( mCurrentPacket );
mCurrentPacket = NULL;
mCurrentPacketOffset = 0;
}
}
else
{
// Read a new packet.
if( !getSourceStream()->read( &mCurrentPacket, 1 ) )
break;
}
}
// Update count of remaining samples.
U32 numBytesCopied = numBytesToCopy - numBytesLeftToCopy;
if( mNumSamplesLeft != U32_MAX )
mNumSamplesLeft -= ( numBytesCopied / mFormat.getBytesPerSample() );
return numBytesCopied;
}

View file

@ -0,0 +1,84 @@
//-----------------------------------------------------------------------------
// 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 _SFXMEMORYSTREAM_H_
#define _SFXMEMORYSTREAM_H_
#ifndef _SFXSTREAM_H_
#include "sfx/sfxStream.h"
#endif
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
#ifndef _RAWDATA_H_
#include "core/util/rawData.h"
#endif
/// A stream filter that converts sample packets from its source stream
/// to a continuous sample stream. Useful for feeding sound from a source
/// that pushes sample data in discrete packets.
///
/// @note For the SFXMemoryStream to allow a reset(), the source input
/// stream must implement IResettable.
class SFXMemoryStream : public SFXStream,
public IInputStreamFilter< U8, IInputStream< RawData* >* >
{
public:
typedef SFXStream Parent;
protected:
///
SFXFormat mFormat;
/// Total number of samples in the stream. If this is U32_MAX, the stream
/// is considered to be of indefinite size.
U32 mNumSamplesTotal;
/// Number of samples left to be read from stream. Locked to U32_MAX for
/// stream of indefinite size.
U32 mNumSamplesLeft;
/// The current sample data packet.
RawData* mCurrentPacket;
/// Read offset in the current sample data packet.
U32 mCurrentPacketOffset;
public:
///
SFXMemoryStream( const SFXFormat& format, SourceStreamType* stream, U32 numSamples = U32_MAX );
// SFXStream.
const SFXFormat& getFormat() const { return mFormat; }
U32 getSampleCount() const { return mNumSamplesTotal; }
U32 getDataLength() const { return ( mNumSamplesTotal == U32_MAX ? U32_MAX : mFormat.getDataLength( getDuration() ) ); }
U32 getDuration() const { return ( mNumSamplesTotal == U32_MAX ? U32_MAX : mFormat.getDuration( mNumSamplesTotal ) ); }
bool isEOS() const { return ( mNumSamplesLeft != 0 ); }
void reset();
U32 read( U8 *buffer, U32 length );
};
#endif // !_SFXMEMORYSTREAM_H_

View file

@ -0,0 +1,194 @@
//-----------------------------------------------------------------------------
// 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/sfxModifier.h"
#include "sfx/sfxSource.h"
//=============================================================================
// SFXOneShotModifier.
//=============================================================================
//-----------------------------------------------------------------------------
SFXOneShotModifier::SFXOneShotModifier( SFXSource* source, F32 triggerPos, bool removeWhenDone )
: Parent( source, removeWhenDone ),
mTriggerPos( triggerPos )
{
}
//-----------------------------------------------------------------------------
bool SFXOneShotModifier::update()
{
if( mSource->getElapsedPlayTimeCurrentCycle() >= mTriggerPos )
{
_onTrigger();
return mRemoveWhenDone;
}
else
return true;
}
//=============================================================================
// SFXRangeModifier.
//=============================================================================
//-----------------------------------------------------------------------------
SFXRangeModifier::SFXRangeModifier( SFXSource* source, F32 startTime, F32 endTime, bool removeWhenDone )
: Parent( source, removeWhenDone ),
mStartTime( startTime ),
mEndTime( endTime ),
mIsActive( false )
{
}
//-----------------------------------------------------------------------------
bool SFXRangeModifier::update()
{
if( !isActive() )
{
SFXStatus status = mSource->getStatus();
if( ( status == SFXStatusPlaying || status == SFXStatusBlocked )
&& mSource->getElapsedPlayTimeCurrentCycle() >= mStartTime )
{
mIsActive = true;
_onStart();
}
}
if( isActive() )
_onUpdate();
if( isActive() )
{
SFXStatus status = mSource->getStatus();
if( ( status == SFXStatusPlaying || status == SFXStatusBlocked )
&& mSource->getElapsedPlayTimeCurrentCycle() > mEndTime )
{
_onEnd();
mIsActive = false;
return mRemoveWhenDone;
}
}
return true;
}
//=============================================================================
// SFXFadeModifier.
//=============================================================================
//-----------------------------------------------------------------------------
SFXFadeModifier::SFXFadeModifier( SFXSource* source, F32 time, F32 endVolume, F32 startTime, EOnEnd onEndDo, bool removeWhenDone )
: Parent( source, startTime, startTime + time, removeWhenDone ),
mEndVolume( endVolume ),
mOnEnd( onEndDo )
{
}
//-----------------------------------------------------------------------------
SFXFadeModifier::~SFXFadeModifier()
{
// If the fade is still ongoing, restore the source's volume.
// For fade-in, set to end volume. For fade-out, set to start volume.
if( isActive() )
{
if( mStartVolume > mEndVolume )
mSource->setVolume( mStartVolume );
else
mSource->setVolume( mEndVolume );
}
}
//-----------------------------------------------------------------------------
void SFXFadeModifier::_onStart()
{
mStartVolume = mSource->getVolume();
mCurrentVolume = mStartVolume;
}
//-----------------------------------------------------------------------------
void SFXFadeModifier::_onUpdate()
{
F32 multiplier = ( mSource->getElapsedPlayTimeCurrentCycle() - mStartTime ) / ( mEndTime - mStartTime );
F32 newVolume;
if( mStartVolume > mEndVolume )
newVolume = mStartVolume - ( ( mStartVolume - mEndVolume ) * multiplier );
else
newVolume = mStartVolume + ( ( mEndVolume - mStartVolume ) * multiplier );
if( newVolume != mCurrentVolume )
{
mCurrentVolume = newVolume;
mSource->setVolume( mCurrentVolume );
}
}
//-----------------------------------------------------------------------------
void SFXFadeModifier::_onEnd()
{
mSource->setVolume( mEndVolume );
switch( mOnEnd )
{
case ON_END_Pause:
mSource->pause( 0.f ); // Pause without fade.
break;
case ON_END_Stop:
mSource->stop( 0.f ); // Stop without fade.
break;
case ON_END_Nop: ;
}
}
//=============================================================================
// SFXMarkerModifier.
//=============================================================================
//-----------------------------------------------------------------------------
SFXMarkerModifier::SFXMarkerModifier( SFXSource* source, const String& name, F32 pos, bool removeWhenDone )
: Parent( source, pos, removeWhenDone ),
mMarkerName( name )
{
}
//-----------------------------------------------------------------------------
void SFXMarkerModifier::_onTrigger()
{
Con::executef( mSource, "onMarkerPassed", mMarkerName.c_str() );
}

View file

@ -0,0 +1,191 @@
//-----------------------------------------------------------------------------
// 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 _SFXMODIFIER_H_
#define _SFXMODIFIER_H_
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
class SFXSource;
/// An SFXModifier modifies the properties of an SFXSource while its playback
/// is running.
class SFXModifier : public IPolled
{
protected:
/// The source that this effect works on.
SFXSource* mSource;
/// If true, the effect is removed from the effects stack
bool mRemoveWhenDone;
public:
/// Create an effect that operates on "source".
SFXModifier( SFXSource* source, bool removeWhenDone = false )
: mSource( source ) {}
virtual ~SFXModifier() {}
};
/// An SFXModifier that is triggered once after passing a certain playback position.
class SFXOneShotModifier : public SFXModifier
{
public:
typedef SFXModifier Parent;
protected:
/// Playback position that triggers the effect.
F32 mTriggerPos;
///
virtual void _onTrigger() = 0;
public:
/// Create an effect that triggers when playback of "source" passes "triggerPos".
SFXOneShotModifier( SFXSource* source, F32 triggerPos, bool removeWhenDone = false );
// IPolled.
virtual bool update();
};
/// An SFXModifier that is spans a certain range of playback time.
class SFXRangeModifier : public SFXModifier
{
public:
typedef SFXModifier Parent;
protected:
/// If true, the effect is currently being applied to the source.
bool mIsActive;
/// Playback position in milliseconds when this effect becomes active.
F32 mStartTime;
/// Playback position in milliseconds when this effect becomes inactive.
F32 mEndTime;
/// Called when the play cursor passes mStartTime.
/// @note There may be latency between the cursor actually passing mStartTime
/// and this method being called.
virtual void _onStart() {}
/// Called on each update() while the play cursor is in range.
virtual void _onUpdate() {}
/// Called when the play cursor passes mEndTime.
/// @note There may be latency between the cursor actually passing mEndTime
/// and this method being called.
virtual void _onEnd() {}
public:
/// Create an effect that operates on "source" between "startTime" seconds
/// (inclusive) and "endTime" seconds (exclusive).
SFXRangeModifier( SFXSource* source, F32 startTime, F32 endTime, bool removeWhenDone = false );
///
bool isActive() const { return mIsActive; }
// IPolled.
virtual bool update();
};
/// A volume fade effect (fade-in or fade-out).
class SFXFadeModifier : public SFXRangeModifier
{
public:
typedef SFXRangeModifier Parent;
enum EOnEnd
{
ON_END_Nop, ///< Do nothing with source when fade is complete.
ON_END_Stop, ///< Stop source when fade is complete.
ON_END_Pause, ///< Pause source when fade is complete.
};
protected:
/// Volume when beginning fade. Set when effect is activated.
F32 mStartVolume;
/// Volume when ending fade.
F32 mEndVolume;
/// Current volume level.
F32 mCurrentVolume;
/// Action to perform when the fade has been completed. Defaults to no action.
EOnEnd mOnEnd;
// SFXModifier.
virtual void _onStart();
virtual void _onUpdate();
virtual void _onEnd();
public:
/// Create an effect that fades the volume of "source" to "endVolume" over the
/// period of "time" seconds. The fade will start at "referenceTime" using the
/// source's current volume at the time as the start.
SFXFadeModifier( SFXSource* source, F32 time, F32 endVolume, F32 startTime, EOnEnd onEndDo = ON_END_Nop, bool removeWhenDone = false );
virtual ~SFXFadeModifier();
};
/// A modifer that calls a method on the SFXSource when a particular playback position
/// is passed.
///
/// @note At the moment, doing a setPosition() on a source will not cause markers that have
/// been jumped over in the operation to be ignored. Instead they will trigger on the
/// next update.
class SFXMarkerModifier : public SFXOneShotModifier
{
public:
typedef SFXOneShotModifier Parent;
protected:
/// Symbolic marker name that is passed to the "onMarkerPassed" callback.
String mMarkerName;
// SFXOneShotModifier
virtual void _onTrigger();
public:
SFXMarkerModifier( SFXSource* source, const String& name, F32 pos, bool removeWhenDone = false );
};
#endif // !_SFXMODIFIER_H_

View file

@ -0,0 +1,350 @@
//-----------------------------------------------------------------------------
// 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/sfxParameter.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "console/simSet.h"
#include "math/mMathFn.h"
#include "math/mathTypes.h"
#include "math/mathIO.h"
#include "core/stream/bitStream.h"
#include "platform/typetraits.h"
IMPLEMENT_CONOBJECT( SFXParameter );
ConsoleDocClass( SFXParameter,
"@brief A sound channel value that can be bound to multiple sound sources.\n\n"
"Parameters are special objects that isolate a specific property that sound sources can have and allows to bind "
"this isolated instance to multiple sound sources such that when the value of the parameter changes, all sources "
"bound to the parameter will have their respective property changed.\n\n"
"Parameters are identified and referenced by their #internalName. This means that the SFXDescription::parameters and "
"SFXTrack::parameters fields should contain the #internalNames of the SFXParameter objects which should be attached to "
"the SFXSources when they are created. No two SFXParameters should have the same #internalName.\n\n"
"All SFXParameter instances are automatically made children of the SFXParameterGroup.\n\n"
"@note To simply control the volume and/or pitch levels of a group of sounds, it is easier and more efficient to use "
"a sound source group rather than SFXParameters (see @ref SFXSource_hierarchies). Simply create a SFXSource object representing the group, assign "
"SFXDescription::sourceGroup of the sounds appropriately, and then set the volume and/or pitch level of the group to "
"modulate the respective properties of all children.\n\n"
"@section SFXParameter_updates Parameter Updates\n"
"Parameters are periodically allowed to update their own values. This makes it possible to attach custom logic to a parameter "
"and have individual parameters synchronize their values autonomously. Use the onUpdate() callback to attach "
"script code to a parameter update.\n\n"
"@tsexample\n"
"new SFXParameter( EngineRPMLevel )\n"
"{\n"
" // Set the name by which this parameter is identified.\n"
" internalName = \"EngineRPMLevel\";\n"
"\n"
" // Let this parameter control the pitch of attached sources to simulate engine RPM ramping up and down.\n"
" channel = \"Pitch\";\n"
"\n"
" // Start out with unmodified pitch.\n"
" defaultValue = 1;\n"
"\n"
" // Add a texture description of what this parameter does.\n"
" description = \"Engine RPM Level\";\n"
"};\n"
"\n"
"// Create a description that automatically attaches the engine RPM parameter.\n"
"singleton SFXDescription( EngineRPMSound : AudioLoop2D )\n"
"{\n"
" parameters[ 0 ] = \"EngineRPMLevel\";\n"
"};\n"
"\n"
"// Create sound sources for the engine.\n"
"sfxCreateSource( EngineRPMSound, \"art/sound/engine/enginePrimary\" );\n"
"sfxCreateSource( EngineRPMSound, \"art/sound/engine/engineSecondary\" );\n"
"\n"
"// Setting the parameter value will now affect the pitch level of both sound sources.\n"
"EngineRPMLevel.value = 0.5;\n"
"EngineRPMLevel.value = 1.5;\n"
"@endtsexample\n\n"
"@ref SFX_interactive\n\n"
"@ingroup SFX"
);
IMPLEMENT_CALLBACK( SFXParameter, onUpdate, void, (), (),
"Called when the sound system triggers an update on the parameter.\n"
"This occurs periodically during system operation." );
//-----------------------------------------------------------------------------
SFXParameter::SFXParameter()
: mValue( 1.f ),
mRange( 0.f, 1.f ),
mChannel( SFXChannelVolume ),
mDefaultValue( 1.f )
{
}
//-----------------------------------------------------------------------------
SFXParameter::~SFXParameter()
{
}
//-----------------------------------------------------------------------------
void SFXParameter::initPersistFields()
{
addGroup( "Sound" );
addProtectedField( "value", TypeF32, Offset( mValue, SFXParameter ),
&SFXParameter::_setValue, &defaultProtectedGetFn,
"Current value of the audio parameter.\n"
"All attached sources are notified when this value changes." );
addProtectedField( "range", TypePoint2F, Offset( mRange, SFXParameter ),
&SFXParameter::_setRange, &defaultProtectedGetFn,
"Permitted range for #value.\n"
"Minimum and maximum allowed value for the parameter. Both inclusive.\n\n"
"For all but the User0-3 channels, this property is automatically set up by SFXParameter." );
addProtectedField( "channel", TYPEID< SFXChannel >(), Offset( mChannel, SFXParameter ),
&SFXParameter::_setChannel, &defaultProtectedGetFn,
"Channel that the parameter controls.\n"
"This controls which property of the sources it is attached to the parameter controls." );
addProtectedField( "defaultValue", TypeF32, Offset( mDefaultValue, SFXParameter ),
&SFXParameter::_setDefaultValue, &defaultProtectedGetFn,
"Value to which the parameter is initially set.\n"
"When the parameter is first added to the system, #value will be set to #defaultValue." );
addField( "description", TypeRealString,Offset( mDescription, SFXParameter ),
"Textual description of the parameter.\n"
"Primarily for use in the Audio Parameters dialog of the editor to allow for easier identification "
"of parameters." );
endGroup( "Sound" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXParameter::_setValue( void *object, const char *index, const char *data )
{
reinterpret_cast< SFXParameter* >( object )->setValue( dAtof( data ) );
return false;
}
//-----------------------------------------------------------------------------
bool SFXParameter::_setRange( void *object, const char *index, const char *data )
{
Point2F range = EngineUnmarshallData< Point2F >()( data );
reinterpret_cast< SFXParameter* >( object )->setRange( range );
return false;
}
//-----------------------------------------------------------------------------
bool SFXParameter::_setChannel( void *object, const char *index, const char *data )
{
SFXChannel channel = EngineUnmarshallData< SFXChannel >()( data );
reinterpret_cast< SFXParameter* >( object )->setChannel( channel );
return false;
}
//-----------------------------------------------------------------------------
bool SFXParameter::_setDefaultValue( void *object, const char *index, const char *data )
{
reinterpret_cast< SFXParameter* >( object )->setDefaultValue( dAtof( data ) );
return false;
}
//-----------------------------------------------------------------------------
bool SFXParameter::onAdd()
{
if( !Parent::onAdd() )
return false;
mValue = mDefaultValue;
// Make sure the parameter has a name.
if( !getInternalName() || !getInternalName()[ 0 ] )
{
Con::errorf( "SFXParameter::onAdd - %i (%s): parameter object does not have a name", getId(), getName() );
return false;
}
// Make sure the parameter has a unique name.
if( find( getInternalName() ) )
{
Con::errorf( "SFXParameter::onAdd - %i (%s): a parameter called '%s' already exists", getId(), getName(), getInternalName() );
return false;
}
// Add us to the SFXParameter group.
Sim::getSFXParameterGroup()->addObject( this );
return true;
}
//-----------------------------------------------------------------------------
void SFXParameter::onRemove()
{
mEventSignal.trigger( this, SFXParameterEvent_Deleted );
Parent::onRemove();
}
//-----------------------------------------------------------------------------
void SFXParameter::update()
{
onUpdate_callback();
}
//-----------------------------------------------------------------------------
void SFXParameter::reset()
{
setValue( mDefaultValue );
}
//-----------------------------------------------------------------------------
void SFXParameter::setValue( F32 value )
{
if( value == mValue )
return;
mValue = mClampF( value, mRange.x, mRange.y );
mEventSignal.trigger( this, SFXParameterEvent_ValueChanged );
}
//-----------------------------------------------------------------------------
void SFXParameter::setDefaultValue( F32 value )
{
mDefaultValue = mClampF( value, mRange.x, mRange.y );
}
//-----------------------------------------------------------------------------
void SFXParameter::setRange( const Point2F& range )
{
if( range == mRange )
return;
mRange = range;
F32 value = mClampF( mValue, mRange.x, mRange.y );
if( value != mValue )
setValue( value );
mDefaultValue = mClampF( mDefaultValue, mRange.x, mRange.y );
}
//-----------------------------------------------------------------------------
void SFXParameter::setChannel( SFXChannel channel )
{
if( mChannel == channel )
return;
mChannel = channel;
F32 value = mValue;
switch( channel )
{
case SFXChannelVolume:
case SFXChannelConeOutsideVolume:
setRange( 0.f, 1.0f );
break;
case SFXChannelConeInsideAngle:
case SFXChannelConeOutsideAngle:
setRange( 0.f, 360.f );
break;
case SFXChannelPitch:
case SFXChannelMinDistance:
case SFXChannelMaxDistance:
case SFXChannelCursor:
setRange( 0.f, TypeTraits< F32 >::MAX );
break;
case SFXChannelStatus:
setRange( F32( SFXStatusPlaying ), F32( SFXStatusStopped ) );
break;
default:
setRange( TypeTraits< F32 >::MIN, TypeTraits< F32 >::MAX );
break;
}
// If the range setting did not result in the value already
// being changed, fire a value-change signal now so that sources
// can catch on to the new semantics. Unfortunately, we can't
// do something about the old semantic's value having been
// changed by us.
if( mValue == value )
mEventSignal.trigger( this, SFXParameterEvent_ValueChanged );
}
//-----------------------------------------------------------------------------
SFXParameter* SFXParameter::find( StringTableEntry name )
{
return dynamic_cast< SFXParameter* >(
Sim::getSFXParameterGroup()->findObjectByInternalName( name )
);
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXParameter, getParameterName, String, (),,
"Get the name of the parameter.\n"
"@return The paramete name." )
{
return object->getInternalName();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXParameter, reset, void, (),,
"Reset the parameter's value to its default.\n"
"@see SFXParameter::defaultValue\n" )
{
object->reset();
}

View file

@ -0,0 +1,171 @@
//-----------------------------------------------------------------------------
// 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 _SFXPARAMETER_H_
#define _SFXPARAMETER_H_
#ifndef _SIMOBJECT_H_
#include "console/simObject.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef _MPOINT2_H_
#include "math/mPoint2.h"
#endif
/// Enumeration of events triggered by SFXParameters.
enum SFXParameterEvent
{
/// The parameter value has changed.
SFXParameterEvent_ValueChanged,
/// The parameter is about to be deleted.
SFXParameterEvent_Deleted,
};
/// Parameter for interactive audio.
///
/// Parameters are tied to sound sources and will signal value changes so that
/// sound sources may react.
///
/// All parameters are global. The name of a parameter is its internal object name.
///
/// Like sources, parameters are exclusively client-side.
///
class SFXParameter : public SimObject
{
public:
typedef SimObject Parent;
typedef Signal< void( SFXParameter* parameter, SFXParameterEvent event ) > EventSignal;
protected:
/// The current value.
F32 mValue;
/// The min/max range of the parameter's value. Both inclusive.
Point2F mRange;
/// The channel being controlled by this parameter.
SFXChannel mChannel;
/// Value assigned to the parameter on creation and reset.
F32 mDefaultValue;
/// Help text.
String mDescription;
/// The signal used to notify attached sources of parameter events.
EventSignal mEventSignal;
/// @name Callbacks
/// @{
DECLARE_CALLBACK( void, onUpdate, () );
/// @}
static bool _setValue( void *object, const char *index, const char *data );
static bool _setRange( void *object, const char *index, const char *data );
static bool _setChannel( void *object, const char *index, const char *data );
static bool _setDefaultValue( void *object, const char *index, const char *data );
public:
SFXParameter();
~SFXParameter();
/// Look up a parameter by the given name.
static SFXParameter* find( StringTableEntry name );
/// Update the parameter's value. The default implementation will invoke a script
/// 'onUpdate' method if it is defined and do nothing otherwise.
virtual void update();
/// Reset the parameter's value to its default.
void reset();
/// Return the current value of this parameter.
F32 getValue() const { return mValue; }
/// Set the parameter's current value. Will be clamped against the parameter's valid
/// value range. If a value change occurs, a SFXParameterEvent_ValueChange event
/// is fired.
void setValue( F32 value );
/// Return the default value of this parameter. This is the value the parameter
/// will be set to when it is added to the system.
F32 getDefaultValue() const { return mDefaultValue; }
/// Set the default value of this parameter. This is the value the parameter
/// is set to when it is added to the system.
void setDefaultValue( F32 value );
/// Return the range of valid values that this parameter may take.
const Point2F& getRange() const { return mRange; }
/// Set the valid range for the value of this parameter. Note that both min
/// and max are inclusive.
void setRange( const Point2F& range );
/// Set the valid range for the value of this parameter. Note that both min
/// and max are inclusive.
void setRange( F32 minValue, F32 maxValue ) { setRange( Point2F( minValue, maxValue ) ); }
/// Return the parameter channel that is being affected by this parameter.
SFXChannel getChannel() const { return mChannel; }
/// Set the parameter channel that is being affected by this parameter.
void setChannel( SFXChannel channel );
/// Return the description text supplied for this parameter. This is used to help
/// identify the purpose of a parameter.
const String& getDescription() const { return mDescription; }
/// Set the description text for this parameter. This may be used to help identify
/// the purpose of a parameter.
void setDescription( const String& str ) { mDescription = str; }
/// Return the event signal for this parameter.
EventSignal& getEventSignal() { return mEventSignal; }
// SimObject.
virtual bool onAdd();
virtual void onRemove();
static void initPersistFields();
DECLARE_CONOBJECT( SFXParameter );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "" );
};
#endif // !_SFXPARAMETER_H_

View file

@ -0,0 +1,468 @@
//-----------------------------------------------------------------------------
// 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/sfxPlayList.h"
#include "sfx/sfxState.h"
#include "sfx/sfxTypes.h"
#include "core/stream/bitStream.h"
#include "math/mRandom.h"
#include "math/mathTypes.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXPlayList );
ConsoleDocClass( SFXPlayList,
"@brief A datablock describing a playback pattern of sounds.\n\n"
"Playlists allow to define intricate playback patterns of invidual tracks and thus allow the sound system to be "
"easily used for playing multiple sounds in single operations.\n\n"
"As playlists are %SFXTracks, they can thus be used anywhere in the engine where sound data can be assigned.\n\n"
"Each playlist can hold a maximum of 16 tracks. Longer playlists may be constructed by cascading lists, i.e. "
"by creating a playlist that references other playlists.\n\n"
"Processing of a single playlist slot progresses in a fixed set of steps that are invariably "
"iterated through for each slot (except the slot is assigned a state and its state is deactivated; in "
"this case, the controller will exit out of the slot directly):\n\n"
"<ol>\n"
"<li><b>delayIn:</b><p>Waits a set amount of time before processing the slot. This is 0 by default and "
"is determined by the #delayTimeIn (seconds to wait) and #delayTimeInVariance (bounds on randomization) "
"properties.</p></li>\n"
"<li><b>#transitionIn:</b><p>Decides what to do @b before playing the slot. Defaults to @c None which makes "
"this stage a no-operation. Alternatively, the slot can be configured to wait for playback of other "
"slots to finish (@c Wait and @c WaitAll) or to stop playback of other slots (@c Stop and @c StopAll). "
"Note that @c Wait and @c Stop always refer to the source that was last started by the list.</p></li>\n"
"<li><b>play:</b><p><p>Finally, the #track attached to the slot is played. However, this will only @b start "
"playback of the track and then immediately move on to the next stage. It will @b not wait for the "
"track to finish playing. Note also that depending on the @c replay setting for the slot, this "
"stage may pick up a source that is already playing on the slot rather than starting a new one.</p> "
"<p>Several slot properties (fade times, min/max distance, and volume/pitch scale) are used in this stage.</p></li>\n"
"<li><b>delayOut:</b><p>Waits a set amount of time before transitioning out of the slot. This works the "
"same as @c delayIn and is set to 0 by default (i.e. no delay).</p></li>\n"
"<li><b>#transitionOut:</b><p>Decides what to do @b after playing the slot. This works like #transitionIn.</p></li>\n"
"</ol>\n\n"
"This is a key difference to playlists in normal music players where upon reaching a certain slot, the slot "
"will immediately play and the player then wait for playback to finish before moving on to the next slot.\n\n"
"@note Be aware that time limits set on slot delays are soft limits. The sound system updates sound sources in discrete "
"(and equally system update frequency dependent) intervals which thus determines the granularity at which "
"time-outs can be handled.\n\n"
"@section SFXPlayList_randomization Value Randomization\n\n"
"For greater variety, many of the values for individual slots may be given a randomization limit that will "
"trigger a dynamic variance of the specified base value.\n\n"
"Any given field @c xyz that may be randomized has a corresponding field @c xyzVariance which is a two-dimensional "
"vector. The first number specifies the greatest value that may be subtracted from the given base value (i.e. the @c xyz field) "
"whereas the second number specifies the greatest value that may be added to the base value. Between these two limits, "
"a random number is generated.\n\n"
"The default variance settings of \"0 0\" will thus not allow to add or subtract anything from the base value and "
"effectively disable randomization.\n\n"
"Randomization is re-evaluated on each cycle through a list.\n\n"
"@section SFXPlayList_states Playlists and States\n\n"
"A unique aspect of playlists is that they allow their playback to be tied to the changing set of active sound states. "
"This feature enables playlists to basically connect to an extensible state machine that can be leveraged by the game "
"code to signal a multitude of different gameplay states with the audio system then automatically reacting to state "
"transitions.\n\n"
"Playlists react to states in three ways:\n"
"- Before a controller starts processing a slot it checks whether the slot is assigned a #state. If this is the "
"case, the controller checks whether the particular state is active. If it is not, the entire slot is skipped. "
"If it is, the controller goes on to process the slot.\n"
"- If a controller is in one of the delay stages for a slot that has a #state assigned and the state is deactivated, "
"the controller will stop the delay and skip any of the remaining processing stages for the slot.\n"
"- Once the play stage has been processed for a slot that has a #state assigned, the slot's #stateMode will determine "
"what happens with the playing sound source if the slot's state is deactivated while the sound is still playing.\n"
"\n"
"A simple example of how to make use of states in combination with playlists would be to set up a playlist for background "
"music that reacts to the mood of the current gameplay situation. For example, during combat, tenser music could play than "
"during normal exploration. To set this up, different %SFXStates would represent different moods in the game and the "
"background music playlist would have one slot set up for each such mood. By making use of volume fades and the "
"@c PauseWhenDeactivated #stateMode, smooth transitions between the various audio tracks can be produced.\n\n"
"@tsexample\n"
"// Create a play list from two SFXProfiles.\n"
"%playList = new SFXPlayList()\n"
"{\n"
" // Use a looped description so the list playback will loop.\n"
" description = AudioMusicLoop2D;\n"
"\n"
" track[ 0 ] = Profile1;\n"
" track[ 1 ] = Profile2;\n"
"};\n"
"\n"
"// Play the list.\n"
"sfxPlayOnce( %playList );\n"
"@endtsexample\n\n"
"@ref SFX_interactive\n\n"
"@ingroup SFX"
);
ImplementEnumType( SFXPlayListLoopMode,
"Playlist behavior when description is set to loop.\n\n"
"@see SFXDescription::isLooping\n\n"
"@see SFXPlayList::loopMode\n\n"
"@ingroup SFX" )
{ SFXPlayList::LOOP_All, "All",
"Loop over all slots, i.e. jump from last to first slot after all slots have played." },
{ SFXPlayList::LOOP_Single, "Single",
"Loop infinitely over the current slot. Only useful in combination with either states or manual playlist control." },
EndImplementEnumType;
ImplementEnumType( SFXPlayListRandomMode,
"Randomization pattern to apply to playlist slot playback order.\n\n"
"@see SFXPlayList::random\n\n"
"@ingroup SFX" )
{ SFXPlayList::RANDOM_NotRandom, "NotRandom",
"Play slots in sequential order. No randomization." },
{ SFXPlayList::RANDOM_StrictRandom, "StrictRandom",
"Play a strictly random selection of slots.\n\n"
"In this mode, a set of numSlotsToPlay random numbers between 0 and numSlotsToPlay-1 (inclusive), i.e. in the range of valid slot indices, is "
"generated and playlist slots are played back in the order of this list. This allows the same slot to occur multiple times in a list and, "
"consequentially, allows for other slots to not appear at all in a given slot ordering." },
{ SFXPlayList::RANDOM_OrderedRandom, "OrderedRandom",
"Play all slots in the list in a random order.\n\n"
"In this mode, the @c numSlotsToPlay slots from the list with valid tracks assigned are put into a random order and played. This guarantees "
"that each slots is played exactly once albeit at a random position in the total ordering." },
EndImplementEnumType;
ImplementEnumType( SFXPlayListTransitionMode,
"Playlist behavior when transitioning in and out of invididual slots.\n\n"
"Transition behaviors apply when the playback controller starts processing a playlist slot and when it ends processing a slot. Using transition "
"behaviors, playback can be synchronized.\n\n"
"@see SFXPlayList::transitionIn\n\n"
"@see SFXPlayList::transitionOut\n\n"
"@ingroup SFX" )
{ SFXPlayList::TRANSITION_None, "None",
"No transition. Immediately move on to processing the slot or immediately move on to the next slot." },
{ SFXPlayList::TRANSITION_Wait, "Wait",
"Wait for the sound source spawned last by this playlist to finish playing. Then proceed." },
{ SFXPlayList::TRANSITION_WaitAll, "WaitAll",
"Wait for all sound sources currently spawned by the playlist to finish playing. Then proceed." },
{ SFXPlayList::TRANSITION_Stop, "Stop",
"Stop the sound source spawned last by this playlist. Then proceed." },
{ SFXPlayList::TRANSITION_StopAll, "StopAll",
"Stop all sound sources spawned by the playlist. Then proceed." },
EndImplementEnumType;
ImplementEnumType( SFXPlayListReplayMode,
"Behavior when hitting the play stage of a slot that is still playing from a previous cycle.\n\n"
"@see SFXPlayList::replay\n\n"
"@ingroup SFX" )
{ SFXPlayList::REPLAY_IgnorePlaying, "IgnorePlaying",
"Ignore any sources that may already be playing on the slot and just create a new source." },
{ SFXPlayList::REPLAY_RestartPlaying, "RestartPlaying",
"Restart all sources that was last created for the slot." },
{ SFXPlayList::REPLAY_KeepPlaying, "KeepPlaying",
"Keep playing the current source(s) as if the source started last on the slot was created in this cycle. For this, "
"the sources associated with the slot are brought to the top of the play stack." },
{ SFXPlayList::REPLAY_StartNew, "StartNew",
"Stop all sources currently playing on the slot and then create a new source." },
{ SFXPlayList::REPLAY_SkipIfPlaying, "SkipIfPlaying",
"If there are sources already playing on the slot, skip the play stage." },
EndImplementEnumType;
ImplementEnumType( SFXPlayListStateMode,
"Reaction behavior when a state is changed incompatibly on a slot that has already started playing.\n\n"
"@see SFXPlayList::stateMode\n\n"
"@ingroup SFX" )
{ SFXPlayList::STATE_StopInactive, "StopWhenDeactivated",
"Stop the sources playing on the slot when a state changes to a setting that is incompatible with "
"the slot's state setting." },
{ SFXPlayList::STATE_PauseInactive, "PauseWhenDeactivated",
"Pause all sources playing on the slot when a state changes to a setting that is incompatible with the "
"slot's state setting.\n\n"
"When the slot's state is reactivated again, the sources will resume playback." },
{ SFXPlayList::STATE_IgnoreInactive, "IgnoreWhenDeactivated",
"Ignore when a state changes to a setting incompatible with the slot's state setting and just keep "
"playing sources attached to the slot." },
EndImplementEnumType;
//-----------------------------------------------------------------------------
SFXPlayList::SFXPlayList()
: mRandomMode( RANDOM_NotRandom ),
mLoopMode( LOOP_All ),
mNumSlotsToPlay( NUM_SLOTS ),
mTrace( false )
{
}
//-----------------------------------------------------------------------------
void SFXPlayList::initPersistFields()
{
addGroup( "Sound" );
addField( "random", TYPEID< ERandomMode >(), Offset( mRandomMode, SFXPlayList ),
"Slot playback order randomization pattern.\n"
"By setting this field to something other than \"NotRandom\" to order in which slots of the "
"playlist are processed can be changed from sequential to a random pattern. This allows to "
"to create more varied playback patterns.\n"
"Defaults to \"NotRandom\"." );
addField( "loopMode", TYPEID< ELoopMode >(), Offset( mLoopMode, SFXPlayList ),
"Behavior when description has looping enabled.\n"
"The loop mode determines whether the list will loop over a single slot or loop over "
"all the entire list of slots being played.\n\n"
"@see SFXDescription::isLooping" );
addField( "numSlotsToPlay", TypeS32, Offset( mNumSlotsToPlay, SFXPlayList ),
"Number of slots to play.\n"
"Up to a maximum of 16, this field determines the number of slots that are taken from the "
"list for playback. Only slots that have a valid #track assigned will be considered for "
"this." );
addArray( "slots", NUM_SLOTS );
addField( "track", TypeSFXTrackName, Offset( mSlots.mTrack, SFXPlayList ), NUM_SLOTS,
"Track to play in this slot.\n"
"This must be set for the slot to be considered for playback. Other settings for a slot "
"will not take effect except this field is set." );
addField( "replay", TYPEID< EReplayMode >(), Offset( mSlots.mReplayMode, SFXPlayList ), NUM_SLOTS,
"Behavior when an already playing sound is encountered on this slot from a previous cycle.\n"
"Each slot can have an arbitrary number of sounds playing on it from previous cycles. This field determines "
"how SFXController will handle these sources." );
addField( "transitionIn", TYPEID< ETransitionMode >(), Offset( mSlots.mTransitionIn, SFXPlayList ), NUM_SLOTS,
"Behavior when moving into this slot.\n"
"After the delayIn time has expired (if any), this slot determines what the controller "
"will do before actually playing the slot." );
addField( "transitionOut", TYPEID< ETransitionMode >(), Offset( mSlots.mTransitionOut, SFXPlayList ), NUM_SLOTS,
"Behavior when moving out of this slot.\n"
"After the #detailTimeOut has expired (if any), this slot determines what the controller "
"will do before moving on to the next slot." );
addField( "delayTimeIn", TypeF32, Offset( mSlots.mDelayTimeIn.mValue, SFXPlayList ), NUM_SLOTS,
"Seconds to wait after moving into slot before #transitionIn." );
addField( "delayTimeInVariance", TypePoint2F, Offset( mSlots.mDelayTimeIn.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #delayTimeIn.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "delayTimeOut", TypeF32, Offset( mSlots.mDelayTimeOut.mValue, SFXPlayList ), NUM_SLOTS,
"Seconds to wait before moving out of slot after #transitionOut." );
addField( "delayTimeOutVariance", TypePoint2F, Offset( mSlots.mDelayTimeOut.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #delayTimeOut.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "fadeTimeIn", TypeF32, Offset( mSlots.mFadeTimeIn.mValue, SFXPlayList ), NUM_SLOTS,
"Seconds to fade sound in (-1 to use the track's own fadeInTime.)\n"
"@see SFXDescription::fadeTimeIn" );
addField( "fadeTimeInVariance", TypePoint2F, Offset( mSlots.mFadeTimeIn.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #fadeInTime.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "fadeTimeOut", TypeF32, Offset( mSlots.mFadeTimeOut.mValue, SFXPlayList ), NUM_SLOTS,
"Seconds to fade sound out (-1 to use the track's own fadeOutTime.)\n"
"@see SFXDescription::fadeTimeOut" );
addField( "fadeTimeOutVariance", TypePoint2F, Offset( mSlots.mFadeTimeOut.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #fadeOutTime\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "referenceDistance", TypeF32, Offset( mSlots.mMinDistance.mValue, SFXPlayList ), NUM_SLOTS,
"@c referenceDistance to set for 3D sounds in this slot (<1 to use @c referenceDistance of track's own description).\n"
"@see SFXDescription::referenceDistance" );
addField( "referenceDistanceVariance", TypePoint2F, Offset( mSlots.mMinDistance.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #referenceDistance.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "maxDistance", TypeF32, Offset( mSlots.mMaxDistance.mValue, SFXPlayList ), NUM_SLOTS,
"@c maxDistance to apply to 3D sounds in this slot (<1 to use @c maxDistance of track's own description).\n"
"@see SFXDescription::maxDistance" );
addField( "maxDistanceVariance", TypePoint2F, Offset( mSlots.mMaxDistance.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #maxDistance.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "volumeScale", TypeF32, Offset( mSlots.mVolumeScale.mValue, SFXPlayList ), NUM_SLOTS,
"Scale factor to apply to volume of sounds played on this list slot.\n"
"This value will scale the actual volume level set on the track assigned to the slot, i.e. a value of 0.5 will "
"cause the track to play at half-volume." );
addField( "volumeScaleVariance", TypePoint2F, Offset( mSlots.mVolumeScale.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #volumeScale.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "pitchScale", TypeF32, Offset( mSlots.mPitchScale.mValue, SFXPlayList ), NUM_SLOTS,
"Scale factor to apply to pitch of sounds played on this list slot.\n"
"This value will scale the actual pitch set on the track assigned to the slot, i.e. a value of 0.5 will "
"cause the track to play at half its assigned speed." );
addField( "pitchScaleVariance", TypePoint2F, Offset( mSlots.mPitchScale.mVariance, SFXPlayList ), NUM_SLOTS,
"Bounds on randomization of #pitchScale.\n\n"
"@ref SFXPlayList_randomization\n" );
addField( "repeatCount", TypeS32, Offset( mSlots.mRepeatCount, SFXPlayList ), NUM_SLOTS,
"Number of times to loop this slot." );
addField( "state", TypeSFXStateName, Offset( mSlots.mState, SFXPlayList ), NUM_SLOTS,
"State that must be active for this slot to play.\n\n"
"@ref SFXPlayList_states" );
addField( "stateMode", TYPEID< EStateMode >(), Offset( mSlots.mStateMode, SFXPlayList ), NUM_SLOTS,
"Behavior when assigned state is deactivated while slot is playing.\n\n"
"@ref SFXPlayList_states" );
endArray( "slots" );
endGroup( "Sound" );
addGroup( "Debug" );
addField( "trace", TypeBool, Offset( mTrace, SFXPlayList ),
"Enable/disable execution tracing for this playlist (local only).\n"
"If this is true, SFXControllers attached to the list will automatically run in trace mode." );
endGroup( "Debug" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXPlayList::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
validate();
// Resolve SFXTracks and SFXStates on client.
if( !server )
{
for( U32 i = 0; i < NUM_SLOTS; ++ i )
{
if( !sfxResolve( &mSlots.mTrack[ i ], errorStr ) )
return false;
if( !sfxResolve( &mSlots.mState[ i ], errorStr ) )
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
void SFXPlayList::packData( BitStream* stream )
{
Parent::packData( stream );
stream->writeInt( mRandomMode, NUM_RANDOM_MODE_BITS );
stream->writeInt( mLoopMode, NUM_LOOP_MODE_BITS );
stream->writeInt( mNumSlotsToPlay, NUM_SLOTS_TO_PLAY_BITS );
#define FOR_EACH_SLOT \
for( U32 i = 0; i < NUM_SLOTS; ++ i )
FOR_EACH_SLOT stream->writeInt( mSlots.mReplayMode[ i ], NUM_REPLAY_MODE_BITS );
FOR_EACH_SLOT stream->writeInt( mSlots.mTransitionIn[ i ], NUM_TRANSITION_MODE_BITS );
FOR_EACH_SLOT stream->writeInt( mSlots.mTransitionOut[ i ], NUM_TRANSITION_MODE_BITS );
FOR_EACH_SLOT stream->writeInt( mSlots.mStateMode[ i ], NUM_STATE_MODE_BITS );
FOR_EACH_SLOT stream->write( mSlots.mFadeTimeIn.mValue[ i ] );
FOR_EACH_SLOT stream->write( mSlots.mFadeTimeIn.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->write( mSlots.mFadeTimeIn.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->write( mSlots.mFadeTimeOut.mValue[ i ] );
FOR_EACH_SLOT stream->write( mSlots.mFadeTimeOut.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->write( mSlots.mFadeTimeOut.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->write( mSlots.mDelayTimeIn.mValue[ i ] );
FOR_EACH_SLOT stream->write( mSlots.mDelayTimeIn.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->write( mSlots.mDelayTimeIn.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->write( mSlots.mDelayTimeOut.mValue[ i ] );
FOR_EACH_SLOT stream->write( mSlots.mDelayTimeOut.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->write( mSlots.mDelayTimeOut.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->write( mSlots.mVolumeScale.mValue[ i ] );
FOR_EACH_SLOT stream->write( mSlots.mVolumeScale.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->write( mSlots.mVolumeScale.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->write( mSlots.mPitchScale.mValue[ i ] );
FOR_EACH_SLOT stream->write( mSlots.mPitchScale.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->write( mSlots.mPitchScale.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->write( mSlots.mRepeatCount[ i ] );
FOR_EACH_SLOT sfxWrite( stream, mSlots.mState[ i ] );
FOR_EACH_SLOT sfxWrite( stream, mSlots.mTrack[ i ] );
}
//-----------------------------------------------------------------------------
void SFXPlayList::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
mRandomMode = ( ERandomMode ) stream->readInt( NUM_RANDOM_MODE_BITS );
mLoopMode = ( ELoopMode ) stream->readInt( NUM_LOOP_MODE_BITS );
mNumSlotsToPlay = stream->readInt( NUM_SLOTS_TO_PLAY_BITS );
FOR_EACH_SLOT mSlots.mReplayMode[ i ] = ( EReplayMode ) stream->readInt( NUM_REPLAY_MODE_BITS );
FOR_EACH_SLOT mSlots.mTransitionIn[ i ] = ( ETransitionMode ) stream->readInt( NUM_TRANSITION_MODE_BITS );
FOR_EACH_SLOT mSlots.mTransitionOut[ i ] = ( ETransitionMode ) stream->readInt( NUM_TRANSITION_MODE_BITS );
FOR_EACH_SLOT mSlots.mStateMode[ i ] = ( EStateMode ) stream->readInt( NUM_STATE_MODE_BITS );
FOR_EACH_SLOT stream->read( &mSlots.mFadeTimeIn.mValue[ i ] );
FOR_EACH_SLOT stream->read( &mSlots.mFadeTimeIn.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->read( &mSlots.mFadeTimeIn.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->read( &mSlots.mFadeTimeOut.mValue[ i ] );
FOR_EACH_SLOT stream->read( &mSlots.mFadeTimeOut.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->read( &mSlots.mFadeTimeOut.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->read( &mSlots.mDelayTimeIn.mValue[ i ] );
FOR_EACH_SLOT stream->read( &mSlots.mDelayTimeIn.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->read( &mSlots.mDelayTimeIn.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->read( &mSlots.mDelayTimeOut.mValue[ i ] );
FOR_EACH_SLOT stream->read( &mSlots.mDelayTimeOut.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->read( &mSlots.mDelayTimeOut.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->read( &mSlots.mVolumeScale.mValue[ i ] );
FOR_EACH_SLOT stream->read( &mSlots.mVolumeScale.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->read( &mSlots.mVolumeScale.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->read( &mSlots.mPitchScale.mValue[ i ] );
FOR_EACH_SLOT stream->read( &mSlots.mPitchScale.mVariance[ i ][ 0 ] );
FOR_EACH_SLOT stream->read( &mSlots.mPitchScale.mVariance[ i ][ 1 ] );
FOR_EACH_SLOT stream->read( &mSlots.mRepeatCount[ i ] );
FOR_EACH_SLOT sfxRead( stream, &mSlots.mState[ i ] );
FOR_EACH_SLOT sfxRead( stream, &mSlots.mTrack[ i ] );
#undef FOR_EACH_SLOT
}
//-----------------------------------------------------------------------------
void SFXPlayList::inspectPostApply()
{
Parent::inspectPostApply();
validate();
}
//-----------------------------------------------------------------------------
void SFXPlayList::validate()
{
if( mNumSlotsToPlay > NUM_SLOTS )
mNumSlotsToPlay = NUM_SLOTS;
mSlots.mFadeTimeIn.validate();
mSlots.mFadeTimeOut.validate();
mSlots.mDelayTimeIn.validate();
mSlots.mDelayTimeOut.validate();
mSlots.mVolumeScale.validate();
mSlots.mPitchScale.validate();
}

View file

@ -0,0 +1,356 @@
//-----------------------------------------------------------------------------
// 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 _SFXPLAYLIST_H_
#define _SFXPLAYLIST_H_
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _SFXTRACK_H_
#include "sfx/sfxTrack.h"
#endif
class SFXState;
/// A playback list of SFXTracks.
///
/// Note that since SFXPlayLists are SFXTracks, play lists can be cascaded.
///
/// Play lists are comprised of a sequence of slots. Each slot can be assigned
/// a track (SFXProfile or another SFXPlayList) as well as a number of options
/// that determine how the particular slot should behave.
///
/// In addition to playing a track, each slot can do an arbitrary combination
/// of the following operations:
///
/// - Wait: wait for the previous or all sources to stop playing
/// - Stop: stop the previous or all sources from playing
/// - Delay: wait some (optionally randomized) amount of time
/// - Shift Pitch: scale pitch when playing by optionally randomized amount
/// - Shift Volume: scale volume when playing by optionally randomized amount
/// - Fade: perform volume fade-in/out
/// - Distance: only start playing track when listener is within a certain range
/// - Loop: loop a set number of times
/// - State: play only when the given SFXState is active; transitions out of
/// slot when state is deactivated
///
/// The order in which slots are played is either sequential (NotRandom),
/// or a random selection (StrictRandom), or a random ordering (OrderedRandom).
///
/// Additionally, the list may be looped over in entirety (All) or looped
/// on single slots (useful for either manual playback control or lists that
/// exclusively use states).
///
/// Be aware that playlists are affected by SFXDescriptions the same way that an
/// SFXProfile is, i.e. fades, looping, 3D sound, etc. all take effect.
///
/// @note Playlists offer a lot of control but unfortunately they also make it
/// pretty easy at the moment to shoot yourself in the foot.
///
class SFXPlayList : public SFXTrack
{
public:
typedef SFXTrack Parent;
enum
{
/// Number of slots in a playlist.
///
/// @note To have longer playlists, simply cascade playlists and use
/// wait behaviors.
NUM_SLOTS = 16,
NUM_TRANSITION_MODE_BITS = 3,
NUM_LOOP_MODE_BITS = 1,
NUM_RANDOM_MODE_BITS = 2,
NUM_SLOTS_TO_PLAY_BITS = 5,
NUM_REPLAY_MODE_BITS = 3,
NUM_STATE_MODE_BITS = 2,
};
/// Behavior when description is set to loop.
enum ELoopMode
{
/// Start over after completing a cycle.
LOOP_All,
/// Loop a single slot over and over.
///
/// @note This behavior is only useful in combination with states or manual
/// playback control. To just loop over a slot for some time, set its loop
/// count instead.
LOOP_Single,
};
/// Random playback mode.
enum ERandomMode
{
/// No randomization of playback order.
RANDOM_NotRandom,
/// Playback order that jumps to a random slot after completing
/// a given slot. The slot being jumped to, however, may be any
/// slot in the list including the slot that has just played.
///
/// @note In order to ensure cycles are always finite, this mode will
/// also just do NUM_SLOTS number of transitions and then stop the
/// current cycle whether all slots have played or not. Otherwise,
/// it would be dependent on the random number sequence generated when
/// and whether at all a given cycle finishes.
RANDOM_StrictRandom,
/// Before a cycle over the playlist starts, a random total ordering of
/// the slots is established and then played. No slot will be played
/// twice in a single cycle.
RANDOM_OrderedRandom,
};
/// Transitioning behavior when moving in and out of slots.
enum ETransitionMode
{
/// No specific behavior for transitioning between slots.
TRANSITION_None,
/// Wait for single slot to stop playing. If transitioning into slot,
/// this is the slot being transitioned from. If transitioning out of slot,
/// this is the current slot.
TRANSITION_Wait,
/// Wait for all slots to stop playing.
TRANSITION_WaitAll,
/// Stop single slot before proceeding. If transitioning into slot, this
/// is the slot being transitioned from. If transitioning out of slot,
/// this is the current slot.
TRANSITION_Stop,
/// Stop all playing slots before proceeding.
TRANSITION_StopAll,
};
/// Behavior when hitting play() on a slot that is still playing from
/// a previous cycle.
enum EReplayMode
{
/// Do not check if a source is already playing on the slot.
REPLAY_IgnorePlaying,
/// Stop the currently playing source and start playing it from the
/// beginning.
REPLAY_RestartPlaying,
/// Move the currently playing source to the top of the stack and pretend
/// it was started by this cycle.
///
/// When using STATE_PauseInactive, it is usally best to also use REPLAY_KeepPlaying
/// as otherwise a new source will be spawned when the state becomes active again.
///
/// @note When the currently playing source is paused, KeepPlaying will
/// resume playback.
REPLAY_KeepPlaying,
/// Let the old source play and start a new one on the same slot.
REPLAY_StartNew,
/// If there is a source currently playing on this slot, skip the play() stage.
REPLAY_SkipIfPlaying,
};
/// State-reaction behavior of slot once a source has started playing.
enum EStateMode
{
/// Stop and remove source when state becomes inactive.
STATE_StopInactive,
/// Pause source when state becomes inactive and resume playback
/// when state becomes active again.
STATE_PauseInactive,
/// Once a source has started to play, it will not be stopped due to
/// state changes. A source will, however, still be prevented from starting
/// to play when its assigned state is not active.
STATE_IgnoreInactive,
};
// All structures here are laid out as structures of arrays instead of arrays of structures
// to allow them to be used as fixed-size TorqueScript arrays.
struct VariantFloat : SFXVariantFloat< NUM_SLOTS >
{
VariantFloat()
{
dMemset( mValue, 0, sizeof( mValue ) );
dMemset( mVariance, 0, sizeof( mVariance ) );
}
};
/// Settings for the playback slots.
struct SlotData
{
/// Behavior when a sound is already playing on a slot from a previous cycle.
EReplayMode mReplayMode[ NUM_SLOTS ];
/// Behavior when transitioning into the slot.
ETransitionMode mTransitionIn[ NUM_SLOTS ];
/// Behavior when transitioning out of the slot.
ETransitionMode mTransitionOut[ NUM_SLOTS ];
/// Seconds to fade sound in. -1 to leave at default.
VariantFloat mFadeTimeIn;
/// Seconds to fade sound out. -1 to leave at default.
VariantFloat mFadeTimeOut;
/// Time to delay before mTransitionIn.
VariantFloat mDelayTimeIn;
/// Time to delay before mTransitionOut.
VariantFloat mDelayTimeOut;
/// Volume scale factor.
VariantFloat mVolumeScale;
/// Pitch scale factor.
VariantFloat mPitchScale;
/// Min distance for 3D sounds.
VariantFloat mMinDistance;
/// Max distance for 3D sounds.
VariantFloat mMaxDistance;
/// Number of times to loop over this slot.
/// @note Each iteration will do a full transition as if proceeding
/// to a different slot.
U32 mRepeatCount[ NUM_SLOTS ];
/// State restriction for this slot. Slot will only play when the given
/// state is active and will be automatically transitioned from
/// if the state becomes inactive.
SFXState* mState[ NUM_SLOTS ];
/// Bahavior when state of this slot is deactivated and the slot's track
/// is playing.
EStateMode mStateMode[ NUM_SLOTS ];
/// Track to play in this slot.
SFXTrack* mTrack[ NUM_SLOTS ];
SlotData()
{
dMemset( mReplayMode, 0, sizeof( mReplayMode ) );
dMemset( mTransitionIn, 0, sizeof( mTransitionIn ) );
dMemset( mTransitionOut, 0, sizeof( mTransitionOut ) );
dMemset( mRepeatCount, 0, sizeof( mRepeatCount ) );
dMemset( mState, 0, sizeof( mState ) );
dMemset( mTrack, 0, sizeof( mTrack ) );
dMemset( mStateMode, 0, sizeof( mStateMode ) );
for( U32 i = 0; i < NUM_SLOTS; ++ i )
{
mTransitionOut[ i ] = TRANSITION_Wait;
mVolumeScale.mValue[ i ] = 1.f;
mPitchScale.mValue[ i ] = 1.f;
mFadeTimeIn.mValue[ i ] = -1.f; // Don't touch by default.
mFadeTimeOut.mValue[ i ] = -1.f; // Don't touch by default.
mMinDistance.mValue[ i ] = -1.f; // Don't touch by default.
mMaxDistance.mValue[ i ] = -1.f; // Don't touch by default.
}
}
};
protected:
/// Trace interpreter execution. This field is not networked.
bool mTrace;
/// Select slots at random.
ERandomMode mRandomMode;
/// Loop over slots in this list.
ELoopMode mLoopMode;
/// Number of slots to play from list. This can be used, for example,
/// to create a list of tracks where only a single track is selected and
/// played for each cycle.
U32 mNumSlotsToPlay;
/// Data for each of the playlist slots.
SlotData mSlots;
public:
SFXPlayList();
/// Make all settings conform to constraints.
void validate();
/// Return true if execution tracing is enabled on this list.
bool trace() const { return mTrace; }
/// Return the number of slots to play from this list in a single cycle.
U32 getNumSlotsToPlay() const { return mNumSlotsToPlay; }
/// Return the slot order randomization behavior.
ERandomMode getRandomMode() const { return mRandomMode; }
/// Return the loop mode (only relevant if this is a looped playlist).
ELoopMode getLoopMode() const { return mLoopMode; }
/// Return the total number of slots in the list.
U32 getNumSlots() const { return NUM_SLOTS; }
/// Return the slot data for this list.
const SlotData& getSlots() const { return mSlots; }
DECLARE_CONOBJECT( SFXPlayList );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "A playback list of SFXProfiles or nested SFXPlayLists." );
// SimDataBlock.
virtual bool preload( bool server, String& errorStr );
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
virtual void inspectPostApply();
static void initPersistFields();
};
typedef SFXPlayList::ELoopMode SFXPlayListLoopMode;
typedef SFXPlayList::ETransitionMode SFXPlayListTransitionMode;
typedef SFXPlayList::EStateMode SFXPlayListStateMode;
typedef SFXPlayList::ERandomMode SFXPlayListRandomMode;
typedef SFXPlayList::EReplayMode SFXPlayListReplayMode;
DefineEnumType( SFXPlayListLoopMode );
DefineEnumType( SFXPlayListTransitionMode );
DefineEnumType( SFXPlayListStateMode );
DefineEnumType( SFXPlayListRandomMode );
DefineEnumType( SFXPlayListReplayMode );
#endif // _SFXPLAYLIST_H_

View file

@ -0,0 +1,374 @@
//-----------------------------------------------------------------------------
// 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 "platform/platform.h"
#include "sfx/sfxProfile.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxStream.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "core/resourceManager.h"
#include "console/engineAPI.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXProfile );
ConsoleDocClass( SFXProfile,
"@brief Encapsulates a single sound file for playback by the sound system.\n\n"
"SFXProfile combines a sound description (SFXDescription) with a sound file such that it can be played "
"by the sound system. To be able to play a sound file, the sound system will always require a profile "
"for it to be created. However, several of the SFX functions (sfxPlayOnce(), sfxCreateSource()) perform "
"this creation internally for convenience using temporary profile objects.\n\n"
"Sound files can be in either OGG or WAV format. However, extended format support is available when using FMOD. "
"See @ref SFX_formats.\n\n"
"@section SFXProfile_loading Profile Loading\n\n"
"By default, the sound data referenced by a profile will be loaded when the profile is first played and the "
"data then kept until either the profile is deleted or until the sound device on which the sound data is held "
"is deleted.\n\n"
"This initial loading my incur a small delay when the sound is first played. To avoid this, a profile may be "
"expicitly set to load its sound data immediately when the profile is added to the system. This is done by "
"setting the #preload property to true.\n\n"
"@note Sounds using streamed playback (SFXDescription::isStreaming) cannot be preloaded and will thus "
"ignore the #preload flag.\n\n"
"@tsexample\n"
"datablock SFXProfile( Shore01Snd )\n"
"{\n"
" fileName = \"art/sound/Lakeshore_mono_01\";\n"
" description = Shore01Looping3d;\n"
" preload = true;\n"
"};\n"
"@endtsexample\n\n"
"@ingroup SFX\n"
"@ingroup Datablocks\n"
);
//-----------------------------------------------------------------------------
SFXProfile::SFXProfile()
: mPreload( false )
{
}
//-----------------------------------------------------------------------------
SFXProfile::SFXProfile( SFXDescription* desc, const String& filename, bool preload )
: Parent( desc ),
mFilename( filename ),
mPreload( preload )
{
}
//-----------------------------------------------------------------------------
SFXProfile::~SFXProfile()
{
}
//-----------------------------------------------------------------------------
void SFXProfile::initPersistFields()
{
addGroup( "Sound" );
addField( "filename", TypeStringFilename, Offset( mFilename, SFXProfile ),
"%Path to the sound file.\n"
"If the extension is left out, it will be inferred by the sound system. This allows to "
"easily switch the sound format without having to go through the profiles and change the "
"filenames there, too.\n" );
addField( "preload", TypeBool, Offset( mPreload, SFXProfile ),
"Whether to preload sound data when the profile is added to system.\n"
"@note This flag is ignored by streamed sounds.\n\n"
"@ref SFXProfile_loading" );
endGroup( "Sound" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXProfile::onAdd()
{
if( !Parent::onAdd() )
return false;
// If we're a streaming profile we don't preload
// or need device events.
if( SFX && !mDescription->mIsStreaming )
{
// If preload is enabled we load the resource
// and device buffer now to avoid a delay on
// first playback.
if( mPreload && !_preloadBuffer() )
Con::errorf( "SFXProfile(%s)::onAdd: The preload failed!", getName() );
}
_registerSignals();
return true;
}
//-----------------------------------------------------------------------------
void SFXProfile::onRemove()
{
_unregisterSignals();
Parent::onRemove();
}
//-----------------------------------------------------------------------------
bool SFXProfile::preload( bool server, String &errorStr )
{
if ( !Parent::preload( server, errorStr ) )
return false;
// TODO: Investigate how NetConnection::filesWereDownloaded()
// effects the system.
// Validate the datablock... has nothing to do with mPreload.
if( !server &&
NetConnection::filesWereDownloaded() &&
( mFilename.isEmpty() || !SFXResource::exists( mFilename ) ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
void SFXProfile::packData(BitStream* stream)
{
Parent::packData( stream );
char buffer[256];
if ( mFilename.isEmpty() )
buffer[0] = 0;
else
dStrncpy( buffer, mFilename.c_str(), 256 );
stream->writeString( buffer );
stream->writeFlag( mPreload );
}
//-----------------------------------------------------------------------------
void SFXProfile::unpackData(BitStream* stream)
{
Parent::unpackData( stream );
char buffer[256];
stream->readString( buffer );
mFilename = buffer;
mPreload = stream->readFlag();
}
//-----------------------------------------------------------------------------
bool SFXProfile::isLooping() const
{
return getDescription()->mIsLooping;
}
//-----------------------------------------------------------------------------
void SFXProfile::_registerSignals()
{
SFX->getEventSignal().notify( this, &SFXProfile::_onDeviceEvent );
ResourceManager::get().getChangedSignal().notify( this, &SFXProfile::_onResourceChanged );
}
//-----------------------------------------------------------------------------
void SFXProfile::_unregisterSignals()
{
ResourceManager::get().getChangedSignal().remove( this, &SFXProfile::_onResourceChanged );
if( SFX )
SFX->getEventSignal().remove( this, &SFXProfile::_onDeviceEvent );
}
//-----------------------------------------------------------------------------
void SFXProfile::_onDeviceEvent( SFXSystemEventType evt )
{
switch( evt )
{
case SFXSystemEvent_CreateDevice:
{
if( mPreload && !mDescription->mIsStreaming && !_preloadBuffer() )
Con::errorf( "SFXProfile::_onDeviceEvent: The preload failed! %s", getName() );
break;
}
default:
break;
}
}
//-----------------------------------------------------------------------------
void SFXProfile::_onResourceChanged( const Torque::Path& path )
{
if( path != Path( mFilename ) )
return;
// Let go of the old resource and buffer.
mResource = NULL;
mBuffer = NULL;
// Load the new resource.
getResource();
if( mPreload && !mDescription->mIsStreaming )
{
if( !_preloadBuffer() )
Con::errorf( "SFXProfile::_onResourceChanged() - failed to preload '%s'", mFilename.c_str() );
}
mChangedSignal.trigger( this );
}
//-----------------------------------------------------------------------------
bool SFXProfile::_preloadBuffer()
{
AssertFatal( !mDescription->mIsStreaming, "SFXProfile::_preloadBuffer() - must not be called for streaming profiles" );
mBuffer = _createBuffer();
return ( !mBuffer.isNull() );
}
//-----------------------------------------------------------------------------
Resource<SFXResource>& SFXProfile::getResource()
{
if( !mResource && !mFilename.isEmpty() )
mResource = SFXResource::load( mFilename );
return mResource;
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXProfile::getBuffer()
{
if ( mDescription->mIsStreaming )
{
// Streaming requires unique buffers per
// source, so this creates a new buffer.
if ( SFX )
return _createBuffer();
return NULL;
}
if ( mBuffer.isNull() )
_preloadBuffer();
return mBuffer;
}
//-----------------------------------------------------------------------------
SFXBuffer* SFXProfile::_createBuffer()
{
SFXBuffer* buffer = 0;
// Try to create through SFXDevie.
if( !mFilename.isEmpty() && SFX )
{
buffer = SFX->_createBuffer( mFilename, mDescription );
if( buffer )
{
#ifdef TORQUE_DEBUG
const SFXFormat& format = buffer->getFormat();
Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)",
mDescription->mIsStreaming ? "Streaming" : "Loaded", mFilename.c_str(),
format.getChannels(),
format.getSamplesPerSecond() / 1000,
F32( buffer->getDuration() ) / 1000.0f,
format.getDataLength( buffer->getDuration() ) / 1024 );
#endif
}
}
// If that failed, load through SFXResource.
if( !buffer )
{
Resource< SFXResource >& resource = getResource();
if( resource != NULL && SFX )
{
#ifdef TORQUE_DEBUG
const SFXFormat& format = resource->getFormat();
Con::printf( "%s SFX: %s (%i channels, %i kHz, %.02f sec, %i kb)",
mDescription->mIsStreaming ? "Streaming" : "Loading", resource->getFileName().c_str(),
format.getChannels(),
format.getSamplesPerSecond() / 1000,
F32( resource->getDuration() ) / 1000.0f,
format.getDataLength( resource->getDuration() ) / 1024 );
#endif
ThreadSafeRef< SFXStream > sfxStream = resource->openStream();
buffer = SFX->_createBuffer( sfxStream, mDescription );
}
}
return buffer;
}
//-----------------------------------------------------------------------------
U32 SFXProfile::getSoundDuration()
{
Resource< SFXResource >& resource = getResource();
if( resource != NULL )
return mResource->getDuration();
else
return 0;
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXProfile, getSoundDuration, F32, (),,
"Return the length of the sound data in seconds.\n\n"
"@return The length of the sound data in seconds or 0 if the sound referenced by the profile could not be found." )
{
return ( F32 ) object->getSoundDuration() * 0.001f;
}

View file

@ -0,0 +1,181 @@
//-----------------------------------------------------------------------------
// 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 _SFXPROFILE_H_
#define _SFXPROFILE_H_
#ifndef _SFXTRACK_H_
#include "sfx/sfxTrack.h"
#endif
#ifndef _SFXRESOURCE_H_
#include "sfx/sfxResource.h"
#endif
#ifndef _SFXBUFFER_H_
#include "sfx/sfxBuffer.h"
#endif
#ifndef _SFXSYSTEM_H_
#include "sfx/sfxSystem.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
class SFXDescription;
/// The SFXProfile is used to define a sound for playback.
///
/// An SFXProfile will first try to load its file directly through the SFXDevice.
/// Only if this fails (which is the case for most SFXDevices as these do not
/// implement their own custom sound format loading), the file is loaded through
/// SFXResource.
///
/// A few tips:
///
/// Make sure each of the defined SFXProfile's fileName doesn't specify
/// an extension. An extension does not need to be specified and by not
/// explicitly saying .ogg or .wav it will allow you to change from one
/// format to the other without having to change the scripts.
///
/// Make sure that server SFXProfiles are defined with the datablock
/// keyword, and that client SFXProfiles are defined with the 'new'
/// keyword.
///
/// Make sure SFXDescriptions exist for your SFXProfiles. Also make sure
/// that SFXDescriptions are defined BEFORE SFXProfiles. This is especially
/// important if your SFXProfiles are located in different files than your
/// SFXDescriptions. In this case, make sure the files containing SFXDescriptions
/// are exec'd before the files containing the SFXProfiles.
///
/// @note Live asset update will not work with files loaded directly through
/// the SFXDevice.
class SFXProfile : public SFXTrack
{
public:
friend class SFXEmitter; // For access to mFilename
typedef SFXTrack Parent;
typedef Signal< void( SFXProfile* ) > ChangedSignal;
protected:
/// The sound data.
/// @note ATM only valid if loaded through SFX's loading system rather than
/// through the SFXDevice's loading system.
Resource< SFXResource > mResource;
/// The sound filename. If no extension is specified
/// the system will try .wav first then other formats.
String mFilename;
/// If true the sound data will be loaded from
/// disk and possibly cached with the active
/// device before the first call for playback.
bool mPreload;
/// The device specific data buffer.
/// This is only used if for non-streaming sounds.
StrongWeakRefPtr< SFXBuffer > mBuffer;
///
ChangedSignal mChangedSignal;
/// Called when the buffer needs to be preloaded.
bool _preloadBuffer();
/// Callback for device events.
void _onDeviceEvent( SFXSystemEventType evt );
///
SFXBuffer* _createBuffer();
///
void _onResourceChanged( const Torque::Path& path );
///
void _registerSignals();
///
void _unregisterSignals();
public:
/// This is only here to allow DECLARE_CONOBJECT
/// to create us from script. You shouldn't use
/// this constructor from C++.
explicit SFXProfile();
/// The constructor.
SFXProfile( SFXDescription* desc,
const String& filename = String(),
bool preload = false );
/// The destructor.
virtual ~SFXProfile();
DECLARE_CONOBJECT( SFXProfile );
static void initPersistFields();
// SFXTrack.
virtual bool isLooping() const;
// SimObject
bool onAdd();
void onRemove();
void packData( BitStream* stream );
void unpackData( BitStream* stream );
/// Returns the sound filename.
const String& getSoundFileName() const { return mFilename; }
/// @note This has nothing to do with mPreload.
/// @see SimDataBlock::preload
bool preload( bool server, String &errorStr );
/// Returns the sound resource loading it from
/// disk if it hasn't been preloaded.
///
/// @note May be NULL if file is loaded directly through SFXDevice.
Resource<SFXResource>& getResource();
/// Returns the device specific buffer for this for this
/// sound. If it hasn't been preloaded it will be loaded
/// at this time.
///
/// If this is a streaming profile then the buffer
/// returned must be deleted by the caller.
SFXBuffer* getBuffer();
/// Gets the sound duration in milliseconds or
/// returns 0 if the resource was not found.
U32 getSoundDuration();
///
ChangedSignal& getChangedSignal() { return mChangedSignal; }
};
#endif // _SFXPROFILE_H_

View file

@ -0,0 +1,95 @@
//-----------------------------------------------------------------------------
// 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 "core/strings/stringFunctions.h"
#include "sfx/sfxProvider.h"
SFXProvider* SFXProvider::smProviders = NULL;
Vector<SFXProvider*> SFXProvider::sAllProviders( __FILE__, __LINE__ );
SFXProvider* SFXProvider::findProvider( String providerName )
{
if( providerName.isEmpty() )
return NULL;
SFXProvider* curr = smProviders;
for ( ; curr != NULL; curr = curr->mNextProvider )
{
if( curr->getName().equal( providerName, String::NoCase ) )
return curr;
}
return NULL;
}
void SFXProvider::regProvider( SFXProvider* provider )
{
AssertFatal( provider, "Got null provider!" );
AssertFatal( findProvider( provider->getName() ) == NULL, "Can't register provider twice!" );
AssertFatal( provider->mNextProvider == NULL, "Can't register provider twice!" );
SFXProvider* oldHead = smProviders;
smProviders = provider;
provider->mNextProvider = oldHead;
}
SFXProvider::SFXProvider( const String& name )
: mName( name ),
mNextProvider( NULL )
{
VECTOR_SET_ASSOCIATION( mDeviceInfo );
sAllProviders.push_back( this );
}
void SFXProvider::initializeAllProviders()
{
for (U32 i = 0; i < sAllProviders.size(); i++)
sAllProviders[i]->init();
}
SFXProvider::~SFXProvider()
{
SFXDeviceInfoVector::iterator iter = mDeviceInfo.begin();
for ( ; iter != mDeviceInfo.end(); iter++ )
delete *iter;
}
SFXDeviceInfo* SFXProvider::_findDeviceInfo( const String& deviceName )
{
SFXDeviceInfoVector::iterator iter = mDeviceInfo.begin();
for ( ; iter != mDeviceInfo.end(); iter++ )
{
if( deviceName.equal( ( *iter )->name, String::NoCase ) )
return *iter;
}
// If not found and deviceName is empty,
// return first (default) device.
if( deviceName.isEmpty() && mDeviceInfo.size() > 0 )
return mDeviceInfo[ 0 ];
return NULL;
}

View file

@ -0,0 +1,124 @@
//-----------------------------------------------------------------------------
// 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 _SFXPROVIDER_H_
#define _SFXPROVIDER_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class SFXDevice;
struct SFXDeviceInfo
{
String driver;
String name;
bool hasHardware;
S32 maxBuffers;
virtual ~SFXDeviceInfo() {}
};
typedef Vector<SFXDeviceInfo*> SFXDeviceInfoVector;
class SFXProvider
{
friend class SFXSystem;
private:
/// The head of the linked list of avalible providers.
static SFXProvider* smProviders;
/// The next provider in the linked list of available providers.
SFXProvider* mNextProvider;
/// The provider name which is passed by the concrete provider
/// class to the SFXProvider constructor.
String mName;
static Vector<SFXProvider*> sAllProviders;
protected:
/// The array of avaIlable devices from this provider. The
/// concrete provider class will fill this on construction.
SFXDeviceInfoVector mDeviceInfo;
/// This registers the provider to the available provider list. It should be called
/// for providers that are properly initialized and available for device enumeration and creation.
/// the add and registration process is 2 steps to avoid issues when TGEA is used as a shared library (specifically on Windows)
static void regProvider( SFXProvider* provider );
virtual void init() = 0;
SFXProvider( const String& name );
~SFXProvider();
/// Look up the SFXDeviceInfo for the given device in mDeviceInfo.
/// Return default device (first in list) if no other device matches (or null if device list is empty).
SFXDeviceInfo* _findDeviceInfo( const String& deviceName );
/// This is called from SFXSystem to create a new device. Must be implemented
/// by all contrete provider classes.
///
/// @param deviceName The case sensitive name of the device or NULL to create the
// default device.
/// @param useHardware Toggles the use of hardware processing when available.
/// @param maxBuffers The maximum buffers for this device to use or -1
/// for the device to pick a reasonable default for that device.
///
/// @return Returns the created device or NULL for failure.
///
virtual SFXDevice* createDevice( const String& deviceName, bool useHardware, S32 maxBuffers ) = 0;
public:
/// Returns a specific provider by searching the provider list
/// for the first provider with the case sensitive name.
static SFXProvider* findProvider( String providerName );
/// Returns the first provider in the provider list. Use
/// getNextProvider() to iterate over list.
static SFXProvider* getFirstProvider() { return smProviders; }
/// Returns the next provider in the provider list or NULL
/// when the end of the list is reached.
SFXProvider* getNextProvider() const { return mNextProvider; }
/// The case sensitive name of this provider.
const String& getName() const { return mName; }
/// Returns a read only vector with device information for
/// all creatable devices available from this provider.
const SFXDeviceInfoVector& getDeviceInfo() const { return mDeviceInfo; }
static void initializeAllProviders();
};
#endif // _SFXPROVIDER_H_

View file

@ -0,0 +1,82 @@
//-----------------------------------------------------------------------------
// 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/sfxResource.h"
#include "sfx/sfxFileStream.h"
#include "core/util/fourcc.h"
#include "core/resourceManager.h"
// Ugly workaround to keep the constructor protected.
struct SFXResource::_NewHelper
{
static SFXResource* New( String fileName, const ThreadSafeRef< SFXStream >& stream )
{
return new SFXResource( fileName, stream );
}
};
template<>
void* Resource< SFXResource >::create( const Torque::Path& path )
{
String fullPath = path.getFullPath();
// Try to open the stream.
ThreadSafeRef< SFXStream > stream = SFXFileStream::create( fullPath );
if( !stream )
return NULL;
// We have a valid stream... create the resource.
SFXResource* res = SFXResource::_NewHelper::New( fullPath, stream );
return res;
}
template<>
ResourceBase::Signature Resource< SFXResource >::signature()
{
return MakeFourCC( 's', 'f', 'x', 'r' );
}
Resource< SFXResource > SFXResource::load( String filename )
{
return ResourceManager::get().load( filename );
}
SFXResource::SFXResource( String fileName, SFXStream *stream )
: mFileName( fileName ),
mFormat( stream->getFormat() ),
mDuration( stream->getDuration() )
{
}
bool SFXResource::exists( String filename )
{
return SFXFileStream::exists( filename );
}
SFXStream* SFXResource::openStream()
{
return SFXFileStream::create( mFileName );
}

View file

@ -0,0 +1,114 @@
//-----------------------------------------------------------------------------
// 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 _SFXRESOURCE_H_
#define _SFXRESOURCE_H_
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
class SFXStream;
/// This is the base class for all sound file resources including
/// streamed sound files. It acts much like an always in-core
/// header to the actual sound data which is read through an SFXStream.
///
/// The first step occurs at ResourceManager::load() time at which
/// only the header information, the format, size frequency, and
/// looping flag, are loaded from the sound file. This provides
/// just the nessasary information to simulate sound playback for
/// sounds playing just out of the users hearing range.
///
/// The second step loads the actual sound data or begins filling
/// the stream buffer. This is triggered by a call to openStream().
/// SFXProfile, for example, does this when mPreload is enabled.
///
class SFXResource
{
public:
typedef void Parent;
protected:
/// The constructor is protected.
/// @see SFXResource::load()
SFXResource();
/// Path to the sound file.
String mFileName;
/// The format of the sample data.
SFXFormat mFormat;
/// The length of the sample in milliseconds.
U32 mDuration;
/// Construct a resource instance for the given file. Format and duration
/// are read from the given stream.
SFXResource( String fileName, SFXStream* stream );
public:
/// The destructor.
virtual ~SFXResource() {}
/// This is a helper function used by SFXProfile for load
/// a sound resource. It takes care of trying different
/// types for extension-less filenames.
///
/// @param filename The sound file path with or without extension.
///
static Resource< SFXResource > load( String filename );
/// A helper function which returns true if the
/// sound resource exists.
///
/// @param filename The sound file path with or without extension.
///
static bool exists( String filename );
/// Return the path to the sound file.
const String& getFileName() { return mFileName; }
/// Returns the total playback time milliseconds.
U32 getDuration() const { return mDuration; }
/// The format of the data in the resource.
const SFXFormat& getFormat() const { return mFormat; }
/// Open a stream for reading the resource's sample data.
SFXStream* openStream();
// Internal.
struct _NewHelper;
friend struct _NewHelper;
};
#endif // _SFXRESOURCE_H_

View file

@ -0,0 +1,708 @@
//-----------------------------------------------------------------------------
// 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/sfxSound.h"
#include "sfx/sfxDevice.h"
#include "sfx/sfxVoice.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxBuffer.h"
#include "sfx/sfxStream.h"
#include "sfx/sfxDescription.h"
#include "core/util/safeDelete.h"
#include "console/engineAPI.h"
//#define DEBUG_SPEW
IMPLEMENT_CONOBJECT( SFXSound );
ConsoleDocClass( SFXSound,
"@brief A sound controller that directly plays a single sound file.\n\n"
"When playing individual audio files, SFXSounds are implicitly created by the sound system.\n\n"
"Each sound source has an associated play cursor that can be queried and explicitly positioned "
"by the user. The cursor is a floating-point value measured in seconds.\n\n"
"For streamed sources, playback may not be continuous in case the streaming queue is interrupted.\n\n"
"@note This class cannot be instantiated directly by the user but rather is implicitly created by the sound "
"system when sfxCreateSource() or sfxPlayOnce() is called on a SFXProfile instance.\n\n"
"@section SFXSound_virtualization Sounds and Voices\n\n"
"To actually emit an audible signal, a sound must allocate a resource on the sound device through "
"which the sound data is being played back. This resource is called 'voice'.\n\n"
"As with other types of resources, the availability of these resources may be restricted, i.e. a given "
"sound device will usually only support a fixed number of voices that are playing at the same time. Since, "
"however, there may be arbitrary many SFXSounds instantiated and playing at the same time, this needs to be "
"solved. \n\n"
"@see SFXDescription::priority\n"
"@ingroup SFX"
);
//-----------------------------------------------------------------------------
SFXSound::SFXSound()
: mVoice( NULL )
{
// NOTE: This should never be used directly
// and is only here to satisfy satisfy the
// construction needs of IMPLEMENT_CONOBJECT.
}
//-----------------------------------------------------------------------------
SFXSound::SFXSound( SFXProfile *profile, SFXDescription* desc )
: Parent( profile, desc ),
mVoice( NULL )
{
}
//-----------------------------------------------------------------------------
SFXSound* SFXSound::_create( SFXDevice *device, SFXProfile *profile )
{
AssertFatal( profile, "SFXSound::_create() - Got a null profile!" );
SFXDescription* desc = profile->getDescription();
if ( !desc )
{
Con::errorf( "SFXSound::_create() - Profile has null description!" );
return NULL;
}
// Create the sound and register it.
SFXSound* sound = new SFXSound( profile, desc );
sound->registerObject();
// Initialize the buffer.
SFXBuffer* buffer = profile->getBuffer();
if( !buffer )
{
sound->deleteObject();
Con::errorf( "SFXSound::_create() - Could not create device buffer!" );
return NULL;
}
sound->_setBuffer( buffer );
// The sound is a console object... register it.
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSound] new sound '%i' with profile '%i' (\"%s\")",
sound->getId(), profile->getId(), profile->getName() );
#endif
// Hook up reloading.
profile->getChangedSignal().notify( sound, &SFXSound::_onProfileChanged );
return sound;
}
//-----------------------------------------------------------------------------
SFXSound* SFXSound::_create( SFXDevice* device,
const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description )
{
AssertFatal( stream.ptr() != NULL, "SFXSound::_create() - Got a null stream!" );
AssertFatal( description, "SFXSound::_create() - Got a null description!" );
// Create the source and register it.
SFXSound* source = new SFXSound( NULL, description );
source->registerObject();
// Create the buffer.
SFXBuffer* buffer = SFX->_createBuffer( stream, description );
if( !buffer )
{
source->deleteObject();
Con::errorf( "SFXSound::_create() - Could not create device buffer!" );
return NULL;
}
source->_setBuffer( buffer );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSound] new source '%i' for stream", source->getId() );
#endif
return source;
}
//-----------------------------------------------------------------------------
void SFXSound::_reloadBuffer()
{
SFXProfile* profile = getProfile();
if( profile != NULL && _releaseVoice() )
{
SFXBuffer* buffer = profile->getBuffer();
if( !buffer )
{
Con::errorf( "SFXSound::_reloadBuffer() - Could not create device buffer!" );
return;
}
_setBuffer( buffer );
if( getLastStatus() == SFXStatusPlaying )
SFX->_assignVoice( this );
}
}
//-----------------------------------------------------------------------------
void SFXSound::_setBuffer( SFXBuffer* buffer )
{
mBuffer = buffer;
// There is no telling when the device will be
// destroyed and the buffers deleted.
//
// By caching the duration now we can allow sources
// to continue virtual playback until the device
// is restored.
mDuration = mBuffer->getDuration();
}
//-----------------------------------------------------------------------------
bool SFXSound::_allocVoice( SFXDevice* device )
{
// We shouldn't have any existing voice!
AssertFatal( !mVoice, "SFXSound::_allocVoice() - Already had a voice!" );
// Must not assign voice to source that isn't playing.
AssertFatal( getLastStatus() == SFXStatusPlaying,
"SFXSound::_allocVoice() - Source is not playing!" );
// The buffer can be lost when the device is reset
// or changed, so initialize it if we have to. If
// that fails then we cannot create the voice.
if( mBuffer.isNull() )
{
SFXProfile* profile = getProfile();
if( profile != NULL )
{
SFXBuffer* buffer = profile->getBuffer();
if( buffer )
_setBuffer( buffer );
}
if( mBuffer.isNull() )
return false;
}
// Ask the device for a voice based on this buffer.
mVoice = device->createVoice( is3d(), mBuffer );
if( !mVoice )
return false;
// Set initial properties.
mVoice->setVolume( mPreAttenuatedVolume );
mVoice->setPitch( mEffectivePitch );
mVoice->setPriority( mEffectivePriority );
if( mDescription->mRolloffFactor != -1.f )
mVoice->setRolloffFactor( mDescription->mRolloffFactor );
// Set 3D parameters.
if( is3d() )
{
// Scatter the position, if requested. Do this only once so
// we don't change position when resuming from virtualized
// playback.
if( !mTransformScattered )
_scatterTransform();
// Set the 3D attributes.
setTransform( mTransform );
setVelocity( mVelocity );
_setMinMaxDistance( mMinDistance, mMaxDistance );
_setCone( mConeInsideAngle, mConeOutsideAngle, mConeOutsideVolume );
}
// Set reverb, if enabled.
if( mDescription->mUseReverb )
mVoice->setReverb( mDescription->mReverb );
// Update the duration... it shouldn't have changed, but
// its probably better that we're accurate if it did.
mDuration = mBuffer->getDuration();
// If virtualized playback has been started, we transfer its position to the
// voice and stop virtualization.
const U32 playTime = mPlayTimer.getPosition();
if( playTime > 0 )
{
const U32 pos = mBuffer->getFormat().getSampleCount( playTime );
mVoice->setPosition( pos);
}
mVoice->play( isLooping() );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSound] allocated voice for source '%i' (pos=%i, 3d=%i, vol=%f)",
getId(), playTime, is3d(), mPreAttenuatedVolume );
#endif
return true;
}
//-----------------------------------------------------------------------------
void SFXSound::_onParameterEvent( SFXParameter* parameter, SFXParameterEvent event )
{
Parent::_onParameterEvent( parameter, event );
switch( event )
{
case SFXParameterEvent_ValueChanged:
switch( parameter->getChannel() )
{
case SFXChannelCursor:
setPosition( parameter->getValue() * 1000.f );
break;
default:
break;
}
break;
default:
break;
}
}
//-----------------------------------------------------------------------------
void SFXSound::onRemove()
{
SFXProfile* profile = getProfile();
if( profile != NULL )
profile->getChangedSignal().remove( this, &SFXSound::_onProfileChanged );
Parent::onRemove();
}
//-----------------------------------------------------------------------------
void SFXSound::onDeleteNotify( SimObject* object )
{
if( object == mDescription )
{
deleteObject();
return;
}
Parent::onDeleteNotify( object );
}
//-----------------------------------------------------------------------------
bool SFXSound::_releaseVoice()
{
if( !mVoice )
return true;
// Refuse to release a voice for a streaming buffer that
// is not coming from a profile. For streaming buffers, we will
// have to release the buffer, too, and without a profile we don't
// know how to recreate the stream.
if( isStreaming() && !mTrack )
return false;
// If we're currently playing, transfer our playback position
// to the playtimer so we can virtualize playback while not
// having a voice.
SFXStatus status = getLastStatus();
if( status == SFXStatusPlaying || status == SFXStatusBlocked )
{
// Sync up the play timer with the voice's current position to make
// sure we handle any lag that's cropped up.
mPlayTimer.setPosition( mVoice->getPosition() );
if( status == SFXStatusBlocked )
status = SFXStatusPlaying;
}
mVoice = NULL;
// If this is a streaming source, release our buffer, too.
// Otherwise the voice will stick around as it is uniquely assigned to
// the buffer. When we get reassigned a voice, we will have to do
// a full stream seek anyway, so it's no real loss here.
if( isStreaming() )
mBuffer = NULL;
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSound] release voice for source '%i' (status: %s)",
getId(), SFXStatusToString( status ) );
#endif
return true;
}
//-----------------------------------------------------------------------------
void SFXSound::_play()
{
Parent::_play();
if( mVoice )
mVoice->play( isLooping() );
else
{
// To ensure the fastest possible reaction
// to this playback let the system reassign
// voices immediately.
SFX->_assignVoice( this );
// If we did not get assigned a voice, we'll be
// running virtualized.
#ifdef DEBUG_SPEW
if( !mVoice )
Platform::outputDebugString( "[SFXSound] virtualizing playback of source '%i'", getId() );
#endif
}
}
//-----------------------------------------------------------------------------
void SFXSound::_stop()
{
Parent::_stop();
if( mVoice )
mVoice->stop();
}
//-----------------------------------------------------------------------------
void SFXSound::_pause()
{
Parent::_pause();
if( mVoice )
mVoice->pause();
}
//-----------------------------------------------------------------------------
void SFXSound::_updateStatus()
{
// If we have a voice, use its status.
if( mVoice )
{
SFXStatus voiceStatus = mVoice->getStatus();
// Filter out SFXStatusBlocked.
if( voiceStatus == SFXStatusBlocked )
_setStatus( SFXStatusPlaying );
else
_setStatus( voiceStatus );
return;
}
// If we're not in a playing state or we're a looping
// sound then we don't need to calculate the status.
if( isLooping() || mStatus != SFXStatusPlaying )
return;
// If we're playing and don't have a voice we
// need to decide if the sound is done playing
// to ensure proper virtualization of the sound.
if( mPlayTimer.getPosition() > mDuration )
{
_stop();
_setStatus( SFXStatusStopped );
}
}
//-----------------------------------------------------------------------------
void SFXSound::_updateVolume( const MatrixF& listener )
{
F32 oldPreAttenuatedVolume = mPreAttenuatedVolume;
Parent::_updateVolume( listener );
// If we have a voice and the pre-attenuated volume has
// changed, pass it on to the voice. Attenuation itself will
// happen on the device.
if( mVoice != NULL && oldPreAttenuatedVolume != mPreAttenuatedVolume )
mVoice->setVolume( mPreAttenuatedVolume );
}
//-----------------------------------------------------------------------------
void SFXSound::_updatePitch()
{
F32 oldEffectivePitch = mEffectivePitch;
Parent::_updatePitch();
if( mVoice != NULL && oldEffectivePitch != mEffectivePitch )
mVoice->setPitch( mEffectivePitch );
}
//-----------------------------------------------------------------------------
void SFXSound::_updatePriority()
{
F32 oldEffectivePriority = mEffectivePriority;
Parent::_updatePriority();
if( mVoice != NULL && oldEffectivePriority != mEffectivePriority )
mVoice->setPriority( mEffectivePriority );
}
//-----------------------------------------------------------------------------
U32 SFXSound::getPosition() const
{
if( mVoice )
return mVoice->getFormat().getDuration( mVoice->getPosition() );
else
return ( mPlayTimer.getPosition() % mDuration ); // Clamp for looped sounds.
}
//-----------------------------------------------------------------------------
void SFXSound::setPosition( U32 ms )
{
AssertFatal( ms < getDuration(), "SFXSound::setPosition() - position out of range" );
if( mVoice )
mVoice->setPosition( mVoice->getFormat().getSampleCount( ms ) );
else
mPlayTimer.setPosition( ms );
}
//-----------------------------------------------------------------------------
void SFXSound::setVelocity( const VectorF& velocity )
{
Parent::setVelocity( velocity );
if( mVoice && is3d() )
mVoice->setVelocity( velocity );
}
//-----------------------------------------------------------------------------
void SFXSound::setTransform( const MatrixF& transform )
{
Parent::setTransform( transform );
if( mVoice && is3d() )
mVoice->setTransform( mTransform );
}
//-----------------------------------------------------------------------------
void SFXSound::_setMinMaxDistance( F32 min, F32 max )
{
Parent::_setMinMaxDistance( min, max );
if( mVoice && is3d() )
mVoice->setMinMaxDistance( mMinDistance, mMaxDistance );
}
//-----------------------------------------------------------------------------
void SFXSound::_setCone( F32 innerAngle,
F32 outerAngle,
F32 outerVolume )
{
Parent::_setCone( innerAngle, outerAngle, outerVolume );
if( mVoice && is3d() )
mVoice->setCone( mConeInsideAngle,
mConeOutsideAngle,
mConeOutsideVolume );
}
//-----------------------------------------------------------------------------
bool SFXSound::isReady() const
{
return ( mBuffer != NULL && mBuffer->isReady() );
}
//-----------------------------------------------------------------------------
bool SFXSound::isVirtualized() const
{
return ( ( mVoice == NULL && isPlaying() ) ||
( mVoice != NULL && mVoice->isVirtual() ) );
}
//-----------------------------------------------------------------------------
SFXProfile* SFXSound::getProfile() const
{
return dynamic_cast< SFXProfile* >( mTrack.getPointer() );
}
//-----------------------------------------------------------------------------
F32 SFXSound::getElapsedPlayTimeCurrentCycle() const
{
return F32( getPosition() ) / 1000.f;
}
//-----------------------------------------------------------------------------
F32 SFXSound::getTotalPlayTime() const
{
return F32( mDuration ) / 1000.f;
}
//-----------------------------------------------------------------------------
// Let the user define a priority value for each channel
// in script. We assign it in the system init and use
// it when doleing out hardware handles.
S32 QSORT_CALLBACK SFXSound::qsortCompare( const void* item1, const void* item2 )
{
const SFXSound* source1 = *( ( SFXSound** ) item1 );
const SFXSound* source2 = *( ( SFXSound** ) item2 );
// Sounds that are playing are always sorted
// closer than non-playing sounds.
const bool source1IsPlaying = source1->isPlaying();
const bool source2IsPlaying = source2->isPlaying();
if( !source1IsPlaying && !source2IsPlaying )
return 0;
else if( !source1IsPlaying && source1IsPlaying )
return 1;
else if( source1IsPlaying && !source2IsPlaying )
return -1;
// Louder attenuated volumes take precedence but adjust them
// by priority so that less audible sounds with higher priority
// become more important.
F32 volume1 = source1->getAttenuatedVolume();
F32 volume2 = source2->getAttenuatedVolume();
volume1 += volume1 * source1->mEffectivePriority;
volume2 += volume2 * source2->mEffectivePriority;
if( volume1 < volume2 )
return 1;
if( volume1 > volume2 )
return -1;
// If we got this far then the source that was
// played last has the higher priority.
if( source1->mPlayStartTick > source2->mPlayStartTick )
return -1;
if( source1->mPlayStartTick < source2->mPlayStartTick )
return 1;
// These are sorted the same!
return 0;
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXSound, isReady, bool, (),,
"Test whether the sound data associated with the sound has been fully loaded and is ready for playback.\n"
"For streamed sounds, this will be false during playback when the stream queue for the sound is starved and "
"waiting for data. For buffered sounds, only an initial loading phase will potentially cause isReady to "
"return false.\n\n"
"@return True if the sound is ready for playback." )
{
return object->isReady();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXSound, getPosition, F32, (),,
"Get the current playback position in seconds.\n"
"@return The current play cursor offset." )
{
return F32( object->getPosition() ) * 0.001f;
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXSound, setPosition, void, ( F32 position ),,
"Set the current playback position in seconds.\n"
"If the source is currently playing, playback will jump to the new position. If playback is stopped or paused, "
"playback will resume at the given position when play() is called.\n\n"
"@param position The new position of the play cursor (in seconds).\n" )
{
if( position >= 0 && position <= object->getDuration() )
object->setPosition( position * 1000.0f );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXSound, getDuration, F32, (),,
"Get the total play time (in seconds) of the sound data attached to the sound.\n"
"@return \n\n"
"@note Be aware that for looped sounds, this will not return the total playback time of the sound.\n" )
{
return F32( object->getDuration() ) * 0.001f;
}

View file

@ -0,0 +1,166 @@
//-----------------------------------------------------------------------------
// 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 _SFXSOUND_H_
#define _SFXSOUND_H_
#ifndef _SFXSOURCE_H_
#include "sfx/sfxSource.h"
#endif
#ifndef _SFXVOICE_H_
#include "sfx/sfxVoice.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
#ifndef _SFXPROFILE_H_
#include "sfx/sfxProfile.h"
#endif
class SFXBuffer;
class SFXDevice;
/// A scriptable controller playing a specific single sound file.
class SFXSound : public SFXSource,
public IPositionable< U32 >
{
friend class SFXSystem;
typedef SFXSource Parent;
protected:
/// Used by SFXSystem to create sources.
static SFXSound* _create( SFXDevice* device, SFXProfile* profile );
static SFXSound* _create( SFXDevice* device, const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
/// Internal constructor used for sources.
SFXSound( SFXProfile* profile, SFXDescription* description );
/// The device specific voice which is used during
/// playback. By making it a SafePtr it will NULL
/// automatically when the device is deleted.
StrongWeakRefPtr< SFXVoice > mVoice;
/// The reference counted device specific buffer used by
/// the voice for playback.
StrongWeakRefPtr< SFXBuffer > mBuffer;
/// The duration of the sound cached from the buffer in
/// _initBuffer() used for managing virtual sources.
U32 mDuration;
/// Create a new voice for this source.
bool _allocVoice( SFXDevice* device );
/// Release the voice if the source has one.
bool _releaseVoice();
///
void _setBuffer( SFXBuffer* buffer );
/// Reload the sound buffer. Temporarily goes to virtualized playback when necessary.
void _reloadBuffer();
///
void _onProfileChanged( SFXProfile* profile )
{
if( profile == mTrack )
_reloadBuffer();
}
// SFXSource.
virtual void _play();
virtual void _pause();
virtual void _stop();
virtual void _updateStatus();
virtual void _onParameterEvent( SFXParameter* parameter, SFXParameterEvent event );
virtual void _updateVolume( const MatrixF& listener );
virtual void _updatePitch();
virtual void _updatePriority();
virtual void _setMinMaxDistance( F32 min, F32 max );
virtual void _setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
public:
DECLARE_CONOBJECT( SFXSound );
/// The default constructor is *only* here to satisfy the
/// construction needs of IMPLEMENT_CONOBJECT. It does not
/// create a valid source!
explicit SFXSound();
/// This is normally called from the system to
/// detect if this source has been assigned a
/// voice for playback.
bool hasVoice() const { return mVoice != NULL; }
/// Return the current playback position in milliseconds.
/// @note For looping sources, this returns the position in the current cycle.
U32 getPosition() const;
/// Set the current playback position in milliseconds.
void setPosition( U32 ms );
/// Returns the source's total playback time in milliseconds.
U32 getDuration() const { return mDuration; }
/// Return true if the sound stream tied to the source is currently in a buffer underrun situation.
bool isBlocked() const { return ( mVoice && mVoice->getStatus() == SFXStatusBlocked ); }
/// Returns true if this is a continuously streaming source.
bool isStreaming() const { return mDescription->mIsStreaming; }
/// Returns true if the source's associated data is ready for playback.
bool isReady() const;
/// Return the SFXProfile datablock attached to this sound.
SFXProfile* getProfile() const;
/// Used to sort sources by attenuated volume and channel priority.
static S32 QSORT_CALLBACK qsortCompare( const void* item1, const void* item2 );
// SFXSource.
virtual void setTransform( const MatrixF& transform );
virtual void setVelocity( const VectorF& velocity );
virtual bool isVirtualized() const;
virtual F32 getElapsedPlayTimeCurrentCycle() const;
virtual F32 getTotalPlayTime() const;
// SimObject.
virtual void onRemove();
virtual void onDeleteNotify( SimObject* object );
};
#endif // !_SFXSOUND_H_

View file

@ -0,0 +1,450 @@
//-----------------------------------------------------------------------------
// 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/sfxSoundscape.h"
#include "sfx/sfxAmbience.h"
#include "sfx/sfxEnvironment.h"
#include "sfx/sfxState.h"
#include "sfx/sfxSource.h"
#include "sfx/sfxSystem.h"
//#define DEBUG_SPEW
//FIXME: fix reverb resetting
//=============================================================================
// SFXSoundscape.
//=============================================================================
// MARK: ---- SFXSoundscape ----
//-----------------------------------------------------------------------------
SFXSoundscape::SFXSoundscape( SFXAmbience* ambience )
: mAmbience( ambience )
{
mDirtyBits.set( AmbienceDirty );
mFlags.set( FlagUnique );
dMemset( mStates, 0, sizeof( mStates ) );
}
//-----------------------------------------------------------------------------
void SFXSoundscape::setAmbience( SFXAmbience* ambience )
{
AssertFatal( ambience != NULL, "SFXSoundscape::setAmbience - ambience cannot be NULL!" );
mDirtyBits.set( AmbienceDirty );
mAmbience = ambience;
}
//=============================================================================
// SFXSoundscapeManager.
//=============================================================================
// MARK: ---- SFXSoundscapeManager ----
//-----------------------------------------------------------------------------
SFXSoundscapeManager::SFXSoundscapeManager()
: mCurrentReverbIndex( -1 )
{
VECTOR_SET_ASSOCIATION( mStack );
VECTOR_SET_ASSOCIATION( mFadeStack );
#ifndef TORQUE_SHIPPING
// Hook on to the ambience change signal (used
// to respond to editing of ambiences).
SFXAmbience::getChangeSignal().notify
( this, &SFXSoundscapeManager::_notifyAmbienceChanged );
#endif
// Push the global ambience.
mDefaultGlobalAmbience = new SFXAmbience;
SFXSoundscape* global = mChunker.alloc();
constructInPlace( global, mDefaultGlobalAmbience );
mStack.push_back( global );
}
//-----------------------------------------------------------------------------
SFXSoundscapeManager::~SFXSoundscapeManager()
{
#ifndef TORQUE_SHIPPING
// Remove the hook on the ambience change signal.
SFXAmbience::getChangeSignal().remove
( this, &SFXSoundscapeManager::_notifyAmbienceChanged );
#endif
mDefaultGlobalAmbience->deleteObject();
}
//-----------------------------------------------------------------------------
void SFXSoundscapeManager::update()
{
// Make sure the topmost reverb on the stack is active.
S32 reverbIndex = _findTopmostReverbOnStack( mStack );
if( mCurrentReverbIndex != reverbIndex )
{
if( reverbIndex == -1 )
{
// No ambience on the stack has reverb settings so reset
// to default.
SFX->setReverb( SFXReverbProperties() );
}
else
{
SFXAmbience* ambience = mStack[ reverbIndex ]->getAmbience();
AssertFatal( ambience->getEnvironment(), "SFXSoundscapeManager::update - Reverb lookup return ambience without reverb!" );
SFX->setRolloffFactor( ambience->getRolloffFactor() );
SFX->setDopplerFactor( ambience->getDopplerFactor() );
SFX->setReverb( ambience->getEnvironment()->getReverb() );
}
mCurrentReverbIndex = reverbIndex;
}
// Update the active soundscapes.
for( U32 i = 0; i < mStack.size(); ++ i )
{
SFXSoundscape* soundscape = mStack[ i ];
// If the soundscape's associated ambience has changed
if( soundscape->mDirtyBits.test( SFXSoundscape::AmbienceDirty ) )
{
SFXAmbience* ambience = soundscape->getAmbience();
// Start playing the ambient audio track if it isn't
// already playing and if the soundscape isn't overridden
// by an instance lower down the stack.
if( !soundscape->_isOverridden() )
{
SFXTrack* track = ambience->getSoundTrack();
if( !soundscape->mSource || soundscape->mSource->getTrack() != track )
{
if( soundscape->mSource != NULL )
{
soundscape->mSource->stop();
soundscape->mSource = NULL;
}
if( track )
soundscape->mSource = SFX->playOnce( track );
}
else if( soundscape->mSource != NULL )
{
// Make sure to revert a fade-out running on the source
// if it has been taken from the fade stack.
soundscape->mSource->play();
}
}
// Activate SFXStates on the ambience. For state slots that
// have changed, deactivate states that we have already activated.
for( U32 i = 0; i < SFXAmbience::MaxStates; ++ i )
{
SFXState* state = ambience->getState( i );
if( soundscape->mStates[ i ] != state )
{
if( soundscape->mStates[ i ] )
soundscape->mStates[ i ]->deactivate();
if( state )
state->activate();
soundscape->mStates[ i ] = state;
}
}
soundscape->mDirtyBits.clear( SFXSoundscape::AmbienceDirty );
}
}
// Clean out the fade stack.
for( U32 i = 0; i < mFadeStack.size(); )
if( mFadeStack[ i ]->mSource == NULL )
{
SFXSoundscape* soundscape = mFadeStack[ i ];
mFadeStack.erase_fast( i );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSoundscapeManager] Deleting faded instance of '%s'",
soundscape->getAmbience()->getName() );
#endif
// Free the soundscape.
destructInPlace( soundscape );
mChunker.free( soundscape );
}
else
++ i;
}
//-----------------------------------------------------------------------------
SFXSoundscape* SFXSoundscapeManager::insertSoundscape( U32 index, SFXAmbience* ambience )
{
AssertFatal( index <= mStack.size(), "SFXSoundscapeManager::insertSoundscape - index out of range" );
AssertFatal( index != 0, "SFXSoundscapeManager::insertSoundscape - cannot insert before global soundscape" );
AssertFatal( ambience != NULL, "SFXSoundscapeManager::insertSoundscape - got a NULL ambience" );
// Look for an existing soundscape that is assigned the
// same ambience.
S32 ambientInstanceIndex = _findOnStack( ambience, mStack );
// Push a soundscape unto the stack. If there is an instance
// on the fade stack that is tied to the given ambience, bring that
// soundscape over to the active stack. Otherwise create a new
// soundscape instance.
SFXSoundscape* soundscape = NULL;
if( ambientInstanceIndex == -1 )
{
S32 fadeIndex = _findOnStack( ambience, mFadeStack );
if( fadeIndex != -1 )
{
soundscape = mFadeStack[ fadeIndex ];
mFadeStack.erase_fast( fadeIndex );
// Make sure the soundscape will get re-evaluated
// on the next update.
soundscape->mDirtyBits.set( SFXSoundscape::AmbienceDirty );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSoundscapeManager] Moving ambience '%s' from fade stack to #%i (total: %i)",
ambience->getName(), index, mStack.size() + 1 );
#endif
}
}
if( !soundscape )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSoundscapeManager] Adding new instance for '%s' at #%i (total: %i)",
ambience->getName(), index, mStack.size() + 1 );
#endif
soundscape = mChunker.alloc();
constructInPlace( soundscape, ambience );
}
mStack.insert( index, soundscape );
// If there is an existing soundscape that is assigned the
// same ambience and it is lower on the stack, steal its sound
// source if it has one. If it is higher up the stack, simply
// mark this soundscape as being overridden.
if( ambientInstanceIndex != -1 )
{
SFXSoundscape* existingSoundscape = mStack[ ambientInstanceIndex ];
existingSoundscape->mFlags.clear( SFXSoundscape::FlagUnique );
soundscape->mFlags.clear( SFXSoundscape::FlagUnique );
if( ambientInstanceIndex < index )
{
existingSoundscape->mFlags.set( SFXSoundscape::FlagOverridden );
SFXSource* source = existingSoundscape->mSource;
existingSoundscape->mSource = NULL;
if( source && source->isPlaying() )
soundscape->mSource = source;
}
else
{
soundscape->mFlags.set( SFXSoundscape::FlagOverridden );
}
}
return soundscape;
}
//-----------------------------------------------------------------------------
void SFXSoundscapeManager::removeSoundscape( SFXSoundscape* soundscape )
{
AssertFatal( soundscape != getGlobalSoundscape(),
"SFXSoundscapeManager::removeSoundscape() - trying to remove the global soundscape" );
// Find the soundscape on the stack.
U32 index = 1;
for( ; index < mStack.size(); ++ index )
if( mStack[ index ] == soundscape )
break;
AssertFatal( index < mStack.size(),
"SFXSoundscapeManager::removeSoundscape() - soundscape not on stack" );
// Find out if the soundscape has the current reverb
// environment. If so, we need to change the reverb to
// the next one higher up the stack.
const bool isCurrentReverb = ( _findTopmostReverbOnStack( mStack ) == index );
// Remove the soundscape from the stack.
mStack.erase( index );
// Update reverb, if necessary.
if( isCurrentReverb )
{
S32 reverbIndex = _findTopmostReverbOnStack( mStack );
if( reverbIndex != -1 )
SFX->setReverb( mStack[ reverbIndex ]->getAmbience()->getEnvironment()->getReverb() );
}
// Deactivate states.
for( U32 i = 0; i < SFXAmbience::MaxStates; ++ i )
if( soundscape->mStates[ i ] )
{
soundscape->mStates[ i ]->deactivate();
soundscape->mStates[ i ] = NULL;
}
// If the soundscape is the only instance of its ambience, move
// it to the fade stack. Otherwise delete the soundscape.
if( soundscape->_isUnique() && soundscape->mSource != NULL )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSoundscapeManager] Moving ambience '%s' at index #%i to fade stack",
soundscape->getAmbience()->getName(), index );
#endif
soundscape->mSource->stop();
mFadeStack.push_back( soundscape );
}
else
{
if( !soundscape->_isUnique() )
{
// If this is the overriding soundscape, transfer its state
// to the ambient instance lower down the stack.
if( !soundscape->_isOverridden() )
{
S32 overrideeIndex = index - 1;
for( ; overrideeIndex >= 0; -- overrideeIndex )
if( soundscape->getAmbience() == mStack[ overrideeIndex ]->getAmbience() )
break;
AssertFatal( overrideeIndex >= 0,
"SFXSoundscapeManager::removeSoundscape() - could not find ambience being overridden on stack" );
// Pass the source on to the previous soundscape.
mStack[ overrideeIndex ]->mSource = soundscape->mSource;
soundscape->mSource = NULL;
}
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSoundscapeManager] Removing instance of '%s' at index #%i",
soundscape->getAmbience()->getName(), index );
#endif
// If there's only one instance of this ambience is
// left, mark it as being unique now.
U32 numInstances = 0;
for( U32 i = 0; i < mStack.size(); ++ i )
if( mStack[ i ]->getAmbience() == soundscape->getAmbience() )
++ numInstances;
if( numInstances == 1 )
mStack[ _findOnStack( soundscape->getAmbience(), mStack ) ]->mFlags.set( SFXSoundscape::FlagUnique );
}
// Free the soundscape.
destructInPlace( soundscape );
mChunker.free( soundscape );
}
}
//-----------------------------------------------------------------------------
S32 SFXSoundscapeManager::_findOnStack( SFXAmbience* ambience, const Vector< SFXSoundscape* >& stack )
{
// Search the stack top to bottom so we always find
// the uppermost instance of the ambience on the stack.
for( S32 i = stack.size() - 1; i >= 0; -- i )
if( stack[ i ]->getAmbience() == ambience )
return i;
return -1;
}
//-----------------------------------------------------------------------------
S32 SFXSoundscapeManager::_findTopmostReverbOnStack( const Vector< SFXSoundscape* >& stack )
{
for( S32 i = stack.size() - 1; i >= 0; -- i )
if( stack[ i ]->getAmbience()->getEnvironment() )
return i;
return -1;
}
//-----------------------------------------------------------------------------
void SFXSoundscapeManager::_notifyAmbienceChanged( SFXAmbience* ambience )
{
//RDTODO: fade stack?
// Set the ambience dirty bit on all soundscapes
// tied to the given ambience.
for( U32 i = 0; i < mStack.size(); ++ i )
if( mStack[ i ]->getAmbience() == ambience )
{
mStack[ i ]->mDirtyBits.set( SFXSoundscape::AmbienceDirty );
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXSoundscapeManager] Ambience '%s' at #%i changed",
ambience->getName(), i );
#endif
}
}

View file

@ -0,0 +1,195 @@
//-----------------------------------------------------------------------------
// 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 _SFXSOUNDSCAPE_H_
#define _SFXSOUNDSCAPE_H_
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _TRESPONSECURVE_H_
#include "math/util/tResponseCurve.h"
#endif
#ifndef _SFXAMBIENCE_H_
#include "sfx/sfxAmbience.h"
#endif
/// @file
/// The soundscape system is responsible for controlling ambient audio.
/// It is largely driven by SFXWorld's listener tracking.
class SFXSource;
/// An instance of an SFXAmbience on the soundscape mixer stack.
///
class SFXSoundscape
{
public:
typedef void Parent;
friend class SFXSoundscapeManager;
enum Flags
{
FlagOverridden = BIT( 0 ), ///< Same ambience is pushed lower down onto the stack.
FlagUnique = BIT( 1 ), ///< No other instance of this ambience on stack.
};
enum DirtyBits
{
AmbienceDirty = BIT( 0 ), ///< Associated ambience has changed.
AllDirty = 0xFFFFFFFF
};
protected:
///
BitSet32 mFlags;
///
BitSet32 mDirtyBits;
/// The current soundtrack playing in this soundscape. This is either the
/// ambient track or the surround sound track depending on whether
SimObjectPtr< SFXSource > mSource;
/// The ambient space assigned to this soundscape.
SFXAmbience* mAmbience;
/// States activated by this soundscape.
SFXState* mStates[ SFXAmbience::MaxStates ];
/// Return true if another soundscape lower down the stack is using the same
/// ambient space as this soundscape.
bool _isOverridden() const { return mFlags.test( FlagOverridden ); }
/// Return true if this soundscape is the only soundscape using the assigned
/// ambient space.
bool _isUnique() const { return mFlags.test( FlagUnique ); }
public:
/// Create a soundscape associated with the given ambient space.
SFXSoundscape( SFXAmbience* ambience );
/// Return the ambient space associated with this soundscape.
SFXAmbience* getAmbience() const { return mAmbience; }
/// Set the ambient space associated with this soundscape. Triggers corresponding
/// recomputations on the next soundscape manager update.
void setAmbience( SFXAmbience* ambience );
};
/// The soundscape manager produces a dynamic mix between multiple active soundscapes.
///
/// @note The more layered soundscapes there are, the costlier ambient sound updates will get.
///
class SFXSoundscapeManager
{
public:
typedef void Parent;
protected:
/// Linear stack of soundscapes. Evaluated bottom to top. Scapes
/// being faded out are always last on the stack.
Vector< SFXSoundscape* > mStack;
/// Stack of soundscape that are currently being faded out. These are
/// kept around so that when their ambient spaces are activated again,
/// they can be brought back with their fades simply being reversed.
///
/// All soundscapes on this stack are unique.
Vector< SFXSoundscape* > mFadeStack;
/// Index into #mStack for the soundscape that defines the current
/// reverb settings. -1 if no current ambience has reverb settings.
S32 mCurrentReverbIndex;
/// Memory manager of soundscape instances.
FreeListChunker< SFXSoundscape > mChunker;
/// Default global SFXAmbience. Not a registered object.
SFXAmbience* mDefaultGlobalAmbience;
/// Found the topmost instance on the given stack associated with the given
/// ambient space.
S32 _findOnStack( SFXAmbience* ambience, const Vector< SFXSoundscape* >& stack );
/// Find the topmost soundscape on the given stack that has a reverb environment
/// defined on its ambient space.
S32 _findTopmostReverbOnStack( const Vector< SFXSoundscape* >& stack );
/// Method hooked up to the SFXAmbience change signal to automatically
/// make soundscapes using the given ambience as dirty and trigger
/// a recomputation of their properties.
void _notifyAmbienceChanged( SFXAmbience* ambience );
public:
///
SFXSoundscapeManager();
///
~SFXSoundscapeManager();
/// Update the current soundscape mix.
void update();
/// Return the total number of soundscape instances currently on the stack.
/// Always >=1.
U32 getNumTotalSoundscapes() const { return mStack.size(); }
/// Insert a new soundscape instance associated with the given ambient space
/// at the given stack index.
SFXSoundscape* insertSoundscape( U32 index, SFXAmbience* ambience );
/// Remove the given soundscape from the stack.
void removeSoundscape( SFXSoundscape* soundscape );
/// Return the topmost soundscape. This soundscape is always defined and cannot
/// be removed.
SFXSoundscape* getGlobalSoundscape() const { return mStack[ 0 ]; }
};
#endif // !_SFXSOUNDSCAPE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,671 @@
//-----------------------------------------------------------------------------
// 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 _SFXSOURCE_H_
#define _SFXSOURCE_H_
#ifndef _SIMSET_H_
#include "console/simSet.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _SFXDESCRIPTION_H_
#include "sfx/sfxDescription.h"
#endif
#ifndef _SFXPARAMETER_H_
#include "sfx/sfxParameter.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TIMESOURCE_H_
#include "core/util/timeSource.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _TORQUE_LIST_
#include "core/util/tList.h"
#endif
class SFXTrack;
class SFXDescription;
class SFXSourceGroup;
class SFXModifier;
class EaseF;
/// Baseclass for sources and controllers.
class SFXSource : public SimGroup
{
public:
typedef SimGroup Parent;
friend class SFXSystem; // _init
protected:
typedef Torque::List< SFXModifier* > ModifierList;
typedef GenericTimeSource< RealMSTimer > TimeSource;
enum Flags
{
CustomVolumeFlag = BIT( 0 ),
CustomPitchFlag = BIT( 1 ),
CustomPriorityFlag = BIT( 2 ),
CustomRadiusFlag = BIT( 3 ),
CustomConeFlag = BIT( 4 ),
CustomFadeFlag = BIT( 5 ),
CustomGroupFlag = BIT( 6 ),
};
///
BitSet32 mFlags;
/// @name Status
/// @{
/// Current playback status.
SFXStatus mStatus;
/// The playback status that the source actually wants to be in.
SFXStatus mSavedStatus;
/// Console functions to call when the source status changes.
/// If not set, "onStatusChange" will be called on the source object.
StringTableEntry mStatusCallback;
/// Used internally for setting the sound status.
virtual void _setStatus( SFXStatus newStatus );
/// Update the playback status of the source. Meant for subclasses
/// that need to poll a connected resource.
virtual void _updateStatus() {}
/// @}
/// @name Datablocks
///
/// The track datablock is optional but the description datablock is required.
/// If either of the two goes away, the source will auto-delete itself.
///
/// These members are SimObjectPtr so that temporary track and description
/// objects that are set to auto-delete will work.
///
/// @{
/// The track being played by this source.
/// @note Will be null for sources that have been created from SFXStreams.
SimObjectPtr< SFXTrack > mTrack;
/// The description holding the SFX sound configuration.
SimObjectPtr< SFXDescription > mDescription;
/// @}
/// @name Volume
///
/// Volume processing goes through a series of stages. The last stage, distance attenuation,
/// happens only for 3D sounds and is done on the device (though the attenuated volume is also
/// computed within SFX).
///
/// The succession of stages is:
///
/// 1. Fade (Apply fade-in/out if currently active)
/// 2. Modulate (Apply scale factor set on source)
/// 3. Modulate (Apply attenuated volume of source group as scale factor)
/// 4. Attenuate (Apply 3D distance attenuation based on current listener position)
///
/// @{
/// The desired sound volume.
F32 mVolume;
/// Volume before fade stage. Used as input to volume computation. Usually this
/// corresponds to mVolume but when fading out from an already faded start volume,
/// this contains the starting mFadedVolume.
F32 mPreFadeVolume;
/// "mVolume" after fade stage. Same as "mPreFadeVolume" if no fade
/// is active.
F32 mFadedVolume;
/// Volume scale factor imposed on this source by controller.
F32 mModulativeVolume;
/// Effective volume after fade and modulation but before distance attenuation.
/// For non-3D sounds, this is the final effective volume.
F32 mPreAttenuatedVolume;
/// Effective volume after distance attenuation. Continuously updated
/// to match listener position. For non-3D sounds, this is the same
/// as mPreAttenuatedVolume.
///
/// @note The distance attenuation that is computed here does not take
/// sound cones into account so the computed attenuated volume may be
/// higher than the actual effective volume on the device (never
/// lower though).
F32 mAttenuatedVolume;
/// Set volume without affecting CustomVolumeFlag.
void _setVolume( F32 volume );
/// Update the effective volume of the source.
virtual void _updateVolume( const MatrixF& listener );
/// @}
/// @name Virtualization
/// @{
/// The desired sound priority.
F32 mPriority;
/// The priority scale factor imposed by controllers.
F32 mModulativePriority;
/// The final priority level.
F32 mEffectivePriority;
/// Set priority without affecting CustomPriorityFlag.
void _setPriority( F32 priority );
/// Update the effective priority of the source.
virtual void _updatePriority();
/// @}
/// @name Pitch
/// @{
/// The desired sound pitch.
F32 mPitch;
/// The pitch scale factor imposed by controllers.
F32 mModulativePitch;
/// The final effective pitch.
F32 mEffectivePitch;
/// Set pitch without affecting CustomPitchFlag.
void _setPitch( F32 pitch );
/// Update the effective pitch of the source.
virtual void _updatePitch();
///
/// @}
/// @name 3D Sound
/// @{
/// The transform if this is a 3d source.
MatrixF mTransform;
/// The last set velocity.
VectorF mVelocity;
/// Distance at which to begin distanced-based volume attenuation.
F32 mMinDistance;
/// Distance at which to stop distance-based volume attenuation.
F32 mMaxDistance;
/// Inside cone angle in degrees.
F32 mConeInsideAngle;
/// Outside cone angle in degrees.
F32 mConeOutsideAngle;
/// Outside cone volume.
F32 mConeOutsideVolume;
/// The distance of this source to the last
/// listener position.
F32 mDistToListener;
/// If true, the transform position has been randomized.
bool mTransformScattered;
/// Randomize transform based on scatter settings.
void _scatterTransform();
/// Set 3D min/max distance without affecting CustomRadiusFlag.
virtual void _setMinMaxDistance( F32 min, F32 max );
/// Set 3D cone without affecting CustomConeFlag.
virtual void _setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume );
/// @}
/// @name Fading
///
/// The fade system consists of "persistent" fades placed at the beginning
/// and end of the playback range and of "temporary" fade segments placed in the
/// playback range when stopping/pausing/resuming a source in midst of playback.
///
/// @{
/// The current "persistent" fade-in time in seconds. Taken initially from the
/// SFXDescription and as long as not being manually set on the source, will
/// stay with the description's "fadeInTime" property.
F32 mFadeInTime;
/// The current "persistent" fade-out time in seconds. Taken initially from the
/// SFXDescription and as long as not being manually set on the source, will
/// stay with the description's "fadeOutTime" property.
F32 mFadeOutTime;
/// Type for temporary fade segments.
enum FadeSegmentType
{
FadeSegmentNone, ///< No temporary fade segment set.
FadeSegmentPlay, ///< Temporary fade-in segment.
FadeSegmentStop, ///< Temporary fade-out segment ending in stop().
FadeSegmentPause ///< Temporary fade-out segment ending in pause().
};
/// Playtime at which persistent fade-in ends. -1 if no fade-in.
F32 mFadeInPoint;
/// Playtime at which persistent fade-out starts. -1 if no fade-out.
F32 mFadeOutPoint;
/// Type of the current temporary fade segment. No temporary fade segment
/// is in place when this is FadeSegmentNone.
FadeSegmentType mFadeSegmentType;
/// Easing curve for the current fade segment.
EaseF* mFadeSegmentEase;
/// Playback position where the current temporary fade segment starts.
F32 mFadeSegmentStartPoint;
/// Playback position where the current temporary fade segment ends.
F32 mFadeSegmentEndPoint;
/// Fade time to apply when transitioning to mSavedStatus.
F32 mSavedFadeTime;
///
virtual void _setFadeTimes( F32 fadeInTime, F32 fadeOutTime );
/// Set up a temporary fade-out segment.
void _setupFadeOutSegment( FadeSegmentType type, F32 fadeOutTime );
/// @}
/// @name Parameters
/// @{
///
Vector< SFXParameter* > mParameters;
///
void _addParameter( StringTableEntry name );
///
virtual void _onParameterEvent( SFXParameter* parameter, SFXParameterEvent event );
/// @}
/// @name Playback
/// @{
/// The simulation tick count that playback was started at for this source.
U32 mPlayStartTick;
/// Time object used to keep track of playback.
TimeSource mPlayTimer;
/// Start playback. For implementation by concrete subclasses.
/// @note This method should not take fading into account.
virtual void _play();
/// Pause playback. For implementation by concrete subclasses.
/// @note This method should not take fading into account.
virtual void _pause();
/// Stop playback. For implementation by concrete subclasses.
/// @note This method should not take fading into account.
virtual void _stop();
/// @}
/// @name Modifiers
/// @{
/// List of modifiers that are active on this source.
ModifierList mModifiers;
/// Delete all modifiers of the given type.
template< class T > void _clearModifiers();
/// @}
/// @name Callbacks
/// @{
DECLARE_CALLBACK( void, onStatusChange, ( SFXStatus newStatus ) );
DECLARE_CALLBACK( void, onParameterValueChange, ( SFXParameter* parameter ) );
/// @}
///
SFXSource( SFXTrack* track, SFXDescription* description = NULL );
///
virtual void _update();
/// We overload this to disable creation of
/// a source via script 'new'.
virtual bool processArguments( S32 argc, const char **argv );
// Console getters/setters.
static bool _setDescription( void *obj, const char *index, const char *data );
static const char* _getDescription( void* obj, const char* data );
public:
///
SFXSource();
~SFXSource();
///
void update();
/// Returns the track played by this source; NULL for sources playing directly
/// from streams.
SFXTrack* getTrack() const;
/// Return the SFX description associated with this source. Never NULL.
SFXDescription* getDescription() const { return mDescription; }
/// Return the source group that this source has been assigned to.
SFXSource* getSourceGroup() const;
/// @name Playback Status
/// @{
/// Returns the last known status without doing an update.
SFXStatus getLastStatus() const { return mStatus; }
/// Return the status that the source wants to be in. Used for playback
/// control in combination with source groups.
SFXStatus getSavedStatus() const { return mSavedStatus; }
/// Returns the sound status.
SFXStatus getStatus() const { const_cast< SFXSource* >( this )->_updateStatus(); return mStatus; }
/// Returns true if the source is playing.
bool isPlaying() const { return getStatus() == SFXStatusPlaying; }
/// Returns true if the source is stopped.
bool isStopped() const { return getStatus() == SFXStatusStopped; }
/// Returns true if the source has been paused.
bool isPaused() const { return getStatus() == SFXStatusPaused; }
/// Returns true if the source is currently being virtualized.
virtual bool isVirtualized() const { return false; }
/// Returns true if this is a looping source.
bool isLooping() const { return mDescription->mIsLooping; }
/// @}
/// @name Playback Control
/// @{
/// Starts the sound from the current playback position.
///
/// @param fadeInTime Seconds for sound to fade in. If -1, fadeInTime from
/// SFXDescription is used. Note that certain SFXSource classes may not
/// support values other than 0 and -1.
virtual void play( F32 fadeInTime = -1.f );
/// Stops playback and resets the playback position.
///
/// @note This method is also required to release all playback-related
/// resources on the device.
///
/// @param fadeOutTime Seconds for sound to fade out. If -1, fadeOutTime from
/// SFXDescription is used. Note that certain SFXSource classes may not support
/// values other than 0 and -1.
virtual void stop( F32 fadeOutTime = -1.f );
/// Pauses the sound playback.
///
/// @param fadeOutTime Seconds for sound to fade out. If -1, fadeOutTime from
/// SFXDescription is used. Note that certain SFXSource clsases may not support
/// values other than 0 and -1.
virtual void pause( F32 fadeOutTime = -1.f );
/// Return the elapsed play time of the current loop cycle so far in seconds.
virtual F32 getElapsedPlayTimeCurrentCycle() const;
/// Return the total elapsed play time so far in seconds.
virtual F32 getElapsedPlayTime() const;
/// Return the total play time of the source in seconds. Positive infinity by default.
///
/// @note For looping sounds, this must include only the playtime of a single cycle.
virtual F32 getTotalPlayTime() const;
/// @}
/// @name 3D Sound
/// @{
/// Returns true if this is a 3D source.
bool is3d() const { return mDescription->mIs3D; }
/// Returns the last set velocity.
const VectorF& getVelocity() const { return mVelocity; }
/// Returns the last set transform.
const MatrixF& getTransform() const { return mTransform; }
/// Sets the position and orientation for a 3d buffer.
virtual void setTransform( const MatrixF& transform );
/// Sets the velocity for a 3d buffer.
virtual void setVelocity( const VectorF& velocity );
/// Sets the minimum and maximum distances for 3d falloff.
void setMinMaxDistance( F32 min, F32 max ) { _setMinMaxDistance( min, max ); mFlags.set( CustomRadiusFlag ); }
/// Set sound cone of a 3D sound.
///
/// @param innerAngle Inner cone angle in degrees.
/// @param outerAngle Outer cone angle in degrees.
/// @param outerVolume Outer volume factor.
void setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) { _setCone( innerAngle, outerAngle, outerVolume ); mFlags.set( CustomConeFlag ); }
/// Returns the last distance to the listener.
/// @note Only works when distance attenuation calculations are being triggered by SFX and
/// are not left exclusively to the SFX device.
F32 getDistToListener() const { return mDistToListener; }
/// @}
/// @name Volume
/// @{
/// Returns the source volume at its unaltered initial setting,
/// i.e. prior to fading, modulation, and attenuation.
F32 getVolume() const { return mVolume; }
/// Sets the source volume which will still be
/// scaled by the master and group volumes.
///
/// @note Note that if you set an explicit volume on a source
void setVolume( F32 volume ) { _setVolume( volume ); mFlags.set( CustomVolumeFlag ); }
///
F32 getModulativeVolume() const { return mModulativeVolume; }
/// Set the per-source volume scale factor.
void setModulativeVolume( F32 value );
///
F32 getPreAttenuatedVolume() const { return mPreAttenuatedVolume; }
/// Returns the volume with respect to the master
/// and group volumes and the listener.
F32 getAttenuatedVolume() const { return mAttenuatedVolume; }
///
F32 getFadeInTime() const { return mFadeInTime; }
///
F32 getFadeOutTime() const { return mFadeOutTime; }
///
void setFadeTimes( F32 fadeInTime, F32 fadeOutTime );
/// @}
/// @name Pitch
/// @{
/// Returns the source pitch scale.
F32 getPitch() const { return mPitch; }
/// Sets the source pitch scale.
void setPitch( F32 pitch ) { _setPitch( pitch ); mFlags.set( CustomPitchFlag ); }
///
F32 getModulativePitch() const { return mModulativePitch; }
///
void setModulativePitch( F32 value );
///
F32 getEffectivePitch() const { return mEffectivePitch; }
/// @}
/// @name Dynamic Parameters
///
/// Dynamic parameters allow to pass on values from the game system to the sound system
/// and thus implement interactive audio.
///
/// It is dependent on the back-end source implementation how it will react to parameter
/// settings.
///
/// @{
///
U32 getNumParameters() const { return mParameters.size(); }
///
SFXParameter* getParameter( U32 index )
{
AssertFatal( index < getNumParameters(), "SFXSource::getParameter() - index out of range" );
return mParameters[ index ];
}
///
virtual void addParameter( SFXParameter* parameter );
///
virtual void removeParameter( SFXParameter* parameter );
/// @}
/// @name Modifiers
/// @{
///
void addModifier( SFXModifier* modifier );
///
void addMarker( const String& name, F32 pos );
/// @}
/// @name Virtualization
/// @{
/// Returns the source priority.
F32 getPriority() const { return mPriority; }
///
void setPriority( F32 priority ) { _setPriority( priority ); mFlags.set( CustomPriorityFlag ); }
///
F32 getModulativePriority() const { return mModulativePriority; }
///
void setModulativePriority( F32 value );
///
F32 getEffectivePriority() const { return mEffectivePriority; }
/// @}
/// @}
/// @name Change Notifications
/// @{
/// Notify the source that its attached SFXDescription has changed.
virtual void notifyDescriptionChanged();
/// Notify the source that its attached SFXTrack has changed.
virtual void notifyTrackChanged();
/// @}
// SimGroup.
virtual bool onAdd();
virtual void onRemove();
virtual void onDeleteNotify( SimObject* object );
virtual bool acceptsAsChild( SimObject* object );
virtual void onGroupAdd();
static void initPersistFields();
DECLARE_CONOBJECT( SFXSource );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "SFX sound playback controller." );
};
/// A simple macro to automate the deletion of a source.
///
/// @see SFXSource
///
#undef SFX_DELETE
#define SFX_DELETE( source ) \
if( source ) \
{ \
source->deleteObject(); \
source = NULL; \
} \
#endif // !_SFXSOURCE_H_

View file

@ -0,0 +1,397 @@
//-----------------------------------------------------------------------------
// 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/sfxState.h"
#include "sfx/sfxTypes.h"
#include "core/stream/bitStream.h"
#include "console/engineAPI.h"
//#define DEBUG_SPEW
IMPLEMENT_CO_DATABLOCK_V1( SFXState );
ConsoleDocClass( SFXState,
"@brief A boolean switch used to modify playlist behavior.\n\n"
"Sound system states are used to allow playlist controllers to make decisions based on global state. This is useful, for "
"example, to couple audio playback to gameplay state. Certain states may, for example, represent different locations that the "
"listener can be in, like underwater, in open space, or indoors. Other states could represent moods of the current gameplay "
"situation, like, for example, an aggressive mood during combat.\n\n"
"By activating and deactivating sound states according to gameplay state, a set of concurrently running playlists may "
"react and adapt to changes in the game.\n\n"
"@section SFXState_activation Activation and Deactivation\n"
"At any time, a given state can be either active or inactive. Calling activate() on a state increases an internal "
"counter and calling deactivate() decreases the counter. Only when the count reaches zero will the state be "
"deactivated.\n\n"
"In addition to the activation count, states also maintain a disabling count. Calling disable() increases this count "
"and calling enable() decreases it. As long as this count is greater than zero, a given state will not be activated "
"even if its activation count is non-zero. Calling disable() on an active state will not only increase the disabling "
"count but also deactivate the state. Calling enable() on a state with a positive activation count will re-activate "
"the state when the disabling count reaches zero.\n\n"
"@section SFXState_dependencies State Dependencies\n"
"By listing other states in in its #includedStates and #excludedStates fields, a state may automatically trigger the "
"activation or disabling of other states in the sytem when it is activated. This allows to form dependency chains "
"between individual states.\n\n"
"@tsexample\n"
"// State indicating that the listener is submerged.\n"
"singleton SFXState( AudioLocationUnderwater )\n"
"{\n"
" parentGroup = AudioLocation;\n"
" // AudioStateExclusive is a class defined in the core scripts that will automatically\n"
" // ensure for a state to deactivate all the sibling SFXStates in its parentGroup when it\n"
" // is activated.\n"
" className = \"AudioStateExclusive\";\n"
"};\n"
"\n"
"// State suitable e.g. for combat.\n"
"singleton SFXState( AudioMoodAggressive )\n"
"{\n"
" parentGroup = AudioMood;\n"
" className = \"AudioStateExclusive\";\n"
"};\n"
"@endtsexample\n\n"
"@see SFXPlayList\n"
"@see SFXController\n"
"@see SFXPlayList::state\n"
"@see SFXPlayList::stateMode\n\n"
"@ref SFX_interactive\n\n"
"@ingroup SFX\n"
"@ingroup Datablocks"
);
IMPLEMENT_CALLBACK( SFXState, onActivate, void, (), (),
"Called when the state goes from inactive to active." );
IMPLEMENT_CALLBACK( SFXState, onDeactivate, void, (), (),
"called when the state goes from active to deactive." );
static Vector< SFXState* > sgActiveStates( __FILE__, __LINE__ );
//-----------------------------------------------------------------------------
SFXState::SFXState()
: mActiveCount( 0 ),
mDisableCount( 0 )
{
dMemset( mIncludedStates, 0, sizeof( mIncludedStates ) );
dMemset( mExcludedStates, 0, sizeof( mExcludedStates ) );
}
//-----------------------------------------------------------------------------
void SFXState::initPersistFields()
{
addGroup( "State" );
addField( "includedStates", TypeSFXStateName, Offset( mIncludedStates, SFXState ),
MaxIncludedStates,
"States that will automatically be activated when this state is activated.\n\n"
"@ref SFXState_activation" );
addField( "excludedStates", TypeSFXStateName, Offset( mExcludedStates, SFXState ),
MaxExcludedStates,
"States that will automatically be disabled when this state is activated.\n\n"
"@ref SFXState_activation" );
endGroup( "State" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
void SFXState::activate()
{
mActiveCount ++;
if( mActiveCount == 1 && !isDisabled() )
_onActivate();
}
//-----------------------------------------------------------------------------
void SFXState::deactivate()
{
if( !mActiveCount )
return;
mActiveCount --;
if( !mActiveCount && !isDisabled() )
_onDeactivate();
}
//-----------------------------------------------------------------------------
void SFXState::enable()
{
if( !mDisableCount )
return;
mDisableCount --;
if( !mDisableCount && mActiveCount > 0 )
_onActivate();
}
//-----------------------------------------------------------------------------
void SFXState::disable()
{
mDisableCount ++;
if( mDisableCount == 1 && mActiveCount > 0 )
_onDeactivate();
}
//-----------------------------------------------------------------------------
bool SFXState::onAdd()
{
if( !Parent::onAdd() )
return false;
Sim::getSFXStateSet()->addObject( this );
return true;
}
//-----------------------------------------------------------------------------
bool SFXState::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
if( !server )
{
for( U32 i = 0; i < MaxIncludedStates; ++ i )
if( !sfxResolve( &mIncludedStates[ i ], errorStr ) )
return false;
for( U32 i = 0; i < MaxExcludedStates; ++ i )
if( !sfxResolve( &mExcludedStates[ i ], errorStr ) )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
void SFXState::packData( BitStream* stream )
{
Parent::packData( stream );
for( U32 i = 0; i < MaxIncludedStates; ++ i )
sfxWrite( stream, mIncludedStates[ i ] );
for( U32 i = 0; i < MaxExcludedStates; ++ i )
sfxWrite( stream, mExcludedStates[ i ] );
}
//-----------------------------------------------------------------------------
void SFXState::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
for( U32 i = 0; i < MaxIncludedStates; ++ i )
sfxRead( stream, &mIncludedStates[ i ] );
for( U32 i = 0; i < MaxExcludedStates; ++ i )
sfxRead( stream, &mExcludedStates[ i ] );
}
//-----------------------------------------------------------------------------
void SFXState::_onActivate()
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXState] Activating '%s'", getName() );
#endif
onActivate_callback();
// Add the state to the list.
sgActiveStates.push_back( this );
// Activate included states.
for( U32 i = 0; i < MaxIncludedStates; ++ i )
if( mIncludedStates[ i ] )
mIncludedStates[ i ]->activate();
// Disable excluded states.
for( U32 i = 0; i < MaxExcludedStates; ++ i )
if( mExcludedStates[ i ] )
mExcludedStates[ i ]->disable();
}
//-----------------------------------------------------------------------------
void SFXState::_onDeactivate()
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXState] Deactivating '%s'", getName() );
#endif
onDeactivate_callback();
// Remove the state from the list.
for( U32 i = 0; i < sgActiveStates.size(); ++ i )
if( sgActiveStates[ i ] == this )
{
sgActiveStates.erase_fast( i );
break;
}
// Deactivate included states.
for( U32 i = 0; i < MaxIncludedStates; ++ i )
if( mIncludedStates[ i ] )
mIncludedStates[ i ]->deactivate();
// Enable excluded states.
for( U32 i = 0; i < MaxExcludedStates; ++ i )
if( mExcludedStates[ i ] )
mExcludedStates[ i ]->enable();
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXState, isActive, bool, (),,
"Test whether the state is currently active.\n"
"This is true when the activation count is >0 and the disabling count is =0.\n"
"@return True if the state is currently active.\n"
"@see activate" )
{
return object->isActive();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXState, activate, void, (),,
"Increase the activation count on the state.\n"
"If the state isn't already active and it is not disabled, the state will be activated.\n"
"@see isActive\n"
"@see deactivate\n" )
{
object->activate();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXState, deactivate, void, (),,
"Decrease the activation count on the state.\n"
"If the count reaches zero and the state was not disabled, the state will be deactivated.\n"
"@see isActive\n"
"@see activate\n" )
{
object->deactivate();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXState, isDisabled, bool, (),,
"Test whether the state is currently disabled.\n"
"This is true when the disabling count of the state is non-zero.\n"
"@return True if the state is disabled.\n\n"
"@see disable\n" )
{
return object->isDisabled();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXState, disable, void, (),,
"Increase the disabling count of the state.\n"
"If the state is currently active, it will be deactivated.\n"
"@see isDisabled\n" )
{
object->disable();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( SFXState, enable, void, (),,
"Decrease the disabling count of the state.\n"
"If the disabling count reaches zero while the activation count is still non-zero, "
"the state will be reactivated again.\n"
"@see isDisabled\n" )
{
object->enable();
}
//=============================================================================
// Console Functions.
//=============================================================================
// MARK: ---- Console Functions ----
//-----------------------------------------------------------------------------
DefineEngineFunction( sfxGetActiveStates, const char*, (),,
"Return a newline-separated list of all active states.\n"
"@return A list of the form\n"
"@verbatim\n"
"stateName1 NL stateName2 NL stateName3 ...\n"
"@endverbatim\n"
"where each element is the name of an active state object.\n\n"
"@tsexample\n"
"// Disable all active states.\n"
"foreach$( %state in sfxGetActiveStates() )\n"
" %state.disable();\n"
"@endtsexample\n\n"
"@ingroup SFX" )
{
StringBuilder str;
bool isFirst = true;
for( U32 i = 0; i < sgActiveStates.size(); ++ i )
{
if( !isFirst )
str.append( ' ' );
str.append( sgActiveStates[ i ]->getName() );
isFirst = false;
}
return Con::getReturnBuffer( str );
}

View file

@ -0,0 +1,120 @@
//-----------------------------------------------------------------------------
// 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 _SFXSTATE_H_
#define _SFXSTATE_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
/// A boolean switch used to modify playlist behavior.
///
class SFXState : public SimDataBlock
{
public:
typedef SimDataBlock Parent;
enum
{
MaxIncludedStates = 4,
MaxExcludedStates = 4
};
protected:
/// Reference count for activation.
U32 mActiveCount;
/// Reference count for disabling.
U32 mDisableCount;
/// States that will be activated when this state is activated.
SFXState* mIncludedStates[ MaxIncludedStates ];
/// States that will be disabled when this state is activated.
SFXState* mExcludedStates[ MaxExcludedStates ];
/// Call when state has become active.
void _onActivate();
/// Call when state has gone back to being deactive.
void _onDeactivate();
/// @name Callbacks
/// @{
DECLARE_CALLBACK( void, onActivate, () );
DECLARE_CALLBACK( void, onDeactivate, () );
/// @}
public:
///
SFXState();
/// Return true if the state is currently active (activated and not disabled).
bool isActive() const { return ( !isDisabled() && mActiveCount > 0 ); }
/// Return true if the state is currently disabled.
bool isDisabled() const { return ( mDisableCount > 0 ); }
/// Activate this state. activate/deactivate calls balance each other.
/// Activating a disabled state will not make it active.
void activate();
///
void deactivate();
/// Re-enable this state.
void enable();
/// Disable this state so that it cannot be activated. This is used
/// by state exclusion.
void disable();
// SimDataBlock.
virtual bool onAdd();
virtual bool preload( bool server, String& errorStr );
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
static void initPersistFields();
DECLARE_CONOBJECT( SFXState );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "A datablock describing a particular state for the SFX system." );
};
#endif // !_SFXSTATE_H_

View file

@ -0,0 +1,86 @@
//-----------------------------------------------------------------------------
// 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 _SFXSTREAM_H_
#define _SFXSTREAM_H_
#ifndef _THREADSAFEREFCOUNT_H_
# include "platform/threads/threadSafeRefCount.h"
#endif
#ifndef _SFXCOMMON_H_
# include "sfx/sfxCommon.h"
#endif
#ifndef _TSTREAM_H_
# include "core/stream/tStream.h"
#endif
/// The base sound data streaming interface.
///
/// @note Streams that support seeking should implement the IPositionable<U32> interface.
/// @note Since SFXStreams are byte streams, all offset/size information is in bytes and
/// not in number of samples.
class SFXStream : public ThreadSafeRefCount< SFXStream >,
public IInputStream< U8 >,
public IResettable
{
public:
typedef void Parent;
/// Destructor.
virtual ~SFXStream() {}
/// Make a copy of this stream with its own private state, so
/// new independent read() operations can be issued on the same
/// data stream.
///
/// @return Returns a copy of the stream or NULL.
virtual SFXStream* clone() const { return NULL; }
/// The format of the data in the stream.
virtual const SFXFormat& getFormat() const = 0;
/// The number of samples in the data stream.
virtual U32 getSampleCount() const = 0;
/// The data size in bytes of the decompressed PCM data.
virtual U32 getDataLength() const = 0;
/// The length of the sound in milliseconds.
virtual U32 getDuration() const = 0;
/// Returns true if we've reached the end of the stream.
virtual bool isEOS() const = 0;
/// Resets the stream to restart reading data from the begining.
virtual void reset() = 0;
/// Reads data from the stream and decompresses it into PCM samples.
///
/// @param length Number of bytes to read.
virtual U32 read( U8 *buffer, U32 length ) = 0;
};
typedef ThreadSafeRef< SFXStream > SFXStreamRef;
#endif // _SFXSTREAM_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,450 @@
//-----------------------------------------------------------------------------
// 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 _SFXSYSTEM_H_
#define _SFXSYSTEM_H_
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _THREADSAFEREFCOUNT_H_
#include "platform/threads/threadSafeRefCount.h"
#endif
class SFXTrack;
class SFXDevice;
class SFXProfile;
class SFXStream;
class SFXAmbience;
class SFXSoundscapeManager;
class SFXSource;
class SFXSound;
class SFXBuffer;
class SFXDescription;
/// SFX system events that can be received notifications on.
enum SFXSystemEventType
{
/// SFX is being updated.
SFXSystemEvent_Update,
/// New SFXDevice has been created.
SFXSystemEvent_CreateDevice,
/// SFXDevice is about to be destroyed.
SFXSystemEvent_DestroyDevice,
};
/// SFXSystemPlugins are used to allow other subsystems hook into core functionality
/// of the sound system.
class SFXSystemPlugin
{
public:
///
virtual void update() {}
///
virtual SFXSource* createSource( SFXTrack* track ) { return NULL; }
/// Filter the given reverb setup before it is set up on the device. This
/// allows to, for example, modify the current reverb depending on listener
/// location.
virtual void filterReverb( SFXReverbProperties& reverb ) {}
};
/// This class provides access to the sound system.
///
/// There are a few script preferences that are used by
/// the sound providers.
///
/// $pref::SFX::frequency - This is the playback frequency
/// for the primary sound buffer used for mixing. Although
/// most providers will reformat on the fly, for best quality
/// and performance match your sound files to this setting.
///
/// $pref::SFX::bitrate - This is the playback bitrate for
/// the primary sound buffer used for mixing. Although most
/// providers will reformat on the fly, for best quality
/// and performance match your sound files to this setting.
///
class SFXSystem
{
friend class SFXSound; // _assignVoices
friend class SFXSource; // _onAddSource, _onRemoveSource.
friend class SFXProfile; // _createBuffer.
public:
typedef Signal< void( SFXSystemEventType event ) > EventSignalType;
typedef Vector< SFXSource* > SFXSourceVector;
typedef Vector< SFXSound* > SFXSoundVector;
protected:
/// The one and only instance of the SFXSystem.
static SFXSystem* smSingleton;
/// The protected constructor.
///
/// @see SFXSystem::init()
///
SFXSystem();
/// The non-virtual destructor. You shouldn't
/// ever need to overload this class.
~SFXSystem();
/// The current output sound device initialized
/// and ready to play back.
SFXDevice* mDevice;
///
SFXSoundVector mSounds;
/// This is used to keep track of play once sources
/// that must be released when they stop playing.
SFXSourceVector mPlayOnceSources;
/// The last time the sources got an update.
U32 mLastSourceUpdateTime;
///
U32 mLastAmbientUpdateTime;
///
U32 mLastParameterUpdateTime;
/// The distance model used for rolloff curve computation on 3D sounds.
SFXDistanceModel mDistanceModel;
/// The current doppler scale factor.
F32 mDopplerFactor;
/// The current curve rolloff factor.
F32 mRolloffFactor;
/// The current position and orientation of all listeners.
Vector< SFXListenerProperties > mListeners;
/// Current global reverb properties.
SFXReverbProperties mReverb;
/// SFX system event signal.
EventSignalType mEventSignal;
/// Ambient soundscape manager.
SFXSoundscapeManager* mSoundscapeMgr;
/// List of plugins currently linked to the SFX system.
Vector< SFXSystemPlugin* > mPlugins;
/// @name Stats
///
/// Stats reported back to the console for tracking performance.
///
/// @{
S32 mStatNumSources;
S32 mStatNumSounds;
S32 mStatNumPlaying;
S32 mStatNumCulled;
S32 mStatNumVoices;
S32 mStatSourceUpdateTime;
S32 mStatParameterUpdateTime;
S32 mStatAmbientUpdateTime;
/// @}
/// Called to reprioritize and reassign buffers as
/// sources change state, volumes are adjusted, and
/// the listener moves around.
///
/// @see SFXSource::_update()
///
void _updateSources();
/// This called to reprioritize and reassign
/// voices to sources.
void _assignVoices();
///
void _assignVoice( SFXSound* sound );
///
void _sortSounds( const SFXListenerProperties& listener );
/// Called from SFXSource::onAdd to register the source.
void _onAddSource( SFXSource* source );
/// Called from SFXSource::onRemove to unregister the source.
void _onRemoveSource( SFXSource* source );
/// Called from SFXProfile to create a device specific
/// sound buffer used in conjunction with a voice in playback.
SFXBuffer* _createBuffer( const ThreadSafeRef< SFXStream >& stream, SFXDescription* description );
/// Load file directly through SFXDevice. Depends on
/// availability with selected SFXDevice.
///
/// @return Return new buffer or NULL.
SFXBuffer* _createBuffer( const String& filename, SFXDescription* description );
///
SFXDevice* _getDevice() const { return mDevice; }
public:
/// Returns the one an only instance of the SFXSystem
/// unless it hasn't been initialized or its been disabled
/// in your build.
///
/// For convienence you can use the SFX-> macro as well.
///
/// @see SFXSystem::init()
/// @see SFX
static SFXSystem* getSingleton() { return smSingleton; }
/// This is called from initialization to prepare the
/// sound system singleton. This also includes registering
/// common resource types and initializing available sound
/// providers.
static void init();
/// This is called after Sim::shutdown() in shutdownLibraries()
/// to free the sound system singlton. After this the SFX
/// singleton is null and any call to it will crash.
static void destroy();
/// This is only public so that it can be called by
/// the game update loop. It updates the current
/// device and all sources.
void _update();
/// Register the given plugin with the system.
void addPlugin( SFXSystemPlugin* plugin );
/// Unregister the given plugin with the system.
void removePlugin( SFXSystemPlugin* plugin );
/// @name Device Management
/// @{
/// This initializes a new device.
///
/// @param providerName The name of the provider.
/// @param deviceName The name of the provider device.
/// @param useHardware Toggles the use of hardware processing when available.
/// @param maxBuffers The maximum buffers for this device to use or -1
/// for the device to pick its own reasonable default.
/// @param changeDevice Allows this to change the current device to a new one
/// @return Returns true if the device was created.
bool createDevice( const String& providerName,
const String& deviceName,
bool useHardware,
S32 maxBuffers,
bool changeDevice = false);
/// Returns the current device information or NULL if no
/// device is present. The information string is in the
/// following format:
///
/// Provider Name\tDevice Name\tUse Hardware\tMax Buffers
String getDeviceInfoString();
/// This destroys the current device. All sources loose their
/// playback buffers, but otherwise continue to function.
void deleteDevice();
/// Returns true if a device is allocated.
bool hasDevice() const { return mDevice != NULL; }
/// @}
/// @name Source Creation
/// @{
/// Used to create new sound sources from a sound profile. The
/// returned source is in a stopped state and ready for playback.
/// Use the SFX_DELETE macro to free the source when your done.
///
/// @note The track must have at least the same lifetime as the
/// source. If the description disappears while the source is still
/// there, the source will go with it.
///
/// @param profile The sound profile for the created source.
/// @param transform The optional transform if creating a 3D source.
/// @param velocity The optional doppler velocity if creating a 3D source.
///
/// @return The sound source or NULL if an error occured.
SFXSource* createSource( SFXTrack* track,
const MatrixF* transform = NULL,
const VectorF* velocity = NULL );
/// Used to create a streaming sound source from a user supplied
/// stream object.
///
/// It is only intended for memory based streams. For sound file
/// streaming use createSource() with a streaming SFXProfile.
///
/// Use the SFX_DELETE macro to free the source when your done.
///
/// @note The description must have at least the same lifetime as the
/// sound. If the description disappears while the source is still
/// there, the sound will go with it.
///
/// @param stream The stream used to create the sound buffer. It
/// must exist for the lifetime of the source and will
/// have its reference count decremented when the source
/// is destroyed.
///
/// @param description The sound description to apply to the source.
///
/// @return The sound source or NULL if an error occured.
SFXSound* createSourceFromStream( const ThreadSafeRef< SFXStream >& stream,
SFXDescription* description );
/// Creates a source which when it finishes playing will auto delete
/// itself. Be aware that the returned SFXSource pointer should only
/// be used for error checking or immediate setting changes. It may
/// be deleted as soon as the next system tick.
///
/// @param profile The sound profile for the created source.
/// @param transform The optional transform if creating a 3D source.
/// @param velocity The optional doppler velocity if creating a 3D source.
///
/// @return The sound source or NULL if an error occured.
SFXSource* playOnce( SFXTrack* track,
const MatrixF* transform = NULL,
const VectorF* velocity = NULL,
F32 fadeInTime = -1.f );
SFXSource* playOnce( SFXProfile* profile,
const MatrixF* transform = NULL,
const VectorF* velocity = NULL,
F32 fadeInTime = -1.f )
{ // Avoids having to require inclusion of sfxProfile.h
return playOnce( ( SFXTrack* ) profile, transform, velocity, fadeInTime );
}
/// Stop the source and delete it. This method will take care of
/// the fade-out time that the source may need before it will actually
/// stop and may be deleted.
void stopAndDeleteSource( SFXSource* source );
/// Mark source for deletion when it is moving into stopped state.
/// This method is useful to basically make a source a play-once source
/// after the fact.
void deleteWhenStopped( SFXSource* source );
/// @}
/// @}
/// @name Listeners
/// @{
/// Return the number of listeners currently configured.
U32 getNumListeners() const { return mListeners.size(); }
/// Set the number of concurrent listeners.
/// @note It depends on the selected device if more than one listener is actually supported.
void setNumListeners( U32 num );
/// Set the property of the given listener.
const SFXListenerProperties& getListener( U32 index = 0 ) const { return mListeners[ index ]; }
/// Set the 3D attributes of the given listener.
void setListener( U32 index, const MatrixF& transform, const Point3F& velocity );
void setListener( U32 index, const SFXListenerProperties& properties )
{
setListener( index, properties.getTransform(), properties.getVelocity() );
}
/// @}
/// @name 3D Sound Configuration
/// {
/// Return the curve model currently used distance attenuation of positional sounds.
SFXDistanceModel getDistanceModel() const { return mDistanceModel; }
///
void setDistanceModel( SFXDistanceModel model );
///
F32 getDopplerFactor() const { return mDopplerFactor; }
///
void setDopplerFactor( F32 factor );
///
F32 getRolloffFactor() const { return mRolloffFactor; }
///
void setRolloffFactor( F32 factor );
///
const SFXReverbProperties& getReverb() const { return mReverb; }
///
void setReverb( const SFXReverbProperties& reverb );
/// @}
///
SFXSoundscapeManager* getSoundscapeManager() const { return mSoundscapeMgr; }
/// Dump information about all current SFXSources to the console or
/// to the given StringBuilder.
void dumpSources( StringBuilder* toString = NULL, bool excludeGroups = true );
/// Return the SFX system event signal.
EventSignalType& getEventSignal() { return mEventSignal; }
/// Notify the SFX system that the given description has changed.
/// All sources currently using the description will be updated.
void notifyDescriptionChanged( SFXDescription* description);
///
void notifyTrackChanged( SFXTrack* track );
};
/// Less verbose macro for accessing the SFX singleton. This
/// should be the prefered method for accessing the system.
///
/// @see SFXSystem
/// @see SFXSystem::getSingleton()
///
#define SFX SFXSystem::getSingleton()
#endif // _SFXSYSTEM_H_

View file

@ -0,0 +1,188 @@
//-----------------------------------------------------------------------------
// 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/sfxTrack.h"
#include "sfx/sfxTypes.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxSystem.h"
#include "core/stream/bitStream.h"
IMPLEMENT_CO_DATABLOCK_V1( SFXTrack );
ConsoleDocClass( SFXTrack,
"@brief Abstract base class for sound data that can be played back by the sound system.\n\n"
"The term \"track\" is used in the sound system to refer to any entity that can be played "
"back as a sound source. These can be individual files (SFXProfile), patterns of other tracks "
"(SFXPlayList), or special sound data defined by a device layer (SFXFMODEvent).\n\n"
"Any track must be paired with a SFXDescription that tells the sound system how to set up "
"playback for the track.\n\n"
"All objects that are of type SFXTrack will automatically be added to @c SFXTrackSet.\n\n"
"@note This class cannot be instantiated directly.\n\n"
"@ingroup SFX\n"
"@ingroup Datablocks\n"
);
//-----------------------------------------------------------------------------
SFXTrack::SFXTrack()
: mDescription( NULL )
{
dMemset( mParameters, 0, sizeof( mParameters ) );
}
//-----------------------------------------------------------------------------
SFXTrack::SFXTrack( SFXDescription* description )
: mDescription( description )
{
dMemset( mParameters, 0, sizeof( mParameters ) );
}
//-----------------------------------------------------------------------------
void SFXTrack::initPersistFields()
{
addGroup( "Sound" );
addField( "description", TypeSFXDescriptionName, Offset( mDescription, SFXTrack ),
"Playback setup description for this track.\n\n"
"If unassigned, the description named \"AudioEffects\" will automatically be assigned to the track. If this description "
"is not defined, track creation will fail." );
addField( "parameters", TypeSFXParameterName, Offset( mParameters, SFXTrack ), MaxNumParameters,
"Parameters to automatically attach to SFXSources created from this track.\n"
"Individual parameters are identified by their #internalName." );
endGroup( "Sound" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
bool SFXTrack::processArguments( S32 argc, const char **argv )
{
if( typeid( *this ) == typeid( SFXTrack ) )
{
Con::errorf( ConsoleLogEntry::Script, "SFXTrack is an abstract base class that cannot be instantiated directly!" );
return false;
}
return Parent::processArguments( argc, argv );
}
//-----------------------------------------------------------------------------
void SFXTrack::setParameter( U32 index, const char* name )
{
AssertFatal( index < MaxNumParameters, "SFXTrack::setParameter() - index out of range" );
mParameters[ index ] = StringTable->insert( name );
}
//-----------------------------------------------------------------------------
void SFXTrack::packData( BitStream* stream )
{
Parent::packData( stream );
sfxWrite( stream, mDescription );
for( U32 i = 0; i < MaxNumParameters; ++ i )
if( stream->writeFlag( mParameters[ i ] ) )
stream->writeString( mParameters[ i ] );
}
//-----------------------------------------------------------------------------
void SFXTrack::unpackData( BitStream* stream )
{
Parent::unpackData( stream );
sfxRead( stream, &mDescription );
for( U32 i = 0; i < MaxNumParameters; ++ i )
if( stream->readFlag() )
mParameters[ i ] = stream->readSTString();
else
mParameters[ i ] = NULL;
}
//-----------------------------------------------------------------------------
bool SFXTrack::preload( bool server, String& errorStr )
{
if( !Parent::preload( server, errorStr ) )
return false;
if( !server )
{
if( !sfxResolve( &mDescription, errorStr ) )
return false;
}
return true;
}
//-----------------------------------------------------------------------------
bool SFXTrack::onAdd()
{
if( !Parent::onAdd() )
return false;
// If we have no SFXDescription, try to grab a default.
if( !mDescription )
{
if( !Sim::findObject( "AudioEffects", mDescription ) && Sim::getSFXDescriptionSet()->size() > 0 )
mDescription = dynamic_cast< SFXDescription* >( Sim::getSFXDescriptionSet()->at( 0 ) );
if( !mDescription )
{
Con::errorf(
"SFXTrack(%s)::onAdd: The profile is missing a description!",
getName() );
return false;
}
}
Sim::getSFXTrackSet()->addObject( this );
return true;
}
//-----------------------------------------------------------------------------
void SFXTrack::inspectPostApply()
{
Parent::inspectPostApply();
if( SFX )
SFX->notifyTrackChanged( this );
}

View file

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// 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 _SFXTRACK_H_
#define _SFXTRACK_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
class SFXDescription;
/// A datablock that describes sound data for playback.
class SFXTrack : public SimDataBlock
{
public:
typedef SimDataBlock Parent;
enum
{
/// Maximum numbers of parameters that can be pre-assigned to tracks.
MaxNumParameters = 8
};
protected:
/// The description which controls playback settings.
SFXDescription *mDescription;
/// Name of the parameters to which sources playing this track should
/// connect.
StringTableEntry mParameters[ MaxNumParameters ];
/// Overload this to disable direct instantiation of this class via script 'new'.
virtual bool processArguments( S32 argc, const char **argv );
public:
///
SFXTrack();
///
SFXTrack( SFXDescription* description );
/// Returns the description object for this sound profile.
SFXDescription* getDescription() const { return mDescription; }
///
StringTableEntry getParameter( U32 index ) const
{
AssertFatal( index < MaxNumParameters, "SFXTrack::getParameter() - index out of range" );
return mParameters[ index ];
}
///
void setParameter( U32 index, const char* name );
// SimDataBlock.
virtual void packData( BitStream* stream );
virtual void unpackData( BitStream* stream );
virtual bool preload( bool server, String& errorStr );
virtual bool onAdd();
virtual void inspectPostApply();
static void initPersistFields();
DECLARE_CONOBJECT( SFXTrack );
DECLARE_CATEGORY( "SFX" );
DECLARE_DESCRIPTION( "Abstract base class for any kind of data that can be turned into SFXSources." );
};
#endif // !_SFXTRACK_H_

View file

@ -0,0 +1,429 @@
//-----------------------------------------------------------------------------
// 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/sfxTypes.h"
#include "sfx/sfxDescription.h"
#include "sfx/sfxTrack.h"
#include "sfx/sfxEnvironment.h"
#include "sfx/sfxState.h"
#include "sfx/sfxAmbience.h"
#include "sfx/sfxSource.h"
#include "core/stringTable.h"
#include "core/stream/bitStream.h"
#include "platform/typetraits.h"
//RDTODO: find a more optimal way to transmit names rather than using full
// strings; don't know how to facilitate NetStrings for this as we don't
// have access to the connection
template< class T >
inline void sWrite( BitStream* stream, T* ptr )
{
if( stream->writeFlag( ptr != NULL ) )
{
if( stream->writeFlag( ptr->isClientOnly() ) )
stream->writeString( ptr->getName() );
else
stream->writeRangedU32( ptr->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
}
template< class T >
inline void sRead( BitStream* stream, T** ptr )
{
if( !stream->readFlag() )
*ptr = NULL;
else
{
if( stream->readFlag() )
{
StringTableEntry name = stream->readSTString();
AssertFatal( !( U32( name ) & 0x1 ), "sRead - misaligned pointer" ); // StringTableEntry pointers are always word-aligned.
*( ( StringTableEntry* ) ptr ) = name;
}
else
*reinterpret_cast< U32* >( ptr ) =
( stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ) << 1 ) | 0x1;
}
}
template< class T >
inline bool sResolve( T** ptr, String& errorString )
{
if( !*ptr )
return true;
else if( *reinterpret_cast< U32* >( ptr ) & 0x1 )
{
U32 id = *reinterpret_cast< U32* >( ptr ) >> 1;
T* p;
if( !Sim::findObject( id, p ) )
{
errorString = String::ToString( "sfxResolve - Could not resolve networked %s datalock with id '%i'",
T::getStaticClassRep()->getClassName(), id );
*ptr = NULL;
return false;
}
*ptr = p;
}
else
{
StringTableEntry name = *( ( StringTableEntry* ) ptr );
T* p;
if( !Sim::findObject( name, p ) )
{
errorString = String::ToString( "sfxResolve - Could not resolve local %s datablock with name '%s'",
T::getStaticClassRep()->getClassName(), name );
*ptr = NULL;
return false;
}
*ptr = p;
}
return true;
}
//=============================================================================
// TypeSFXSourceName.
//=============================================================================
ConsoleType( SFXSource, TypeSFXSourceName, SFXSource* )
ConsoleGetType( TypeSFXSourceName )
{
SFXSource** obj = ( SFXSource** ) dptr;
if( !*obj )
return "";
else
return Con::getReturnBuffer( ( *obj )->getName() );
}
ConsoleSetType( TypeSFXSourceName )
{
if( argc == 1 )
{
SFXSource** obj = ( SFXSource**) dptr;
Sim::findObject( argv[ 0 ], *obj );
}
else
Con::printf("(TypeSFXSourceName) Cannot set multiple args to a single SFXSource.");
}
//=============================================================================
// TypeSFXParameterName.
//=============================================================================
ConsoleType( string, TypeSFXParameterName, StringTableEntry )
ConsoleGetType( TypeSFXParameterName )
{
return *( ( const char** ) dptr );
}
ConsoleSetType( TypeSFXParameterName )
{
if( argc == 1 )
*( ( const char** ) dptr ) = StringTable->insert( argv[ 0 ] );
else
Con::errorf( "(TypeSFXParameterName) Cannot set multiple args to a single SFXParameter." );
}
//=============================================================================
// TypeSFXDescriptionName.
//=============================================================================
ConsoleType( SFXDescription, TypeSFXDescriptionName, SFXDescription* )
ConsoleSetType( TypeSFXDescriptionName )
{
if( argc == 1 )
{
SFXDescription* description;
Sim::findObject( argv[ 0 ], description );
*( ( SFXDescription** ) dptr ) = description;
}
else
Con::errorf( "(TypeSFXDescriptionName) Cannot set multiple args to a single SFXDescription.");
}
ConsoleGetType( TypeSFXDescriptionName )
{
SFXDescription* description = *( ( SFXDescription** ) dptr );
if( !description || !description->getName() )
return "";
else
return description->getName();
}
//=============================================================================
// TypeSFXTrackName.
//=============================================================================
ConsoleType( SFXTrack, TypeSFXTrackName, SFXTrack* )
ConsoleSetType( TypeSFXTrackName )
{
if( argc == 1 )
{
SFXTrack* track;
Sim::findObject( argv[ 0 ], track );
*( ( SFXTrack** ) dptr ) = track;
}
else
Con::errorf( "(TypeSFXTrackName) Cannot set multiple args to a single SFXTrack.");
}
ConsoleGetType( TypeSFXTrackName )
{
SFXTrack* track = *( ( SFXTrack** ) dptr );
if( !track || !track->getName() )
return "";
else
return track->getName();
}
//=============================================================================
// TypeSFXEnvironmentName.
//=============================================================================
ConsoleType( SFXEnvironment, TypeSFXEnvironmentName, SFXEnvironment* )
ConsoleSetType( TypeSFXEnvironmentName )
{
if( argc == 1 )
{
SFXEnvironment* environment;
Sim::findObject( argv[ 0 ], environment );
*( ( SFXEnvironment** ) dptr ) = environment;
}
else
Con::errorf( "(TypeSFXEnvironmentName) Cannot set multiple args to a single SFXEnvironment.");
}
ConsoleGetType( TypeSFXEnvironmentName )
{
SFXEnvironment* environment = *( ( SFXEnvironment** ) dptr );
if( !environment || !environment->getName() )
return "";
else
return environment->getName();
}
//=============================================================================
// TypeSFXStateName.
//=============================================================================
ConsoleType( SFXState, TypeSFXStateName, SFXState* )
ConsoleSetType( TypeSFXStateName )
{
if( argc == 1 )
{
SFXState* state;
Sim::findObject( argv[ 0 ], state );
*( ( SFXState** ) dptr ) = state;
}
else
Con::errorf( "(TypeSFXStateName) Cannot set multiple args to a single SFXState.");
}
ConsoleGetType( TypeSFXStateName )
{
SFXState* state = *( ( SFXState** ) dptr );
if( !state || !state->getName() )
return "";
else
return state->getName();
}
//=============================================================================
// TypeSFXAmbienceName.
//=============================================================================
ConsoleType( SFXAmbience, TypeSFXAmbienceName, SFXAmbience* )
ConsoleSetType( TypeSFXAmbienceName )
{
if( argc == 1 )
{
SFXAmbience* ambience;
Sim::findObject( argv[ 0 ], ambience );
*( ( SFXAmbience** ) dptr ) = ambience;
}
else
Con::errorf( "(TypeSFXAmbienceName) Cannot set multiple args to a single SFXAmbience.");
}
ConsoleGetType( TypeSFXAmbienceName )
{
SFXAmbience* ambience = *( ( SFXAmbience** ) dptr );
if( !ambience || !ambience->getName() )
return "";
else
return ambience->getName();
}
//=============================================================================
// I/O.
//=============================================================================
//-----------------------------------------------------------------------------
void sfxWrite( BitStream* stream, SFXSource* source )
{
if( stream->writeFlag( source != NULL ) )
stream->writeString( source->getName() );
}
//-----------------------------------------------------------------------------
void sfxWrite( BitStream* stream, SFXDescription* description )
{
sWrite( stream, description );
}
//-----------------------------------------------------------------------------
void sfxWrite( BitStream* stream, SFXTrack* track )
{
sWrite( stream, track );
}
//-----------------------------------------------------------------------------
void sfxWrite( BitStream* stream, SFXEnvironment* environment )
{
sWrite( stream, environment );
}
//-----------------------------------------------------------------------------
void sfxWrite( BitStream* stream, SFXState* state )
{
sWrite( stream, state );
}
//-----------------------------------------------------------------------------
void sfxWrite( BitStream* stream, SFXAmbience* ambience )
{
sWrite( stream, ambience );
}
//-----------------------------------------------------------------------------
void sfxRead( BitStream* stream, SFXDescription** description )
{
sRead( stream, description );
}
//-----------------------------------------------------------------------------
void sfxRead( BitStream* stream, SFXTrack** track )
{
sRead( stream, track );
}
//-----------------------------------------------------------------------------
void sfxRead( BitStream* stream, SFXEnvironment** environment )
{
sRead( stream, environment );
}
//-----------------------------------------------------------------------------
void sfxRead( BitStream* stream, SFXState** state )
{
sRead( stream, state );
}
//-----------------------------------------------------------------------------
void sfxRead( BitStream* stream, SFXAmbience** ambience )
{
sRead( stream, ambience );
}
//-----------------------------------------------------------------------------
bool sfxResolve( SFXDescription** description, String& errorString )
{
return sResolve( description, errorString );
}
//-----------------------------------------------------------------------------
bool sfxResolve( SFXTrack** track, String& errorString )
{
return sResolve( track, errorString );
}
//-----------------------------------------------------------------------------
bool sfxResolve( SFXEnvironment** environment, String& errorString )
{
return sResolve( environment, errorString );
}
//-----------------------------------------------------------------------------
bool sfxResolve( SFXState** state, String& errorString )
{
return sResolve( state, errorString );
}
//-----------------------------------------------------------------------------
bool sfxResolve( SFXAmbience** ambience, String& errorString )
{
return sResolve( ambience, errorString );
}
//-----------------------------------------------------------------------------
bool sfxReadAndResolve( BitStream* stream, SFXSource** source, String& errorString )
{
if( !stream->readFlag() )
{
*source = NULL;
return true;
}
const char* name = stream->readSTString();
SFXSource* object;
if( !Sim::findObject( name, object ) )
{
errorString = String::ToString( "sfxReadAndResolve - no SFXSource '%s'", name );
return false;
}
*source = object;
return true;
}

View file

@ -0,0 +1,140 @@
//-----------------------------------------------------------------------------
// 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 _SFXTYPES_H_
#define _SFXTYPES_H_
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
/// @file
/// Additional SFX console types.
class BitStream;
class SFXTrack;
class SFXEnvironment;
class SFXDescription;
class SFXState;
class SFXAmbience;
class SFXSource;
///
DefineConsoleType( TypeSFXSourceName, SFXSource* );
/// Name of an SFXParameter. Resolved to SFXParamter instances on the
/// client when creating a source.
///
/// This is a separate type so that the inspector can provide meaningful
/// popups.
DefineConsoleType( TypeSFXParameterName, StringTableEntry );
/// SFXDescription datablock reference. May be client-only.
DefineConsoleType( TypeSFXDescriptionName, SFXDescription* );
/// SFXTrack datablock reference. May be client-only.
DefineConsoleType( TypeSFXTrackName, SFXTrack* );
/// SFXEnvironment datablock reference. May be client-only.
DefineConsoleType( TypeSFXEnvironmentName, SFXEnvironment* );
/// SFXState datablock reference. May be client-only.
DefineConsoleType( TypeSFXStateName, SFXState* );
/// SFXAmbience datablock reference. May be client-only.
DefineConsoleType( TypeSFXAmbienceName, SFXAmbience* );
/// @name Datablock Network I/O
///
/// The following functions allow to transmit datablock references over the network
/// even when the referenced datablocks are not networked ("client-only" datablocks).
///
/// The write functions write a reference to the bitstream.
///
/// The read functions read a reference from the bitstream and store it in a non-resolved
/// form, i.e. the resulting pointer will not yet be valid. This is necessary as during
/// datablock transmission, the referenced datablock may actually only be transmitted
/// later in the stream and cannot thus be immediately looked up on the client.
///
/// The resolve functions take an unresolved datablock reference and resolve them to
/// valid pointers.
///
/// @{
void sfxWrite( BitStream* stream, SFXSource* source );
void sfxWrite( BitStream* stream, SFXDescription* description );
void sfxWrite( BitStream* stream, SFXTrack* track );
void sfxWrite( BitStream* stream, SFXEnvironment* environment );
void sfxWrite( BitStream* stream, SFXState* state );
void sfxWrite( BitStream* stream, SFXAmbience* ambience );
void sfxRead( BitStream* stream, SFXDescription** description );
void sfxRead( BitStream* stream, SFXTrack** track );
void sfxRead( BitStream* stream, SFXEnvironment** environment );
void sfxRead( BitStream* stream, SFXState** state );
void sfxRead( BitStream* stream, SFXAmbience** ambience );
bool sfxResolve( SFXDescription** description, String& errorString );
bool sfxResolve( SFXTrack** track, String& errorString );
bool sfxResolve( SFXEnvironment** environment, String& errorString );
bool sfxResolve( SFXState** state, String& errorString );
bool sfxResolve( SFXAmbience** ambience, String& errorString );
bool sfxReadAndResolve( BitStream* stream, SFXSource** source, String& errorString );
inline bool sfxReadAndResolve( BitStream* stream, SFXDescription** description, String& errorString )
{
sfxRead( stream, description );
return sfxResolve( description, errorString );
}
inline bool sfxReadAndResolve( BitStream* stream, SFXTrack** track, String& errorString )
{
sfxRead( stream, track );
return sfxResolve( track, errorString );
}
inline bool sfxReadAndResolve( BitStream* stream, SFXEnvironment** environment, String& errorString )
{
sfxRead( stream, environment );
return sfxResolve( environment, errorString );
}
inline bool sfxReadAndResolve( BitStream* stream, SFXState** state, String& errorString )
{
sfxRead( stream, state );
return sfxResolve( state, errorString );
}
inline bool sfxReadAndResolve( BitStream* stream, SFXAmbience** ambience, String& errorString )
{
sfxRead( stream, ambience );
return sfxResolve( ambience, errorString );
}
/// @}
#endif // !_SFXTYPES_H_

View 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();
}

View file

@ -0,0 +1,226 @@
//-----------------------------------------------------------------------------
// 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 _SFXVOICE_H_
#define _SFXVOICE_H_
#ifndef _REFBASE_H_
#include "core/util/refBase.h"
#endif
#ifndef _TSTREAM_H_
#include "core/stream/tStream.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
#ifndef _SFXBUFFER_H_
#include "sfx/sfxBuffer.h"
#endif
namespace SFXInternal {
class SFXVoiceTimeSource;
class SFXAynscQueue;
}
/// The voice interface provides for playback of sound buffers and positioning
/// of 3D sounds.
///
/// This abstract class is derived from in the different device layers to implement
/// device-specific playback control.
///
/// The primary responsibility of this class is to mediate between the user requests
/// (play(), stop(), pause(), setPosition()), the buffer (which may change state
/// asynchronously), and the underlying device playback control (_play(), _stop(),
/// _pause(), _seek()).
class SFXVoice : public StrongRefBase,
public IPositionable< U32 >
{
public:
typedef void Parent;
friend class SFXDevice; // _attachToBuffer
friend class SFXInternal::SFXVoiceTimeSource; // _tell
friend class SFXInternal::SFXAsyncQueue; // mOffset
protected:
/// Current playback status.
/// @note This is maintained on both the sound update thread as well
/// as the main thread.
mutable volatile SFXStatus mStatus;
/// Sound data played back by the voice.
WeakRefPtr< SFXBuffer > mBuffer;
/// For streaming voices, this keeps track of play start offset
/// after seeking. Expressed in number of samples.
U32 mOffset;
explicit SFXVoice( SFXBuffer* buffer );
/// @name Device Control Methods
/// @{
/// Return the current playback status (playing, paused, or stopped). Default
/// status is stopped.
virtual SFXStatus _status() const = 0;
/// Stop playback on the device.
/// @note Called from both the SFX update thread and the main thread.
virtual void _stop() = 0;
/// Start playback on the device.
/// @note Called from both the SFX update thread and the main thread.
virtual void _play() = 0;
/// Pause playback on the device.
/// @note Called from both the SFX update thread and the main thread.
virtual void _pause() = 0;
/// Set the playback cursor on the device.
/// @note Only used for non-streaming voices.
virtual void _seek( U32 sample ) = 0;
/// Get the playback cursor on the device.
///
/// When the voice is playing or paused, this method must return a valid sample position.
/// When the voice is stopped, the result of this method is undefined.
///
/// For streaming voices that are looping, the sample position must be a total count of the
/// number of samples played so far which thus includes the count of all cycles before the
/// current one. For non-looping voices, this behavior is optional.
///
/// @note This is called for both streaming and non-streaming voices.
virtual U32 _tell() const = 0;
/// @}
/// Hooked up to SFXBuffer::mOnStatusChange of #mBuffer.
/// @note Called on the SFX update thread.
virtual void _onBufferStatusChange( SFXBuffer* buffer, SFXBuffer::Status newStatus );
///
void _attachToBuffer();
/// @name Streaming
/// The following methods are for streaming voices only.
/// @{
/// Reset streaming of the voice by cloning the current streaming source and
/// letting the resulting stream start from @a sampleStartPos.
void _resetStream( U32 sampleStartPos, bool triggerUpdate = true );
/// @}
public:
static Signal< void( SFXVoice* ) > smVoiceCreatedSignal;
static Signal< void( SFXVoice* ) > smVoiceDestroyedSignal;
/// The destructor.
virtual ~SFXVoice();
///
const SFXFormat& getFormat() const { return mBuffer->getFormat(); }
/// Return the current playback position (in number of samples).
///
/// @note For looping sounds, this will return the position in the
/// current cycle and not the total number of samples played so far.
virtual U32 getPosition() const;
/// Sets the playback position to the given sample count.
///
/// @param sample Offset in number of samples. This is allowed to use an offset
/// accumulated from multiple cycles. Each cycle will wrap around back to the
/// beginning of the buffer.
virtual void setPosition( U32 sample );
/// @return the current playback status.
/// @note For streaming voices, the reaction to for the voice to update its status
/// to SFXStatusStopped after the voice has stopped playing depends on the synchronization
/// of the underlying device. If, for example, the underlying device uses periodic updates
/// and doesn't have notifications, then a delay up to the total length of the period time
/// may occur before the status changes. Note that in-between the actual playback stopping
/// and the voice updating its status, the result of getPosition() is undefined.
virtual SFXStatus getStatus() const;
/// Starts playback from the current position.
virtual void play( bool looping );
/// Stops playback and moves the position to the start.
virtual void stop();
/// Pauses playback.
virtual void pause();
/// Sets the position and orientation for a 3d voice.
virtual void setTransform( const MatrixF &transform ) = 0;
/// Sets the velocity for a 3d voice.
virtual void setVelocity( const VectorF &velocity ) = 0;
/// Sets the minimum and maximum distances for 3d falloff.
virtual void setMinMaxDistance( F32 min, F32 max ) = 0;
/// Set the distance attenuation rolloff factor. Support by device optional.
virtual void setRolloffFactor( F32 factor ) {}
/// Sets the volume.
virtual void setVolume( F32 volume ) = 0;
/// Sets the pitch scale.
virtual void setPitch( F32 pitch ) = 0;
/// Set sound cone of a 3D sound.
///
/// @param innerAngle Inner cone angle in degrees.
/// @param outerAngle Outer cone angle in degrees.
/// @param outerVolume Outer volume factor.
virtual void setCone( F32 innerAngle,
F32 outerAngle,
F32 outerVolume ) = 0;
/// Set the reverb properties for playback of this sound.
/// @note Has no effect on devices that do not support reverb.
virtual void setReverb( const SFXSoundReverbProperties& reverb ) {}
/// Set the priority of this voice. Default 1.0.
/// @note Has no effect on devices that do not support voice management.
virtual void setPriority( F32 priority ) {}
/// Returns true if the voice is virtualized on the device.
/// @note Always false on devices that do not support voice management.
virtual bool isVirtual() const { return false; }
};
#endif // _SFXVOICE_H_

View file

@ -0,0 +1,427 @@
//-----------------------------------------------------------------------------
// 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 _SFXWORLD_H_
#define _SFXWORLD_H_
#ifndef _SCOPETRACKER_H_
#include "util/scopeTracker.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _SFXSYSTEM_H_
#include "sfx/sfxSystem.h"
#endif
#ifndef _SFXSOUNDSCAPE_H_
#include "sfx/sfxSoundscape.h"
#endif
#ifndef _SFXAMBIENCE_H_
#include "sfx/sfxAmbience.h"
#endif
// Disable warning about unreferenced
// local function on VC.
#ifdef TORQUE_COMPILER_VISUALC
#pragma warning( disable: 4505 )
#endif
//#define DEBUG_SPEW
/// @file
/// System for representing and tracking changes to the SFX world of
/// sounds, sound spaces, occluders, and receivers.
///
/// This is system is abstracted over the number of dimensions it will
/// work with, so it is usable for both 2D and 3D.
///
/// To put it to use, it has to be connected to the engine's scene
/// graph structure by deriving suitable types from SFXObject.
class SFXAmbience;
/// Property flags for SFXObjects.
enum SFXObjectFlags
{
/// Object occludes sound.
SFXObjectOccluder = BIT( 0 ),
/// Object connects two ambient zones. Results in a continuous blend
/// between the two ambient zones as the listener travels through the
/// portal.
SFXObjectPortal = BIT( 1 ),
///
SFXObjectZone = BIT( 2 ),
/// Object is currently used as listener.
///
/// @note An object used as a listener will automatically have all
/// its other sound-related functionality (occlusion, zoning) ignored.
SFXObjectListener = BIT( 3 ),
///
SFXObjectEmitter = BIT( 4 ),
};
/// Information about an object in the SFX world.
///
/// SFXObjects are:
///
/// - Occluders: blocking sound
/// - Zones: ambient sound spaces
/// - Portals: connectors between zones
/// - Listeners: receiving sound
///
/// Note that emitters are not managed by the SFX world. Instead, the set of
/// 3D voices active on the device at any one point is defined as the set of
/// current sound sources.
///
template< int NUM_DIMENSIONS >
class SFXObject : public ScopeTrackerObject< NUM_DIMENSIONS >
{
public:
typedef ScopeTrackerObject< NUM_DIMENSIONS > Parent;
protected:
///
BitSet32 mFlags;
public:
///
SFXObject()
: Parent() {}
/// Return true if this object is only a sound occluder without any more ambient
/// sound properties. These kind of objects are not put into the tracking system.
bool isOccluderOnly() const { return ( isOccluder() && !isZone() ); }
/// Return true if this object is occluding sound.
bool isOccluder() const { return mFlags.test( SFXObjectOccluder ); }
/// Return true if this object connects two ambient spaces.
bool isPortal() const { return mFlags.test( SFXObjectPortal ); }
///
bool isZone() const { return mFlags.test( SFXObjectZone ); }
/// Return true if this object is currently being used as the listener.
/// @note All other sound-related properties (occlusion, ambience, etc.) will be ignored
/// on the listener object.
bool isListener() const { return mFlags.test( SFXObjectListener ); }
///
bool isEmitter() const { return mFlags.test( SFXObjectEmitter ); }
///
void setFlags( U32 bits ) { mFlags.set( bits ); }
///
void clearFlags( U32 bits ) { mFlags.clear( bits ); }
/// @name Implementor Interface
///
/// The following methods must be implemented by the client. They are defined here
/// just for reference. If you don't override them, you'll get link errors.
///
/// @{
/// Return the world-space position of the ears on this object.
/// This will only be called on objects that are made listeners.
void getReferenceCenter( F32 pos[ NUM_DIMENSIONS ] ) const;
/// Return the object's bounding box in world-space including its surround zone.
void getBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const;
/// Return the object's bounding box in world-space excluding its surround zone.
void getRealBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const;
/// Return true if the given point is contained in the object's volume.
bool containsPoint( const F32 point[ NUM_DIMENSIONS ] ) const;
/// Return the ambient space active within this object.
/// @note Portals cannot have their own ambient spaces.
SFXAmbience* getAmbience() const;
///
String describeSelf() const;
/// @}
};
/// SFXWorld tracks an N-dimensional world of SFXObjects.
///
///
/// This class uses two systems as back-ends: occlusion handling is passed on to the
/// occlusion manager installed on the system and tracking the listener traveling through
/// the ambient spaces is
///
template< int NUM_DIMENSIONS, typename Object >
class SFXWorld : public ScopeTracker< NUM_DIMENSIONS, Object >
{
public:
typedef ScopeTracker< NUM_DIMENSIONS, Object > Parent;
protected:
/// Record for objects that are currently in scope.
struct Scope
{
/// Sort key on scope stack. This is used to establish an ordering between
/// the ambient spaces that the listener is in concurrently.
F32 mSortValue;
/// The soundscape instance. Only objects for which the listener actually
/// is in one of the sound zones, will have an associated soundscape.
SFXSoundscape* mSoundscape;
/// The object defining this scope. If this is a portal, we transition
/// between this space and the space above us in the stack.
Object mObject;
Scope() {}
Scope( F32 sortValue, Object object )
: mSortValue( sortValue ),
mSoundscape( NULL ),
mObject( object ) {}
};
/// A top-down ordering of all objects that are currently in scope.
Vector< Scope > mScopeStack;
/// Return the index on the scope stack that is tied to the given object or -1 if
/// the object is not on the stack.
S32 _findScope( Object object );
///
void _sortScopes();
///
F32 _getSortValue( Object object );
// ScopeTracker.
virtual void _onScopeIn( Object object );
virtual void _onScopeOut( Object object );
public:
///
SFXWorld();
/// Update the state of the SFX world.
///
/// @note This method may potentially be costly; don't update every frame.
void update();
// ScopeTracker.
void notifyChanged( Object object );
};
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
SFXWorld< NUM_DIMENSIONS, Object >::SFXWorld()
{
VECTOR_SET_ASSOCIATION( mScopeStack );
}
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
void SFXWorld< NUM_DIMENSIONS, Object >::update()
{
if( !this->mReferenceObject )
return;
// Get the reference center (listener) pos.
F32 listenerPos[ NUM_DIMENSIONS ];
for( U32 n = 0; n < NUM_DIMENSIONS; n ++ )
listenerPos[ n ] = Deref( this->mReferenceObject ).getMinTrackingNode( n )->getPosition();
// Check all current scopes.
SFXSoundscapeManager* soundscapeMgr = SFX->getSoundscapeManager();
for( U32 i = 0; i < mScopeStack.size(); ++ i )
{
Scope& scope = mScopeStack[ i ];
if( Deref( scope.mObject ).containsPoint( listenerPos ) )
{
// Listener is in the object.
if( !scope.mSoundscape )
{
// Add the object's ambient space to the soundscape mix.
SFXAmbience* ambience = Deref( scope.mObject ).getAmbience();
AssertFatal( ambience != NULL, "SFXWorld::update() - object on stack that does not have an ambient space!" );
// Find the index at which to insert the ambient space. 0 is always
// the global space and each active soundscape lower down our stack
// increments by one.
U32 index = 1;
for( U32 j = 0; j < i; ++ j )
if( mScopeStack[ j ].mSoundscape )
++ index;
scope.mSoundscape = soundscapeMgr->insertSoundscape( index, ambience );
}
}
else
{
SFXAmbience* ambience = Deref( scope.mObject ).getAmbience();
AssertFatal( ambience != NULL, "SFXWorld::update() - object on stack that does not have an ambient space!" );
// Listener is neither inside object nor in its
// proximity zone. Kill its ambient soundscape if it
// has one.
if( scope.mSoundscape )
{
soundscapeMgr->removeSoundscape( scope.mSoundscape );
scope.mSoundscape = NULL;
}
}
}
}
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
void SFXWorld< NUM_DIMENSIONS, Object >::notifyChanged( Object object )
{
SFXAmbience* ambience = Deref( object ).getAmbience();
if( !ambience )
return;
for( U32 i = 0; i < mScopeStack.size(); ++ i )
{
Scope& scope = mScopeStack[ i ];
if( scope.mObject == object )
{
if( scope.mSoundscape )
scope.mSoundscape->setAmbience( ambience );
break;
}
}
}
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
void SFXWorld< NUM_DIMENSIONS, Object >::_onScopeIn( Object object )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXWorld] Object 0x%x in scope now", object );
#endif
// Get the object's ambience properties. If it has
// none, ignore the object.
SFXAmbience* ambience = Deref( object ).getAmbience();
if( !ambience )
return;
// Find where to insert the object into the stack.
typename Vector< Scope >::iterator iter = mScopeStack.begin();
F32 sortValue = _getSortValue( object );
while( iter != mScopeStack.end() && iter->mSortValue >= sortValue )
++ iter;
// Insert the object into the stack.
mScopeStack.insert( iter, Scope( sortValue, object ) );
}
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
void SFXWorld< NUM_DIMENSIONS, Object >::_onScopeOut( Object object )
{
#ifdef DEBUG_SPEW
Platform::outputDebugString( "[SFXWorld] Object 0x%x out of scope now", object );
#endif
// Find the object on the stack.
S32 index = _findScope( object );
if( index == -1 )
return;
// Remove its soundscape.
Scope& scope = mScopeStack[ index ];
if( scope.mSoundscape )
SFX->getSoundscapeManager()->removeSoundscape( scope.mSoundscape );
mScopeStack.erase( index );
}
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
F32 SFXWorld< NUM_DIMENSIONS, Object >::_getSortValue( Object object )
{
//RDTODO: probably need to work with the overlap here instead of the full volumes
// Get the real object bounds (without the surround zone).
F32 minBounds[ NUM_DIMENSIONS ], maxBounds[ NUM_DIMENSIONS ];
Deref( object ).getRealBounds( minBounds, maxBounds );
// Get the minimum extent.
F32 minExtent = maxBounds[ 0 ] - minBounds[ 0 ];
for( U32 n = 1; n < NUM_DIMENSIONS; ++ n )
minExtent = getMin( minExtent, maxBounds[ n ] - minBounds[ n ] );
return minExtent;
}
//-----------------------------------------------------------------------------
template< int NUM_DIMENSIONS, class Object >
S32 SFXWorld< NUM_DIMENSIONS, Object >::_findScope( Object object )
{
for( U32 i = 0; i < mScopeStack.size(); ++ i )
if( mScopeStack[ i ].mObject == object )
return i;
return -1;
}
#endif // !_SFXWORLD_H_

Some files were not shown because too many files have changed in this diff Show more