Torque3D Documentation / _generateds / sfxXAudioVoice.cpp

sfxXAudioVoice.cpp

Engine/source/sfx/xaudio/sfxXAudioVoice.cpp

More...

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