sfxXAudioVoice.cpp
Engine/source/sfx/xaudio/sfxXAudioVoice.cpp
Public Functions
sfxFormatToWAVEFORMATEX(const SFXFormat & format, WAVEFORMATEX * wfx)
Detailed Description
Public Functions
sfxFormatToWAVEFORMATEX(const SFXFormat & format, WAVEFORMATEX * wfx)
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "sfx/xaudio/sfxXAudioVoice.h" 26#include "sfx/xaudio/sfxXAudioDevice.h" 27#include "sfx/xaudio/sfxXAudioBuffer.h" 28#include "core/util/safeDelete.h" 29#include "math/mMathFn.h" 30 31 32//#define DEBUG_SPEW 33 34 35static void sfxFormatToWAVEFORMATEX( const SFXFormat& format, WAVEFORMATEX *wfx ) 36{ 37 dMemset( wfx, 0, sizeof( WAVEFORMATEX ) ); 38 wfx->wFormatTag = WAVE_FORMAT_PCM; 39 wfx->nChannels = format.getChannels(); 40 wfx->nSamplesPerSec = format.getSamplesPerSecond(); 41 wfx->wBitsPerSample = format.getBitsPerChannel(); 42 wfx->nBlockAlign = wfx->nChannels * wfx->wBitsPerSample / 8; 43 wfx->nAvgBytesPerSec = wfx->nSamplesPerSec * wfx->nBlockAlign; 44} 45 46 47SFXXAudioVoice* SFXXAudioVoice::create( IXAudio2 *xaudio, 48 bool is3D, 49 SFXXAudioBuffer *buffer, 50 SFXXAudioVoice* inVoice ) 51{ 52 AssertFatal( xaudio, "SFXXAudioVoice::create() - Got null XAudio!" ); 53 AssertFatal( buffer, "SFXXAudioVoice::create() - Got null buffer!" ); 54 55 // Create the voice object first as it also the callback object. 56 SFXXAudioVoice* voice = inVoice; 57 if( !voice ) 58 voice = new SFXXAudioVoice( buffer ); 59 60 // Get the buffer format. 61 WAVEFORMATEX wfx; 62 sfxFormatToWAVEFORMATEX( buffer->getFormat(), &wfx ); 63 64 // We don't support multi-channel 3d sounds! 65 if ( is3D && wfx.nChannels > 1 ) 66 return NULL; 67 68 // Create the voice. 69 IXAudio2SourceVoice *xaVoice; 70 HRESULT hr = xaudio->CreateSourceVoice( &xaVoice, 71 (WAVEFORMATEX*)&wfx, 72 0, 73 XAUDIO2_DEFAULT_FREQ_RATIO, 74 voice, 75 NULL, 76 NULL ); 77 78 if( FAILED( hr ) || !voice ) 79 { 80 if( !inVoice ) 81 delete voice; 82 return NULL; 83 } 84 85 voice->mIs3D = is3D; 86 voice->mEmitter.ChannelCount = wfx.nChannels; 87 voice->mXAudioVoice = xaVoice; 88 89 return voice; 90} 91 92SFXXAudioVoice::SFXXAudioVoice( SFXXAudioBuffer* buffer ) 93 : Parent( buffer ), 94 mXAudioDevice( NULL ), 95 mXAudioVoice( NULL ), 96 mIs3D( false ), 97 mPitch( 1.0f ), 98 mHasStopped( false ), 99 mHasStarted( false ), 100 mIsLooping( false ), 101 mIsPlaying( false ), 102 mNonStreamSampleStartPos( 0 ), 103 mNonStreamBufferLoaded( false ), 104 mSamplesPlayedOffset( 0 ) 105{ 106 dMemset( &mEmitter, 0, sizeof( mEmitter ) ); 107 mEmitter.DopplerScaler = 1.0f; 108 109 InitializeCriticalSection( &mLock ); 110} 111 112SFXXAudioVoice::~SFXXAudioVoice() 113{ 114 if ( mEmitter.pVolumeCurve ) 115 { 116 SAFE_DELETE_ARRAY( mEmitter.pVolumeCurve->pPoints ); 117 SAFE_DELETE( mEmitter.pVolumeCurve ); 118 } 119 120 SAFE_DELETE( mEmitter.pCone ); 121 122 if ( mXAudioVoice ) 123 mXAudioVoice->DestroyVoice(); 124 125 DeleteCriticalSection( &mLock ); 126} 127 128SFXStatus SFXXAudioVoice::_status() const 129{ 130 if( mHasStopped ) 131 return SFXStatusStopped; 132 else if( mHasStarted ) 133 { 134 if( !mIsPlaying ) 135 return SFXStatusPaused; 136 else 137 return SFXStatusPlaying; 138 } 139 else 140 return SFXStatusStopped; 141} 142 143void SFXXAudioVoice::_flush() 144{ 145 AssertFatal( mXAudioVoice != NULL, 146 "SFXXAudioVoice::_flush() - invalid voice" ); 147 148 EnterCriticalSection( &mLock ); 149 150 mXAudioVoice->Stop( 0 ); 151 mXAudioVoice->FlushSourceBuffers(); 152 mNonStreamBufferLoaded = false; 153 154 #ifdef DEBUG_SPEW 155 Platform::outputDebugString( "[SFXXAudioVoice] Flushed state" ); 156 #endif 157 158 mIsPlaying = false; 159 mHasStarted = false; 160 mHasStopped = true; 161 162 //WORKAROUND: According to the docs, SamplesPlayed reported by the 163 // voice should get reset as soon as we submit a new buffer to the voice. 164 // Alas it won't. So, save the current value here and offset our future 165 // play cursors. 166 167 XAUDIO2_VOICE_STATE state; 168 mXAudioVoice->GetState( &state ); 169 mSamplesPlayedOffset = - S32( state.SamplesPlayed ); 170 171 LeaveCriticalSection( &mLock ); 172} 173 174void SFXXAudioVoice::_play() 175{ 176 AssertFatal( mXAudioVoice != NULL, 177 "SFXXAudioVoice::_play() - invalid voice" ); 178 179 // For non-streaming voices queue the data if we haven't yet. 180 181 if( !mBuffer->isStreaming() && !mNonStreamBufferLoaded ) 182 _loadNonStreamed(); 183 184 // Start playback. 185 186 mXAudioVoice->Start( 0, 0 ); 187 188 #ifdef DEBUG_SPEW 189 Platform::outputDebugString( "[SFXXAudioVoice] Started playback" ); 190 #endif 191 192 mIsPlaying = true; 193 mHasStarted = true; 194 mHasStopped = false; 195} 196 197void SFXXAudioVoice::_pause() 198{ 199 AssertFatal( mXAudioVoice != NULL, 200 "SFXXAudioVoice::_pause() - invalid voice" ); 201 202 mXAudioVoice->Stop( 0 ); 203 mIsPlaying = false; 204 205 #ifdef DEBUG_SPEW 206 Platform::outputDebugString( "[SFXXAudioVoice] Paused playback" ); 207 #endif 208} 209 210void SFXXAudioVoice::_stop() 211{ 212 AssertFatal( mXAudioVoice != NULL, 213 "SFXXAudioVoice::_stop() - invalid voice" ); 214 215 _flush(); 216 217 mIsPlaying = false; 218 mHasStarted = false; 219 mHasStopped = true; 220 221 #ifdef DEBUG_SPEW 222 Platform::outputDebugString( "[SFXXAudioVoice] Stopped playback" ); 223 #endif 224} 225 226void SFXXAudioVoice::_seek( U32 sample ) 227{ 228 #ifdef DEBUG_SPEW 229 Platform::outputDebugString( "[SFXXAudioVoice] Seeking to %i", sample ); 230 #endif 231 232 mNonStreamSampleStartPos = sample; 233 234 bool wasPlaying = mIsPlaying; 235 236 _stop(); 237 if( wasPlaying ) 238 _play(); 239} 240 241void SFXXAudioVoice::_loadNonStreamed() 242{ 243 AssertFatal( !mBuffer->isStreaming(), "SFXXAudioVoice::_loadNonStreamed - must not be called on streaming voices" ); 244 AssertFatal( mXAudioVoice != NULL, "SFXXAudioVoice::_loadNonStreamed - invalid voice" ); 245 AssertWarn( !mNonStreamBufferLoaded, "SFXXAudioVoice::_nonStreamNonstreamed - Data already loaded" ); 246 247 #ifdef DEBUG_SPEW 248 Platform::outputDebugString( "[SFXXAudioVoice] Loading non-stream buffer at %i", mNonStreamSampleStartPos ); 249 #endif 250 251 EnterCriticalSection( &mLock ); 252 253 const XAUDIO2_BUFFER& orgBuffer = _getBuffer()->mBufferQueue.front().mData; 254 255 mNonStreamBuffer = orgBuffer; 256 257 if( mNonStreamSampleStartPos ) 258 { 259 mNonStreamBuffer.PlayBegin = mNonStreamSampleStartPos; 260 mNonStreamBuffer.PlayLength = _getBuffer()->getNumSamples() - mNonStreamSampleStartPos; 261 mSamplesPlayedOffset += mNonStreamSampleStartPos; // Add samples that we are skipping. 262 mNonStreamSampleStartPos = 0; 263 } 264 265 if( mIsLooping ) 266 { 267 mNonStreamBuffer.LoopCount = XAUDIO2_LOOP_INFINITE; 268 mNonStreamBuffer.LoopLength = _getBuffer()->getNumSamples(); 269 } 270 271 // Submit buffer. 272 273 mXAudioVoice->SubmitSourceBuffer( &mNonStreamBuffer ); 274 mNonStreamBufferLoaded = true; 275 276 LeaveCriticalSection( &mLock ); 277} 278 279U32 SFXXAudioVoice::_tell() const 280{ 281 XAUDIO2_VOICE_STATE state; 282 mXAudioVoice->GetState( &state ); 283 284 // Workaround SamplesPlayed not getting reset. 285 return ( state.SamplesPlayed + mSamplesPlayedOffset ); 286} 287 288void SFXXAudioVoice::setMinMaxDistance( F32 min, F32 max ) 289{ 290 // Set the overall volume curve scale. 291 mEmitter.CurveDistanceScaler = max; 292 293 // The curve uses normalized distances, so 294 // figure out the normalized min distance. 295 F32 normMin = 0.0f; 296 if ( min > 0.0f ) 297 normMin = min / max; 298 299 // See what type of curve we are supposed to generate. 300 const bool linear = ( mXAudioDevice->mDistanceModel == SFXDistanceModelLinear ); 301 302 // Have we setup the curve yet? 303 if( !mEmitter.pVolumeCurve 304 || ( linear && mEmitter.pVolumeCurve->PointCount != 2 ) 305 || ( !linear && mEmitter.pVolumeCurve->PointCount != 6 ) ) 306 { 307 if( !mEmitter.pVolumeCurve ) 308 mEmitter.pVolumeCurve = new X3DAUDIO_DISTANCE_CURVE; 309 else 310 SAFE_DELETE_ARRAY( mEmitter.pVolumeCurve->pPoints ); 311 312 // We use 6 points for logarithmic volume curves and 2 for linear volume curves. 313 if( linear ) 314 { 315 mEmitter.pVolumeCurve->pPoints = new X3DAUDIO_DISTANCE_CURVE_POINT[ 2 ]; 316 mEmitter.pVolumeCurve->PointCount = 2; 317 } 318 else 319 { 320 mEmitter.pVolumeCurve->pPoints = new X3DAUDIO_DISTANCE_CURVE_POINT[ 6 ]; 321 mEmitter.pVolumeCurve->PointCount = 6; 322 } 323 324 // The first and last points are known 325 // and will not change. 326 mEmitter.pVolumeCurve->pPoints[ 0 ].Distance = 0.0f; 327 mEmitter.pVolumeCurve->pPoints[ 0 ].DSPSetting = 1.0f; 328 mEmitter.pVolumeCurve->pPoints[ linear ? 1 : 5 ].Distance = 1.0f; 329 mEmitter.pVolumeCurve->pPoints[ linear ? 1 : 5 ].DSPSetting = 0.0f; 330 } 331 332 if( !linear ) 333 { 334 // Set the second point of the curve. 335 mEmitter.pVolumeCurve->pPoints[1].Distance = normMin; 336 mEmitter.pVolumeCurve->pPoints[1].DSPSetting = 1.0f; 337 338 // The next three points are calculated to 339 // give the sound a rough logarithmic falloff. 340 F32 distStep = ( 1.0f - normMin ) / 4.0f; 341 for ( U32 i=0; i < 3; i++ ) 342 { 343 U32 index = 2 + i; 344 F32 dist = normMin + ( distStep * (F32)( i + 1 ) ); 345 346 mEmitter.pVolumeCurve->pPoints[index].Distance = dist; 347 mEmitter.pVolumeCurve->pPoints[index].DSPSetting = 1.0f - log10( dist * 10.0f ); 348 } 349 } 350} 351 352void SFXXAudioVoice::OnBufferEnd( void* bufferContext ) 353{ 354 if( mBuffer->isStreaming() ) 355 SFXInternal::TriggerUpdate(); 356} 357 358void SFXXAudioVoice::OnStreamEnd() 359{ 360 // Warning: This is being called within the XAudio 361 // thread, so be sure you're thread safe! 362 363 mHasStopped = true; 364 365 if( mBuffer->isStreaming() ) 366 SFXInternal::TriggerUpdate(); 367 else 368 _stop(); 369} 370 371void SFXXAudioVoice::play( bool looping ) 372{ 373 // Give the device a chance to calculate our positional 374 // audio settings before we start playback... this is 375 // important else we get glitches. 376 if( mIs3D ) 377 mXAudioDevice->_setOutputMatrix( this ); 378 379 mIsLooping = looping; 380 Parent::play( looping ); 381} 382 383void SFXXAudioVoice::setVelocity( const VectorF& velocity ) 384{ 385 mEmitter.Velocity.x = velocity.x; 386 mEmitter.Velocity.y = velocity.y; 387 388 // XAudio and Torque use opposite handedness, so 389 // flip the z coord to account for that. 390 mEmitter.Velocity.z = -velocity.z; 391} 392 393void SFXXAudioVoice::setTransform( const MatrixF& transform ) 394{ 395 transform.getColumn( 3, (Point3F*)&mEmitter.Position ); 396 transform.getColumn( 1, (Point3F*)&mEmitter.OrientFront ); 397 transform.getColumn( 2, (Point3F*)&mEmitter.OrientTop ); 398 399 // XAudio and Torque use opposite handedness, so 400 // flip the z coord to account for that. 401 mEmitter.Position.z *= -1.0f; 402 mEmitter.OrientFront.z *= -1.0f; 403 mEmitter.OrientTop.z *= -1.0f; 404} 405 406void SFXXAudioVoice::setVolume( F32 volume ) 407{ 408 mXAudioVoice->SetVolume( volume ); 409} 410 411void SFXXAudioVoice::setPitch( F32 pitch ) 412{ 413 mPitch = mClampF( pitch, XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_DEFAULT_FREQ_RATIO ); 414 mXAudioVoice->SetFrequencyRatio( mPitch ); 415} 416 417void SFXXAudioVoice::setCone( F32 innerAngle, F32 outerAngle, F32 outerVolume ) 418{ 419 // If the cone is set to 360 then the 420 // cone is null and doesn't need to be 421 // set on the voice. 422 if ( mIsEqual( innerAngle, 360 ) ) 423 { 424 SAFE_DELETE( mEmitter.pCone ); 425 return; 426 } 427 428 if ( !mEmitter.pCone ) 429 { 430 mEmitter.pCone = new X3DAUDIO_CONE; 431 432 // The inner volume is always 1... the overall 433 // volume is what scales it. 434 mEmitter.pCone->InnerVolume = 1.0f; 435 436 // We don't use these yet. 437 mEmitter.pCone->InnerLPF = 0.0f; 438 mEmitter.pCone->OuterLPF = 0.0f; 439 mEmitter.pCone->InnerReverb = 0.0f; 440 mEmitter.pCone->OuterReverb = 0.0f; 441 } 442 443 mEmitter.pCone->InnerAngle = mDegToRad( innerAngle ); 444 mEmitter.pCone->OuterAngle = mDegToRad( outerAngle ); 445 mEmitter.pCone->OuterVolume = outerVolume; 446} 447