mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 04:34:48 +00:00
944 lines
29 KiB
C++
944 lines
29 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "sfx/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 );
|
|
}
|