mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-04-27 07:15:37 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
27
Engine/source/sfx/dsound/dsFunctions.h
Normal file
27
Engine/source/sfx/dsound/dsFunctions.h
Normal 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) )
|
||||
|
||||
277
Engine/source/sfx/dsound/sfxDSBuffer.cpp
Normal file
277
Engine/source/sfx/dsound/sfxDSBuffer.cpp
Normal 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) );
|
||||
}
|
||||
101
Engine/source/sfx/dsound/sfxDSBuffer.h
Normal file
101
Engine/source/sfx/dsound/sfxDSBuffer.h
Normal 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_
|
||||
229
Engine/source/sfx/dsound/sfxDSDevice.cpp
Normal file
229
Engine/source/sfx/dsound/sfxDSDevice.cpp
Normal 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.
|
||||
}
|
||||
142
Engine/source/sfx/dsound/sfxDSDevice.h
Normal file
142
Engine/source/sfx/dsound/sfxDSDevice.h
Normal 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_
|
||||
198
Engine/source/sfx/dsound/sfxDSProvider.cpp
Normal file
198
Engine/source/sfx/dsound/sfxDSProvider.cpp
Normal 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;
|
||||
}
|
||||
229
Engine/source/sfx/dsound/sfxDSVoice.cpp
Normal file
229
Engine/source/sfx/dsound/sfxDSVoice.cpp
Normal 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!" );
|
||||
}
|
||||
92
Engine/source/sfx/dsound/sfxDSVoice.h
Normal file
92
Engine/source/sfx/dsound/sfxDSVoice.h
Normal 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_
|
||||
115
Engine/source/sfx/fmod/fmodErrors.h
Normal file
115
Engine/source/sfx/fmod/fmodErrors.h
Normal 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 )
|
||||
157
Engine/source/sfx/fmod/fmodFunctions.h
Normal file
157
Engine/source/sfx/fmod/fmodFunctions.h
Normal 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" )
|
||||
322
Engine/source/sfx/fmod/sfxFMODBuffer.cpp
Normal file
322
Engine/source/sfx/fmod/sfxFMODBuffer.cpp
Normal 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;
|
||||
}
|
||||
63
Engine/source/sfx/fmod/sfxFMODBuffer.h
Normal file
63
Engine/source/sfx/fmod/sfxFMODBuffer.h
Normal 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_
|
||||
581
Engine/source/sfx/fmod/sfxFMODDevice.cpp
Normal file
581
Engine/source/sfx/fmod/sfxFMODDevice.cpp
Normal 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(¤t, &max);
|
||||
Con::printf("Fmod current: %d, max: %d", current, max);
|
||||
}
|
||||
345
Engine/source/sfx/fmod/sfxFMODDevice.h
Normal file
345
Engine/source/sfx/fmod/sfxFMODDevice.h
Normal 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_
|
||||
339
Engine/source/sfx/fmod/sfxFMODEvent.cpp
Normal file
339
Engine/source/sfx/fmod/sfxFMODEvent.cpp
Normal 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, ¶meter );
|
||||
|
||||
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() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Engine/source/sfx/fmod/sfxFMODEvent.h
Normal file
135
Engine/source/sfx/fmod/sfxFMODEvent.h
Normal 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_
|
||||
510
Engine/source/sfx/fmod/sfxFMODEventGroup.cpp
Normal file
510
Engine/source/sfx/fmod/sfxFMODEventGroup.cpp
Normal 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();
|
||||
}
|
||||
159
Engine/source/sfx/fmod/sfxFMODEventGroup.h
Normal file
159
Engine/source/sfx/fmod/sfxFMODEventGroup.h
Normal 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_
|
||||
337
Engine/source/sfx/fmod/sfxFMODEventSource.cpp
Normal file
337
Engine/source/sfx/fmod/sfxFMODEventSource.cpp
Normal 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() );
|
||||
}
|
||||
}
|
||||
101
Engine/source/sfx/fmod/sfxFMODEventSource.h
Normal file
101
Engine/source/sfx/fmod/sfxFMODEventSource.h
Normal 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_
|
||||
37
Engine/source/sfx/fmod/sfxFMODPlugin.cpp
Normal file
37
Engine/source/sfx/fmod/sfxFMODPlugin.cpp
Normal 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 );
|
||||
}
|
||||
48
Engine/source/sfx/fmod/sfxFMODPlugin.h
Normal file
48
Engine/source/sfx/fmod/sfxFMODPlugin.h
Normal 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_
|
||||
493
Engine/source/sfx/fmod/sfxFMODProject.cpp
Normal file
493
Engine/source/sfx/fmod/sfxFMODProject.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
162
Engine/source/sfx/fmod/sfxFMODProject.h
Normal file
162
Engine/source/sfx/fmod/sfxFMODProject.h
Normal 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_
|
||||
403
Engine/source/sfx/fmod/sfxFMODProvider.cpp
Normal file
403
Engine/source/sfx/fmod/sfxFMODProvider.cpp
Normal 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;
|
||||
}
|
||||
303
Engine/source/sfx/fmod/sfxFMODVoice.cpp
Normal file
303
Engine/source/sfx/fmod/sfxFMODVoice.cpp
Normal 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;
|
||||
}
|
||||
121
Engine/source/sfx/fmod/sfxFMODVoice.h
Normal file
121
Engine/source/sfx/fmod/sfxFMODVoice.h
Normal 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_
|
||||
283
Engine/source/sfx/media/sfxVorbisStream.cpp
Normal file
283
Engine/source/sfx/media/sfxVorbisStream.cpp
Normal 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
|
||||
112
Engine/source/sfx/media/sfxVorbisStream.h
Normal file
112
Engine/source/sfx/media/sfxVorbisStream.h
Normal 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_
|
||||
299
Engine/source/sfx/media/sfxWavStream.cpp
Normal file
299
Engine/source/sfx/media/sfxWavStream.cpp
Normal 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;
|
||||
}
|
||||
80
Engine/source/sfx/media/sfxWavStream.h
Normal file
80
Engine/source/sfx/media/sfxWavStream.h
Normal 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_
|
||||
43
Engine/source/sfx/null/sfxNullBuffer.cpp
Normal file
43
Engine/source/sfx/null/sfxNullBuffer.cpp
Normal 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 ] );
|
||||
}
|
||||
49
Engine/source/sfx/null/sfxNullBuffer.h
Normal file
49
Engine/source/sfx/null/sfxNullBuffer.h
Normal 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_
|
||||
74
Engine/source/sfx/null/sfxNullDevice.cpp
Normal file
74
Engine/source/sfx/null/sfxNullDevice.cpp
Normal 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.
|
||||
}
|
||||
63
Engine/source/sfx/null/sfxNullDevice.h
Normal file
63
Engine/source/sfx/null/sfxNullDevice.h
Normal 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_
|
||||
97
Engine/source/sfx/null/sfxNullProvider.cpp
Normal file
97
Engine/source/sfx/null/sfxNullProvider.cpp
Normal 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;
|
||||
}
|
||||
121
Engine/source/sfx/null/sfxNullVoice.cpp
Normal file
121
Engine/source/sfx/null/sfxNullVoice.cpp
Normal 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 )
|
||||
{
|
||||
}
|
||||
86
Engine/source/sfx/null/sfxNullVoice.h
Normal file
86
Engine/source/sfx/null/sfxNullVoice.h
Normal 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_
|
||||
209
Engine/source/sfx/openal/LoadOAL.h
Normal file
209
Engine/source/sfx/openal/LoadOAL.h
Normal 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_
|
||||
314
Engine/source/sfx/openal/aldlist.cpp
Normal file
314
Engine/source/sfx/openal/aldlist.cpp
Normal 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;
|
||||
}
|
||||
70
Engine/source/sfx/openal/aldlist.h
Normal file
70
Engine/source/sfx/openal/aldlist.h
Normal 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
|
||||
445
Engine/source/sfx/openal/mac/LoadOAL.mac.cpp
Normal file
445
Engine/source/sfx/openal/mac/LoadOAL.mac.cpp
Normal 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.
|
||||
}
|
||||
238
Engine/source/sfx/openal/sfxALBuffer.cpp
Normal file
238
Engine/source/sfx/openal/sfxALBuffer.cpp
Normal 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
|
||||
}
|
||||
119
Engine/source/sfx/openal/sfxALBuffer.h
Normal file
119
Engine/source/sfx/openal/sfxALBuffer.h
Normal 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_
|
||||
40
Engine/source/sfx/openal/sfxALCaps.h
Normal file
40
Engine/source/sfx/openal/sfxALCaps.h
Normal 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
|
||||
199
Engine/source/sfx/openal/sfxALDevice.cpp
Normal file
199
Engine/source/sfx/openal/sfxALDevice.cpp
Normal 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;
|
||||
}
|
||||
90
Engine/source/sfx/openal/sfxALDevice.h
Normal file
90
Engine/source/sfx/openal/sfxALDevice.h
Normal 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_
|
||||
146
Engine/source/sfx/openal/sfxALProvider.cpp
Normal file
146
Engine/source/sfx/openal/sfxALProvider.cpp
Normal 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;
|
||||
}
|
||||
257
Engine/source/sfx/openal/sfxALVoice.cpp
Normal file
257
Engine/source/sfx/openal/sfxALVoice.cpp
Normal 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 );
|
||||
}
|
||||
107
Engine/source/sfx/openal/sfxALVoice.h
Normal file
107
Engine/source/sfx/openal/sfxALVoice.h
Normal 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_
|
||||
454
Engine/source/sfx/openal/win32/LoadOAL.cpp
Normal file
454
Engine/source/sfx/openal/win32/LoadOAL.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
212
Engine/source/sfx/sfxAmbience.cpp
Normal file
212
Engine/source/sfx/sfxAmbience.cpp
Normal 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()
|
||||
{
|
||||
}
|
||||
120
Engine/source/sfx/sfxAmbience.h
Normal file
120
Engine/source/sfx/sfxAmbience.h
Normal 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_
|
||||
291
Engine/source/sfx/sfxBuffer.cpp
Normal file
291
Engine/source/sfx/sfxBuffer.cpp
Normal 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 );
|
||||
}
|
||||
236
Engine/source/sfx/sfxBuffer.h
Normal file
236
Engine/source/sfx/sfxBuffer.h
Normal 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_
|
||||
594
Engine/source/sfx/sfxCommon.h
Normal file
594
Engine/source/sfx/sfxCommon.h
Normal 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_
|
||||
943
Engine/source/sfx/sfxController.cpp
Normal file
943
Engine/source/sfx/sfxController.cpp
Normal 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 );
|
||||
}
|
||||
220
Engine/source/sfx/sfxController.h
Normal file
220
Engine/source/sfx/sfxController.h
Normal 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_
|
||||
|
||||
654
Engine/source/sfx/sfxDescription.cpp
Normal file
654
Engine/source/sfx/sfxDescription.cpp
Normal 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 );
|
||||
}
|
||||
198
Engine/source/sfx/sfxDescription.h
Normal file
198
Engine/source/sfx/sfxDescription.h
Normal 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_
|
||||
210
Engine/source/sfx/sfxDevice.cpp
Normal file
210
Engine/source/sfx/sfxDevice.cpp
Normal 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 );
|
||||
}
|
||||
}
|
||||
211
Engine/source/sfx/sfxDevice.h
Normal file
211
Engine/source/sfx/sfxDevice.h
Normal 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_
|
||||
326
Engine/source/sfx/sfxEnvironment.cpp
Normal file
326
Engine/source/sfx/sfxEnvironment.cpp
Normal 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 );
|
||||
}
|
||||
73
Engine/source/sfx/sfxEnvironment.h
Normal file
73
Engine/source/sfx/sfxEnvironment.h
Normal 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_
|
||||
173
Engine/source/sfx/sfxFileStream.cpp
Normal file
173
Engine/source/sfx/sfxFileStream.cpp
Normal 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;
|
||||
}
|
||||
115
Engine/source/sfx/sfxFileStream.h
Normal file
115
Engine/source/sfx/sfxFileStream.h
Normal 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_
|
||||
200
Engine/source/sfx/sfxInternal.cpp
Normal file
200
Engine/source/sfx/sfxInternal.cpp
Normal 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
|
||||
456
Engine/source/sfx/sfxInternal.h
Normal file
456
Engine/source/sfx/sfxInternal.h
Normal 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_
|
||||
117
Engine/source/sfx/sfxMemoryStream.cpp
Normal file
117
Engine/source/sfx/sfxMemoryStream.cpp
Normal 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;
|
||||
}
|
||||
84
Engine/source/sfx/sfxMemoryStream.h
Normal file
84
Engine/source/sfx/sfxMemoryStream.h
Normal 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_
|
||||
194
Engine/source/sfx/sfxModifier.cpp
Normal file
194
Engine/source/sfx/sfxModifier.cpp
Normal 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() );
|
||||
}
|
||||
191
Engine/source/sfx/sfxModifier.h
Normal file
191
Engine/source/sfx/sfxModifier.h
Normal 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_
|
||||
350
Engine/source/sfx/sfxParameter.cpp
Normal file
350
Engine/source/sfx/sfxParameter.cpp
Normal 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();
|
||||
}
|
||||
171
Engine/source/sfx/sfxParameter.h
Normal file
171
Engine/source/sfx/sfxParameter.h
Normal 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_
|
||||
|
||||
468
Engine/source/sfx/sfxPlayList.cpp
Normal file
468
Engine/source/sfx/sfxPlayList.cpp
Normal 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();
|
||||
}
|
||||
356
Engine/source/sfx/sfxPlayList.h
Normal file
356
Engine/source/sfx/sfxPlayList.h
Normal 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_
|
||||
374
Engine/source/sfx/sfxProfile.cpp
Normal file
374
Engine/source/sfx/sfxProfile.cpp
Normal 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;
|
||||
}
|
||||
181
Engine/source/sfx/sfxProfile.h
Normal file
181
Engine/source/sfx/sfxProfile.h
Normal 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_
|
||||
95
Engine/source/sfx/sfxProvider.cpp
Normal file
95
Engine/source/sfx/sfxProvider.cpp
Normal 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;
|
||||
}
|
||||
124
Engine/source/sfx/sfxProvider.h
Normal file
124
Engine/source/sfx/sfxProvider.h
Normal 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_
|
||||
82
Engine/source/sfx/sfxResource.cpp
Normal file
82
Engine/source/sfx/sfxResource.cpp
Normal 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 );
|
||||
}
|
||||
114
Engine/source/sfx/sfxResource.h
Normal file
114
Engine/source/sfx/sfxResource.h
Normal 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_
|
||||
708
Engine/source/sfx/sfxSound.cpp
Normal file
708
Engine/source/sfx/sfxSound.cpp
Normal 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;
|
||||
}
|
||||
166
Engine/source/sfx/sfxSound.h
Normal file
166
Engine/source/sfx/sfxSound.h
Normal 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_
|
||||
450
Engine/source/sfx/sfxSoundscape.cpp
Normal file
450
Engine/source/sfx/sfxSoundscape.cpp
Normal 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
|
||||
}
|
||||
}
|
||||
195
Engine/source/sfx/sfxSoundscape.h
Normal file
195
Engine/source/sfx/sfxSoundscape.h
Normal 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_
|
||||
1720
Engine/source/sfx/sfxSource.cpp
Normal file
1720
Engine/source/sfx/sfxSource.cpp
Normal file
File diff suppressed because it is too large
Load diff
671
Engine/source/sfx/sfxSource.h
Normal file
671
Engine/source/sfx/sfxSource.h
Normal 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_
|
||||
397
Engine/source/sfx/sfxState.cpp
Normal file
397
Engine/source/sfx/sfxState.cpp
Normal 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 );
|
||||
}
|
||||
120
Engine/source/sfx/sfxState.h
Normal file
120
Engine/source/sfx/sfxState.h
Normal 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_
|
||||
86
Engine/source/sfx/sfxStream.h
Normal file
86
Engine/source/sfx/sfxStream.h
Normal 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_
|
||||
1883
Engine/source/sfx/sfxSystem.cpp
Normal file
1883
Engine/source/sfx/sfxSystem.cpp
Normal file
File diff suppressed because it is too large
Load diff
450
Engine/source/sfx/sfxSystem.h
Normal file
450
Engine/source/sfx/sfxSystem.h
Normal 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_
|
||||
188
Engine/source/sfx/sfxTrack.cpp
Normal file
188
Engine/source/sfx/sfxTrack.cpp
Normal 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 );
|
||||
}
|
||||
97
Engine/source/sfx/sfxTrack.h
Normal file
97
Engine/source/sfx/sfxTrack.h
Normal 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_
|
||||
429
Engine/source/sfx/sfxTypes.cpp
Normal file
429
Engine/source/sfx/sfxTypes.cpp
Normal 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;
|
||||
}
|
||||
140
Engine/source/sfx/sfxTypes.h
Normal file
140
Engine/source/sfx/sfxTypes.h
Normal 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_
|
||||
420
Engine/source/sfx/sfxVoice.cpp
Normal file
420
Engine/source/sfx/sfxVoice.cpp
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "sfx/sfxVoice.h"
|
||||
#include "sfx/sfxBuffer.h"
|
||||
#include "sfx/sfxInternal.h"
|
||||
#include "console/console.h"
|
||||
|
||||
|
||||
// [rene, 07-May-11] The interplay between SFXBuffer and SFXVoice here isn't good.
|
||||
// Too complex, and while it works reliably in most cases, when doing seeks
|
||||
// on streaming sources, it is prone to subtle timing dependencies.
|
||||
|
||||
|
||||
//#define DEBUG_SPEW
|
||||
|
||||
|
||||
Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceCreatedSignal;
|
||||
Signal< void( SFXVoice* voice ) > SFXVoice::smVoiceDestroyedSignal;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SFXVoice::SFXVoice( SFXBuffer* buffer )
|
||||
: mBuffer( buffer ),
|
||||
mStatus( SFXStatusNull ),
|
||||
mOffset( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SFXVoice::~SFXVoice()
|
||||
{
|
||||
smVoiceDestroyedSignal.trigger( this );
|
||||
|
||||
if( mBuffer )
|
||||
mBuffer->mOnStatusChange.remove( this, &SFXVoice::_onBufferStatusChange );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::_attachToBuffer()
|
||||
{
|
||||
using namespace SFXInternal;
|
||||
|
||||
// If the buffer is unique, attach us as its unique voice.
|
||||
|
||||
if( mBuffer->isUnique() )
|
||||
{
|
||||
AssertFatal( !mBuffer->mUniqueVoice,
|
||||
"SFXVoice::SFXVoice - streaming buffer already is assigned a voice" );
|
||||
|
||||
mBuffer->mUniqueVoice = this;
|
||||
|
||||
// The buffer can start its queuing now so give it a chance
|
||||
// to run an update.
|
||||
SFXInternal::TriggerUpdate();
|
||||
}
|
||||
|
||||
mBuffer->mOnStatusChange.notify( this, &SFXVoice::_onBufferStatusChange );
|
||||
|
||||
smVoiceCreatedSignal.trigger( this );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::_onBufferStatusChange( SFXBuffer* buffer, SFXBuffer::Status newStatus )
|
||||
{
|
||||
AssertFatal( buffer == mBuffer, "SFXVoice::_onBufferStatusChange() - got an invalid buffer" );
|
||||
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Buffer changes status to: %s",
|
||||
newStatus == SFXBuffer::STATUS_Null ? "STATUS_Null" :
|
||||
newStatus == SFXBuffer::STATUS_Loading ? "STATUS_Loading" :
|
||||
newStatus == SFXBuffer::STATUS_Ready ? "STATUS_Ready" :
|
||||
newStatus == SFXBuffer::STATUS_Blocked ? "STATUS_Blocked" :
|
||||
newStatus == SFXBuffer::STATUS_AtEnd ? "STATUS_AtEnd" : "unknown" );
|
||||
#endif
|
||||
|
||||
// This is called concurrently!
|
||||
|
||||
switch( newStatus )
|
||||
{
|
||||
case SFXBuffer::STATUS_Loading:
|
||||
// Can ignore this. Buffer simply lets us know it has started
|
||||
// its initial stream load.
|
||||
break;
|
||||
|
||||
case SFXBuffer::STATUS_AtEnd:
|
||||
|
||||
// Streaming voice has played to end of stream.
|
||||
|
||||
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) )
|
||||
{
|
||||
_stop();
|
||||
mOffset = 0;
|
||||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusStopped );
|
||||
}
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Voice stopped as end of stream reached" );
|
||||
#endif
|
||||
break;
|
||||
|
||||
case SFXBuffer::STATUS_Blocked:
|
||||
|
||||
// Streaming has fallen behind.
|
||||
|
||||
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) )
|
||||
{
|
||||
_pause();
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusBlocked );
|
||||
}
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Voice waiting for buffer to catch up" );
|
||||
#endif
|
||||
break;
|
||||
|
||||
case SFXBuffer::STATUS_Ready:
|
||||
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
|
||||
{
|
||||
// Get the playback going again.
|
||||
|
||||
_play();
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPlaying );
|
||||
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Buffer caught up with voice" );
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case SFXBuffer::STATUS_Null:
|
||||
AssertFatal( false, "SFXVoice::_onBufferStatusChange - Buffer changed to invalid NULL status" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SFXStatus SFXVoice::getStatus() const
|
||||
{
|
||||
// Detect when the device has finished playback. Only for
|
||||
// non-streaming voices. For streaming voices, we rely on
|
||||
// the buffer to send us a STATUS_AtEnd signal when it is
|
||||
// done playing.
|
||||
|
||||
if( mStatus == SFXStatusPlaying &&
|
||||
!mBuffer->isStreaming() &&
|
||||
_status() == SFXStatusStopped )
|
||||
mStatus = SFXStatusStopped;
|
||||
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::play( bool looping )
|
||||
{
|
||||
AssertFatal( mBuffer != NULL, "SFXVoice::play() - no buffer" );
|
||||
using namespace SFXInternal;
|
||||
|
||||
// For streaming, check whether we have played previously.
|
||||
// If so, reset the buffer's stream.
|
||||
|
||||
if( mBuffer->isStreaming() &&
|
||||
mStatus == SFXStatusStopped )
|
||||
_resetStream( 0 );
|
||||
|
||||
// Now switch state.
|
||||
|
||||
while( mStatus != SFXStatusPlaying &&
|
||||
mStatus != SFXStatusBlocked )
|
||||
{
|
||||
if( !mBuffer->isReady() &&
|
||||
( dCompareAndSwap( ( U32& ) mStatus, SFXStatusNull, SFXStatusBlocked ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusStopped, SFXStatusBlocked ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusBlocked ) ) )
|
||||
{
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Wanted to start playback but buffer isn't ready" );
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
else if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusNull, SFXStatusTransition ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusStopped, SFXStatusTransition ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusTransition ) )
|
||||
{
|
||||
_play();
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPlaying );
|
||||
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Started playback" );
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::pause()
|
||||
{
|
||||
while( mStatus != SFXStatusPaused &&
|
||||
mStatus != SFXStatusNull &&
|
||||
mStatus != SFXStatusStopped )
|
||||
{
|
||||
if( dCompareAndSwap( ( U32& ) mStatus, SFXStatusPlaying, SFXStatusTransition ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
|
||||
{
|
||||
_pause();
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusPaused );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::stop()
|
||||
{
|
||||
while( mStatus != SFXStatusStopped &&
|
||||
mStatus != SFXStatusNull )
|
||||
{
|
||||
if( dCompareAndSwap( ( U32& ) mStatus, ( U32 ) SFXStatusPlaying, ( U32 ) SFXStatusTransition ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusPaused, SFXStatusTransition ) ||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusBlocked, SFXStatusTransition ) )
|
||||
{
|
||||
_stop();
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, SFXStatusStopped );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
U32 SFXVoice::getPosition() const
|
||||
{
|
||||
// When stopped, always return 0.
|
||||
|
||||
if( getStatus() == SFXStatusStopped )
|
||||
return 0;
|
||||
|
||||
// It depends on the device if and when it will return a count of the total samples
|
||||
// played so far. With streaming buffers, all devices will do that. With non-streaming
|
||||
// buffers, some may do for looping voices thus returning a number that exceeds the actual
|
||||
// source stream size. So, clamp things into range here and also take care of any offsetting
|
||||
// resulting from a setPosition() call.
|
||||
|
||||
U32 pos = _tell() + mOffset;
|
||||
const U32 numStreamSamples = mBuffer->getFormat().getSampleCount( mBuffer->getDuration() );
|
||||
|
||||
if( mBuffer->mIsLooping )
|
||||
pos %= numStreamSamples;
|
||||
else if( pos > numStreamSamples )
|
||||
{
|
||||
// Ensure we never report out-of-range positions even if the device does.
|
||||
|
||||
pos = numStreamSamples;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::setPosition( U32 inSample )
|
||||
{
|
||||
// Clamp to sample range.
|
||||
const U32 sample = inSample % ( mBuffer->getFormat().getSampleCount( mBuffer->getDuration() ) - 1 );
|
||||
|
||||
// Don't perform a seek when we already are at the
|
||||
// given position. Especially avoids a costly stream
|
||||
// clone when seeking on a streamed voice.
|
||||
|
||||
if( getPosition() == sample )
|
||||
return;
|
||||
|
||||
if( !mBuffer->isStreaming() )
|
||||
{
|
||||
// Non-streaming sound. Just seek in the device buffer.
|
||||
|
||||
_seek( sample );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Streaming sound. Reset the stream and playback.
|
||||
//
|
||||
// Unfortunately, the logic here is still prone to subtle timing dependencies
|
||||
// in relation to the buffer updates. In retrospect, I feel that solving all issues
|
||||
// of asynchronous operation on a per-voice/buffer level has greatly complicated
|
||||
// the system. It seems now that it would have been a lot simpler to have a single
|
||||
// asynchronous buffer/voice manager that manages the updates of all voices and buffers
|
||||
// currently in the system in one spot. Packet reads could still be pushed out to
|
||||
// the thread pool but queue updates would all be handled centrally in one spot. This
|
||||
// would do away with problems like those (mostly) solved by the multi-step procedure
|
||||
// here.
|
||||
|
||||
// Go into transition.
|
||||
|
||||
SFXStatus oldStatus;
|
||||
while( true )
|
||||
{
|
||||
oldStatus = mStatus;
|
||||
if( oldStatus != SFXStatusTransition &&
|
||||
dCompareAndSwap( ( U32& ) mStatus, oldStatus, SFXStatusTransition ) )
|
||||
break;
|
||||
}
|
||||
|
||||
// Switch the stream.
|
||||
|
||||
_resetStream( sample, false );
|
||||
|
||||
// Come out of transition.
|
||||
|
||||
SFXStatus newStatus = oldStatus;
|
||||
if( oldStatus == SFXStatusPlaying )
|
||||
newStatus = SFXStatusBlocked;
|
||||
|
||||
dCompareAndSwap( ( U32& ) mStatus, SFXStatusTransition, newStatus );
|
||||
|
||||
// Trigger an update.
|
||||
|
||||
SFXInternal::TriggerUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void SFXVoice::_resetStream( U32 sampleStartPos, bool triggerUpdate )
|
||||
{
|
||||
AssertFatal( mBuffer->isStreaming(), "SFXVoice::_resetStream - Not a streaming voice!" );
|
||||
|
||||
ThreadSafeRef< SFXBuffer::AsyncState > oldState = mBuffer->mAsyncState;
|
||||
AssertFatal( oldState != NULL,
|
||||
"SFXVoice::_resetStream() - streaming buffer must have valid async state" );
|
||||
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[SFXVoice] Resetting stream to %i", sampleStartPos );
|
||||
#endif
|
||||
|
||||
// Rather than messing up the async code by adding repositioning (which
|
||||
// further complicates synchronizing the various parts), just construct
|
||||
// a complete new AsyncState and discard the old one. The only problem
|
||||
// here is the stateful sound streams. We can't issue a new packet as long
|
||||
// as we aren't sure there's no request pending, so we just clone the stream
|
||||
// and leave the old one to the old AsyncState.
|
||||
|
||||
ThreadSafeRef< SFXStream > sfxStream = oldState->mStream->getSourceStream()->clone();
|
||||
if( sfxStream == NULL )
|
||||
{
|
||||
Con::errorf( "SFXVoice::_resetStream - could not clone SFXStream" );
|
||||
return;
|
||||
}
|
||||
|
||||
IPositionable< U32 >* sfxPositionable = dynamic_cast< IPositionable< U32 >* >( sfxStream.ptr() );
|
||||
if( !sfxPositionable )
|
||||
{
|
||||
Con::errorf( "SFXVoice::_resetStream - could not seek in SFXStream" );
|
||||
return;
|
||||
}
|
||||
|
||||
sfxPositionable->setPosition( sampleStartPos * sfxStream->getFormat().getBytesPerSample() );
|
||||
|
||||
ThreadSafeRef< SFXInternal::SFXAsyncStream > newStream =
|
||||
new SFXInternal::SFXAsyncStream
|
||||
( sfxStream,
|
||||
true,
|
||||
oldState->mStream->getPacketDuration() / 1000,
|
||||
oldState->mStream->getReadAhead(),
|
||||
oldState->mStream->isLooping() );
|
||||
newStream->setReadSilenceAtEnd( oldState->mStream->getReadSilenceAtEnd() );
|
||||
|
||||
AssertFatal( newStream->getPacketSize() == oldState->mStream->getPacketSize(),
|
||||
"SFXVoice::setPosition() - packet size mismatch with new stream" );
|
||||
|
||||
ThreadSafeRef< SFXBuffer::AsyncState > newState =
|
||||
new SFXBuffer::AsyncState( newStream );
|
||||
newStream->start();
|
||||
|
||||
// Switch the states.
|
||||
|
||||
mOffset = sampleStartPos;
|
||||
mBuffer->mAsyncState = newState;
|
||||
|
||||
// Stop the old state from reading more data.
|
||||
|
||||
oldState->mStream->stop();
|
||||
|
||||
// Trigger update.
|
||||
|
||||
if( triggerUpdate )
|
||||
SFXInternal::TriggerUpdate();
|
||||
}
|
||||
226
Engine/source/sfx/sfxVoice.h
Normal file
226
Engine/source/sfx/sfxVoice.h
Normal 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_
|
||||
427
Engine/source/sfx/sfxWorld.h
Normal file
427
Engine/source/sfx/sfxWorld.h
Normal 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
Loading…
Add table
Add a link
Reference in a new issue