123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- //-----------------------------------------------------------------------------
- // 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/xaudio/sfxXAudioVoice.h"
- #include "sfx/xaudio/sfxXAudioDevice.h"
- #include "sfx/xaudio/sfxXAudioBuffer.h"
- #include "core/util/safeDelete.h"
- #include "math/mMathFn.h"
- //#define DEBUG_SPEW
- static void sfxFormatToWAVEFORMATEX( const SFXFormat& format, 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;
- }
- SFXXAudioVoice* SFXXAudioVoice::create( IXAudio2 *xaudio,
- bool is3D,
- SFXXAudioBuffer *buffer,
- SFXXAudioVoice* inVoice )
- {
- AssertFatal( xaudio, "SFXXAudioVoice::create() - Got null XAudio!" );
- AssertFatal( buffer, "SFXXAudioVoice::create() - Got null buffer!" );
- // Create the voice object first as it also the callback object.
- SFXXAudioVoice* voice = inVoice;
- if( !voice )
- voice = new SFXXAudioVoice( buffer );
- // Get the buffer format.
- WAVEFORMATEX wfx;
- sfxFormatToWAVEFORMATEX( buffer->getFormat(), &wfx );
- // We don't support multi-channel 3d sounds!
- if ( is3D && wfx.nChannels > 1 )
- return NULL;
- // Create the voice.
- IXAudio2SourceVoice *xaVoice;
- HRESULT hr = xaudio->CreateSourceVoice( &xaVoice,
- (WAVEFORMATEX*)&wfx,
- 0,
- XAUDIO2_DEFAULT_FREQ_RATIO,
- voice,
- NULL,
- NULL );
- if( FAILED( hr ) || !voice )
- {
- if( !inVoice )
- delete voice;
- return NULL;
- }
- voice->mIs3D = is3D;
- voice->mEmitter.ChannelCount = wfx.nChannels;
- voice->mXAudioVoice = xaVoice;
- return voice;
- }
- SFXXAudioVoice::SFXXAudioVoice( SFXXAudioBuffer* buffer )
- : Parent( buffer ),
- mXAudioDevice( NULL ),
- mXAudioVoice( NULL ),
- mIs3D( false ),
- mPitch( 1.0f ),
- mHasStopped( false ),
- mHasStarted( false ),
- mIsLooping( false ),
- mIsPlaying( false ),
- mNonStreamSampleStartPos( 0 ),
- mNonStreamBufferLoaded( false ),
- mSamplesPlayedOffset( 0 )
- {
- dMemset( &mEmitter, 0, sizeof( mEmitter ) );
- mEmitter.DopplerScaler = 1.0f;
- InitializeCriticalSection( &mLock );
- }
- SFXXAudioVoice::~SFXXAudioVoice()
- {
- if ( mEmitter.pVolumeCurve )
- {
- SAFE_DELETE_ARRAY( mEmitter.pVolumeCurve->pPoints );
- SAFE_DELETE( mEmitter.pVolumeCurve );
- }
- SAFE_DELETE( mEmitter.pCone );
- if ( mXAudioVoice )
- mXAudioVoice->DestroyVoice();
- DeleteCriticalSection( &mLock );
- }
- SFXStatus SFXXAudioVoice::_status() const
- {
- if( mHasStopped )
- return SFXStatusStopped;
- else if( mHasStarted )
- {
- if( !mIsPlaying )
- return SFXStatusPaused;
- else
- return SFXStatusPlaying;
- }
- else
- return SFXStatusStopped;
- }
- void SFXXAudioVoice::_flush()
- {
- AssertFatal( mXAudioVoice != NULL,
- "SFXXAudioVoice::_flush() - invalid voice" );
- EnterCriticalSection( &mLock );
- mXAudioVoice->Stop( 0 );
- mXAudioVoice->FlushSourceBuffers();
- mNonStreamBufferLoaded = false;
- #ifdef DEBUG_SPEW
- Platform::outputDebugString( "[SFXXAudioVoice] Flushed state" );
- #endif
- mIsPlaying = false;
- mHasStarted = false;
- mHasStopped = true;
- //WORKAROUND: According to the docs, SamplesPlayed reported by the
- // voice should get reset as soon as we submit a new buffer to the voice.
- // Alas it won't. So, save the current value here and offset our future
- // play cursors.
- XAUDIO2_VOICE_STATE state;
- mXAudioVoice->GetState( &state );
- mSamplesPlayedOffset = - S32( state.SamplesPlayed );
- LeaveCriticalSection( &mLock );
- }
- void SFXXAudioVoice::_play()
- {
- AssertFatal( mXAudioVoice != NULL,
- "SFXXAudioVoice::_play() - invalid voice" );
- // For non-streaming voices queue the data if we haven't yet.
- if( !mBuffer->isStreaming() && !mNonStreamBufferLoaded )
- _loadNonStreamed();
- // Start playback.
- mXAudioVoice->Start( 0, 0 );
- #ifdef DEBUG_SPEW
- Platform::outputDebugString( "[SFXXAudioVoice] Started playback" );
- #endif
- mIsPlaying = true;
- mHasStarted = true;
- mHasStopped = false;
- }
- void SFXXAudioVoice::_pause()
- {
- AssertFatal( mXAudioVoice != NULL,
- "SFXXAudioVoice::_pause() - invalid voice" );
- mXAudioVoice->Stop( 0 );
- mIsPlaying = false;
- #ifdef DEBUG_SPEW
- Platform::outputDebugString( "[SFXXAudioVoice] Paused playback" );
- #endif
- }
- void SFXXAudioVoice::_stop()
- {
- AssertFatal( mXAudioVoice != NULL,
- "SFXXAudioVoice::_stop() - invalid voice" );
- _flush();
- mIsPlaying = false;
- mHasStarted = false;
- mHasStopped = true;
- #ifdef DEBUG_SPEW
- Platform::outputDebugString( "[SFXXAudioVoice] Stopped playback" );
- #endif
- }
- void SFXXAudioVoice::_seek( U32 sample )
- {
- #ifdef DEBUG_SPEW
- Platform::outputDebugString( "[SFXXAudioVoice] Seeking to %i", sample );
- #endif
- mNonStreamSampleStartPos = sample;
- bool wasPlaying = mIsPlaying;
-
- _stop();
- if( wasPlaying )
- _play();
- }
- void SFXXAudioVoice::_loadNonStreamed()
- {
- AssertFatal( !mBuffer->isStreaming(), "SFXXAudioVoice::_loadNonStreamed - must not be called on streaming voices" );
- AssertFatal( mXAudioVoice != NULL, "SFXXAudioVoice::_loadNonStreamed - invalid voice" );
- AssertWarn( !mNonStreamBufferLoaded, "SFXXAudioVoice::_nonStreamNonstreamed - Data already loaded" );
- #ifdef DEBUG_SPEW
- Platform::outputDebugString( "[SFXXAudioVoice] Loading non-stream buffer at %i", mNonStreamSampleStartPos );
- #endif
- EnterCriticalSection( &mLock );
- const XAUDIO2_BUFFER& orgBuffer = _getBuffer()->mBufferQueue.front().mData;
- mNonStreamBuffer = orgBuffer;
- if( mNonStreamSampleStartPos )
- {
- mNonStreamBuffer.PlayBegin = mNonStreamSampleStartPos;
- mNonStreamBuffer.PlayLength = _getBuffer()->getNumSamples() - mNonStreamSampleStartPos;
- mSamplesPlayedOffset += mNonStreamSampleStartPos; // Add samples that we are skipping.
- mNonStreamSampleStartPos = 0;
- }
- if( mIsLooping )
- {
- mNonStreamBuffer.LoopCount = XAUDIO2_LOOP_INFINITE;
- mNonStreamBuffer.LoopLength = _getBuffer()->getNumSamples();
- }
- // Submit buffer.
- mXAudioVoice->SubmitSourceBuffer( &mNonStreamBuffer );
- mNonStreamBufferLoaded = true;
- LeaveCriticalSection( &mLock );
- }
- U32 SFXXAudioVoice::_tell() const
- {
- XAUDIO2_VOICE_STATE state;
- mXAudioVoice->GetState( &state );
- // Workaround SamplesPlayed not getting reset.
- return ( state.SamplesPlayed + mSamplesPlayedOffset );
- }
- void SFXXAudioVoice::setMinMaxDistance( F32 min, F32 max )
- {
- // Set the overall volume curve scale.
- mEmitter.CurveDistanceScaler = max;
- // The curve uses normalized distances, so
- // figure out the normalized min distance.
- F32 normMin = 0.0f;
- if ( min > 0.0f )
- normMin = min / max;
- // See what type of curve we are supposed to generate.
- const bool linear = ( mXAudioDevice->mDistanceModel == SFXDistanceModelLinear );
- // Have we setup the curve yet?
- if( !mEmitter.pVolumeCurve
- || ( linear && mEmitter.pVolumeCurve->PointCount != 2 )
- || ( !linear && mEmitter.pVolumeCurve->PointCount != 6 ) )
- {
- if( !mEmitter.pVolumeCurve )
- mEmitter.pVolumeCurve = new X3DAUDIO_DISTANCE_CURVE;
- else
- SAFE_DELETE_ARRAY( mEmitter.pVolumeCurve->pPoints );
- // We use 6 points for logarithmic volume curves and 2 for linear volume curves.
- if( linear )
- {
- mEmitter.pVolumeCurve->pPoints = new X3DAUDIO_DISTANCE_CURVE_POINT[ 2 ];
- mEmitter.pVolumeCurve->PointCount = 2;
- }
- else
- {
- mEmitter.pVolumeCurve->pPoints = new X3DAUDIO_DISTANCE_CURVE_POINT[ 6 ];
- mEmitter.pVolumeCurve->PointCount = 6;
- }
- // The first and last points are known
- // and will not change.
- mEmitter.pVolumeCurve->pPoints[ 0 ].Distance = 0.0f;
- mEmitter.pVolumeCurve->pPoints[ 0 ].DSPSetting = 1.0f;
- mEmitter.pVolumeCurve->pPoints[ linear ? 1 : 5 ].Distance = 1.0f;
- mEmitter.pVolumeCurve->pPoints[ linear ? 1 : 5 ].DSPSetting = 0.0f;
- }
- if( !linear )
- {
- // Set the second point of the curve.
- mEmitter.pVolumeCurve->pPoints[1].Distance = normMin;
- mEmitter.pVolumeCurve->pPoints[1].DSPSetting = 1.0f;
- // The next three points are calculated to
- // give the sound a rough logarithmic falloff.
- F32 distStep = ( 1.0f - normMin ) / 4.0f;
- for ( U32 i=0; i < 3; i++ )
- {
- U32 index = 2 + i;
- F32 dist = normMin + ( distStep * (F32)( i + 1 ) );
- mEmitter.pVolumeCurve->pPoints[index].Distance = dist;
- mEmitter.pVolumeCurve->pPoints[index].DSPSetting = 1.0f - log10( dist * 10.0f );
- }
- }
- }
- void SFXXAudioVoice::OnBufferEnd( void* bufferContext )
- {
- if( mBuffer->isStreaming() )
- SFXInternal::TriggerUpdate();
- }
- void SFXXAudioVoice::OnStreamEnd()
- {
- // Warning: This is being called within the XAudio
- // thread, so be sure you're thread safe!
- mHasStopped = true;
- if( mBuffer->isStreaming() )
- SFXInternal::TriggerUpdate();
- else
- _stop();
- }
- void SFXXAudioVoice::play( bool looping )
- {
- // Give the device a chance to calculate our positional
- // audio settings before we start playback... this is
- // important else we get glitches.
- if( mIs3D )
- mXAudioDevice->_setOutputMatrix( this );
- mIsLooping = looping;
- Parent::play( looping );
- }
- void SFXXAudioVoice::setVelocity( const VectorF& velocity )
- {
- mEmitter.Velocity.x = velocity.x;
- mEmitter.Velocity.y = velocity.y;
- // XAudio and Torque use opposite handedness, so
- // flip the z coord to account for that.
- mEmitter.Velocity.z = -velocity.z;
- }
- void SFXXAudioVoice::setTransform( const MatrixF& transform )
- {
- transform.getColumn( 3, (Point3F*)&mEmitter.Position );
- transform.getColumn( 1, (Point3F*)&mEmitter.OrientFront );
- transform.getColumn( 2, (Point3F*)&mEmitter.OrientTop );
- // XAudio and Torque use opposite handedness, so
- // flip the z coord to account for that.
- mEmitter.Position.z *= -1.0f;
- mEmitter.OrientFront.z *= -1.0f;
- mEmitter.OrientTop.z *= -1.0f;
- }
- void SFXXAudioVoice::setVolume( F32 volume )
- {
- mXAudioVoice->SetVolume( volume );
- }
- void SFXXAudioVoice::setPitch( F32 pitch )
- {
- mPitch = mClampF( pitch, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO );
- mXAudioVoice->SetFrequencyRatio( mPitch );
- }
- void SFXXAudioVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume )
- {
- // If the cone is set to 360 then the
- // cone is null and doesn't need to be
- // set on the voice.
- if ( mIsEqual( innerAngle, 360 ) )
- {
- SAFE_DELETE( mEmitter.pCone );
- return;
- }
- if ( !mEmitter.pCone )
- {
- mEmitter.pCone = new X3DAUDIO_CONE;
- // The inner volume is always 1... the overall
- // volume is what scales it.
- mEmitter.pCone->InnerVolume = 1.0f;
- // We don't use these yet.
- mEmitter.pCone->InnerLPF = 0.0f;
- mEmitter.pCone->OuterLPF = 0.0f;
- mEmitter.pCone->InnerReverb = 0.0f;
- mEmitter.pCone->OuterReverb = 0.0f;
- }
- mEmitter.pCone->InnerAngle = mDegToRad( innerAngle );
- mEmitter.pCone->OuterAngle = mDegToRad( outerAngle );
- mEmitter.pCone->OuterVolume = outerVolume;
- }
|