summaryrefslogtreecommitdiff
path: root/engine/audio/private/snd_wave_mixer.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/audio/private/snd_wave_mixer.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'engine/audio/private/snd_wave_mixer.cpp')
-rw-r--r--engine/audio/private/snd_wave_mixer.cpp788
1 files changed, 788 insertions, 0 deletions
diff --git a/engine/audio/private/snd_wave_mixer.cpp b/engine/audio/private/snd_wave_mixer.cpp
new file mode 100644
index 0000000..484b431
--- /dev/null
+++ b/engine/audio/private/snd_wave_mixer.cpp
@@ -0,0 +1,788 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=====================================================================================//
+
+#include "audio_pch.h"
+#include "fmtstr.h"
+#include "sysexternal.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern bool FUseHighQualityPitch( channel_t *pChannel );
+
+//-----------------------------------------------------------------------------
+// These mixers provide an abstraction layer between the audio device and
+// mixing/decoding code. They allow data to be decoded and mixed using
+// optimized, format sensitive code by calling back into the device that
+// controls them.
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: maps mixing to 8-bit mono mixer
+//-----------------------------------------------------------------------------
+class CAudioMixerWave8Mono : public CAudioMixerWave
+{
+public:
+ CAudioMixerWave8Mono( IWaveData *data ) : CAudioMixerWave( data ) {}
+ virtual int GetMixSampleSize() { return CalcSampleSize(8, 1); }
+ virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
+ {
+ pDevice->Mix8Mono( pChannel, (char *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: maps mixing to 8-bit stereo mixer
+//-----------------------------------------------------------------------------
+class CAudioMixerWave8Stereo : public CAudioMixerWave
+{
+public:
+ CAudioMixerWave8Stereo( IWaveData *data ) : CAudioMixerWave( data ) {}
+ virtual int GetMixSampleSize( ) { return CalcSampleSize(8, 2); }
+ virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
+ {
+ pDevice->Mix8Stereo( pChannel, (char *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: maps mixing to 16-bit mono mixer
+//-----------------------------------------------------------------------------
+class CAudioMixerWave16Mono : public CAudioMixerWave
+{
+public:
+ CAudioMixerWave16Mono( IWaveData *data ) : CAudioMixerWave( data ) {}
+ virtual int GetMixSampleSize() { return CalcSampleSize(16, 1); }
+ virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
+ {
+ pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: maps mixing to 16-bit stereo mixer
+//-----------------------------------------------------------------------------
+class CAudioMixerWave16Stereo : public CAudioMixerWave
+{
+public:
+ CAudioMixerWave16Stereo( IWaveData *data ) : CAudioMixerWave( data ) {}
+ virtual int GetMixSampleSize() { return CalcSampleSize(16, 2); }
+ virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
+ {
+ pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
+ }
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create an appropriate mixer type given the data format
+// Input : *data - data access abstraction
+// format - pcm or adpcm (1 or 2 -- RIFF format)
+// channels - number of audio channels (1 = mono, 2 = stereo)
+// bits - bits per sample
+// Output : CAudioMixer * abstract mixer type that maps mixing to appropriate code
+//-----------------------------------------------------------------------------
+CAudioMixer *CreateWaveMixer( IWaveData *data, int format, int nChannels, int bits, int initialStreamPosition )
+{
+ CAudioMixer *pMixer = NULL;
+
+ if ( format == WAVE_FORMAT_PCM )
+ {
+ if ( nChannels > 1 )
+ {
+ if ( bits == 8 )
+ pMixer = new CAudioMixerWave8Stereo( data );
+ else
+ pMixer = new CAudioMixerWave16Stereo( data );
+ }
+ else
+ {
+ if ( bits == 8 )
+ pMixer = new CAudioMixerWave8Mono( data );
+ else
+ pMixer = new CAudioMixerWave16Mono( data );
+ }
+ }
+ else if ( format == WAVE_FORMAT_ADPCM )
+ {
+ return CreateADPCMMixer( data );
+ }
+#if defined( _X360 )
+ else if ( format == WAVE_FORMAT_XMA )
+ {
+ return CreateXMAMixer( data, initialStreamPosition );
+ }
+#endif
+ else
+ {
+ // unsupported format or wav file missing!!!
+ return NULL;
+ }
+
+ if ( pMixer )
+ {
+ Assert( CalcSampleSize(bits, nChannels ) == pMixer->GetMixSampleSize() );
+ }
+ else
+ {
+ Assert( 0 );
+ }
+ return pMixer;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Init the base WAVE mixer.
+// Input : *data - data access object
+//-----------------------------------------------------------------------------
+CAudioMixerWave::CAudioMixerWave( IWaveData *data ) : m_pData(data)
+{
+ CAudioSource *pSource = GetSource();
+ if ( pSource )
+ {
+ pSource->ReferenceAdd( this );
+ }
+
+ m_fsample_index = 0;
+ m_sample_max_loaded = 0;
+ m_sample_loaded_index = -1;
+ m_finished = false;
+ m_forcedEndSample = 0;
+ m_delaySamples = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Frees the data access object (we own it after construction)
+//-----------------------------------------------------------------------------
+CAudioMixerWave::~CAudioMixerWave( void )
+{
+ CAudioSource *pSource = GetSource();
+ if ( pSource )
+ {
+ pSource->ReferenceRemove( this );
+ }
+ delete m_pData;
+}
+
+bool CAudioMixerWave::IsReadyToMix()
+{
+ return m_pData->IsReadyToMix();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Decode and read the data
+// by default we just pass the request on to the data access object
+// other mixers may need to buffer or decode the data for some reason
+//
+// Input : **pData - dest pointer
+// sampleCount - number of samples needed
+// Output : number of samples available in this batch
+//-----------------------------------------------------------------------------
+int CAudioMixerWave::GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ int samples_loaded;
+ // clear this out in case the underlying code leaves it unmodified
+ *pData = NULL;
+ samples_loaded = m_pData->ReadSourceData( pData, m_sample_max_loaded, sampleCount, copyBuf );
+
+ // keep track of total samples loaded
+ m_sample_max_loaded += samples_loaded;
+
+ // keep track of index of last sample loaded
+ m_sample_loaded_index += samples_loaded;
+
+ return samples_loaded;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: calls through the wavedata to get the audio source
+// Output : CAudioSource
+//-----------------------------------------------------------------------------
+CAudioSource *CAudioMixerWave::GetSource( void )
+{
+ if ( m_pData )
+ return &m_pData->Source();
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Gets the current sample location in playback (index of next sample
+// to be loaded).
+// Output : int (samples from start of wave)
+//-----------------------------------------------------------------------------
+int CAudioMixerWave::GetSamplePosition( void )
+{
+ return m_sample_max_loaded;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : delaySamples -
+//-----------------------------------------------------------------------------
+void CAudioMixerWave::SetStartupDelaySamples( int delaySamples )
+{
+ m_delaySamples = delaySamples;
+}
+
+// Move the current position to newPosition
+void CAudioMixerWave::SetSampleStart( int newPosition )
+{
+ CAudioSource *pSource = GetSource();
+ if ( pSource )
+ newPosition = pSource->ZeroCrossingAfter( newPosition );
+
+ m_fsample_index = newPosition;
+
+ // index of last sample loaded - set to sample at new position
+ m_sample_loaded_index = newPosition;
+ m_sample_max_loaded = m_sample_loaded_index + 1;
+}
+
+// End playback at newEndPosition
+void CAudioMixerWave::SetSampleEnd( int newEndPosition )
+{
+ // forced end of zero means play the whole sample
+ if ( !newEndPosition )
+ newEndPosition = 1;
+
+ CAudioSource *pSource = GetSource();
+ if ( pSource )
+ newEndPosition = pSource->ZeroCrossingBefore( newEndPosition );
+
+ // past current position? limit.
+ if ( newEndPosition < m_fsample_index )
+ newEndPosition = m_fsample_index;
+
+ m_forcedEndSample = newEndPosition;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Skip source data (read but don't mix). The mixer must provide the
+// full amount of samples or have silence in its output stream.
+//-----------------------------------------------------------------------------
+int CAudioMixerWave::SkipSamples( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
+{
+ float flTempPitch = pChannel->pitch;
+ pChannel->pitch = 1.0f;
+ int nRetVal = MixDataToDevice_( NULL, pChannel, sampleCount, outputRate, outputOffset, true );
+ pChannel->pitch = flTempPitch;
+ return nRetVal;
+}
+
+// wrapper routine to append without overflowing the temp buffer
+static uint AppendToBuffer( char *pBuffer, const char *pSampleData, size_t nBytes, const char *pBufferEnd )
+{
+#if defined(_WIN32) && !defined(_X360)
+ // FIXME: Some clients are crashing here. Let's try to detect why.
+ if ( nBytes > 0 && ( (size_t)pBuffer <= 0xFFF || (size_t)pSampleData <= 0xFFF ) )
+ {
+ Warning( "AppendToBuffer received potentially bad values (%p, %p, %u, %p)\n", pBuffer, pSampleData, (int)nBytes, pBufferEnd );
+ }
+#endif
+
+ if ( pBufferEnd > pBuffer )
+ {
+ size_t nAvail = pBufferEnd - pBuffer;
+ size_t nCopy = MIN( nBytes, nAvail );
+ Q_memcpy( pBuffer, pSampleData, nCopy );
+ return nCopy;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+// Load a static copy buffer (g_temppaintbuffer) with the requested number of samples,
+// with the first sample(s) in the buffer always set up as the last sample(s) of the previous load.
+// Return a pointer to the head of the copy buffer.
+// This ensures that interpolating pitch shifters always have the previous sample to reference.
+// pChannel: sound's channel data
+// sample_load_request: number of samples to load from source data
+// pSamplesLoaded: returns the actual number of samples loaded (should always = sample_load_request)
+// copyBuf: req'd by GetOutputData, used by some Mixers
+// Returns: NULL ptr to data if no samples available, otherwise always fills remainder of copy buffer with
+// 0 to pad remainder.
+// NOTE: DO NOT MODIFY THIS ROUTINE (KELLYB)
+char *CAudioMixerWave::LoadMixBuffer( channel_t *pChannel, int sample_load_request, int *pSamplesLoaded, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
+{
+ int samples_loaded;
+ char *pSample = NULL;
+ char *pData = NULL;
+ int cCopySamps = 0;
+
+ // save index of last sample loaded (updated in GetOutputData)
+ int sample_loaded_index = m_sample_loaded_index;
+
+ // get data from source (copyBuf is expected to be available for use)
+ samples_loaded = GetOutputData( (void **)&pData, sample_load_request, copyBuf );
+ if ( !samples_loaded && sample_load_request )
+ {
+ // none available, bail out
+ // 360 might not be able to get samples due to latency of loop seek
+ // could also be the valid EOF for non-loops (caller keeps polling for data, until no more)
+ AssertOnce( IsX360() || !m_pData->Source().IsLooped() );
+ *pSamplesLoaded = 0;
+ return NULL;
+ }
+
+ int samplesize = GetMixSampleSize();
+ const int nTempCopyBufferSize = ( TEMP_COPY_BUFFER_SIZE * sizeof( portable_samplepair_t ) );
+ char *pCopy = (char *)g_temppaintbuffer;
+ const char *pCopyBufferEnd = pCopy + nTempCopyBufferSize;
+
+
+
+ if ( IsX360() || IsDebug() )
+ {
+ // for safety, 360 always validates sample request, due to new xma audio code and possible logic flaws
+ // PC can expect number of requested samples to be within tolerances due to exisiting aged code
+ // otherwise buffer overruns cause hard to track random crashes
+ if ( ( ( sample_load_request + 1 ) * samplesize ) > nTempCopyBufferSize )
+ {
+ // make sure requested samples will fit in temp buffer.
+ // if this assert fails, then pitch is too high (ie: > 2.0) or the sample counters have diverged.
+ // NOTE: to prevent this, pitch should always be capped in MixDataToDevice (but isn't nor are the sample counters).
+ DevWarning( "LoadMixBuffer: sample load request %d exceeds buffer sizes\n", sample_load_request );
+ Assert( 0 );
+ *pSamplesLoaded = 0;
+ return NULL;
+ }
+ }
+
+ // copy all samples from pData to copy buffer, set 0th sample to saved previous sample - this ensures
+ // interpolation pitch shift routines always have a previous sample to reference.
+
+ // copy previous sample(s) to head of copy buffer pCopy
+ // In some cases, we'll need the previous 2 samples. This occurs when
+ // Rate < 1.0 - in example below, sample 4.86 - 6.48 requires samples 4-7 (previous samples saved are 4 & 5)
+
+ /*
+ Example:
+ rate = 0.81, sampleCount = 3 (ie: # of samples to return )
+
+ _____load 3______ ____load 3_______ __load 2__
+
+ 0 1 2 3 4 5 6 7 sample_index (whole samples)
+
+ ^ ^ ^ ^ ^ ^ ^ ^ ^
+ | | | | | | | | |
+ 0 0.81 1.68 2.43 3.24 4.05 4.86 5.67 6.48 m_fsample_index (rate*sample)
+ _______________ ________________ ________________
+ ^ ^ ^ ^
+ | | | |
+ m_sample_loaded_index | | m_sample_loaded_index
+ | |
+ m_fsample_index---- ----m_fsample_index
+
+ [return 3 samp] [return 3 samp] [return 3 samp]
+ */
+ pSample = &(pChannel->sample_prev[0]);
+
+ // determine how many saved samples we need to copy to head of copy buffer (0,1 or 2)
+ // so that pitch interpolation will correctly reference samples.
+ // NOTE: pitch interpolators always reference the sample before and after the indexed sample.
+
+ // cCopySamps = sample_max_loaded - floor(m_fsample_index);
+
+ if ( sample_loaded_index < 0 || (floor(m_fsample_index) > sample_loaded_index))
+ {
+ // no samples previously loaded, or
+ // next sample index is entirely within the next block of samples to be loaded,
+ // so we won't need any samples from the previous block. (can occur when rate > 2.0)
+ cCopySamps = 0;
+ }
+ else if ( m_fsample_index < sample_loaded_index )
+ {
+ // next sample index is entirely within the previous block of samples loaded,
+ // so we'll need the last 2 samples loaded. (can occur when rate < 1.0)
+ Assert ( ceil(m_fsample_index + 0.00000001) == sample_loaded_index );
+ cCopySamps = 2;
+ }
+ else
+ {
+ // next sample index is between the next block and the previously loaded block,
+ // so we'll need the last sample loaded. (can occur when 1.0 < rate < 2.0)
+ Assert( floor(m_fsample_index) == sample_loaded_index );
+ cCopySamps = 1;
+ }
+ Assert( cCopySamps >= 0 && cCopySamps <= 2 );
+
+ // point to the sample(s) we are to copy
+ if ( cCopySamps )
+ {
+ pSample = cCopySamps == 1 ? pSample + samplesize : pSample;
+ pCopy += AppendToBuffer( pCopy, pSample, samplesize * cCopySamps, pCopyBufferEnd );
+ }
+
+ // copy loaded samples from pData into pCopy
+ // and update pointer to free space in copy buffer
+ if ( ( samples_loaded * samplesize ) != 0 && !pData )
+ {
+ char const *pWavName = "";
+ CSfxTable *source = pChannel->sfx;
+ if ( source )
+ {
+ pWavName = source->getname();
+ }
+
+ Warning( "CAudioMixerWave::LoadMixBuffer: '%s' samples_loaded * samplesize = %i but pData == NULL\n", pWavName, ( samples_loaded * samplesize ) );
+ *pSamplesLoaded = 0;
+ return NULL;
+ }
+
+ pCopy += AppendToBuffer( pCopy, pData, samples_loaded * samplesize, pCopyBufferEnd );
+
+ // if we loaded fewer samples than we wanted to, and we're not
+ // delaying, load more samples or, if we run out of samples from non-looping source,
+ // pad copy buffer.
+ if ( samples_loaded < sample_load_request )
+ {
+ // retry loading source data until 0 bytes returned, or we've loaded enough data.
+ // if we hit 0 bytes, fill remaining space in copy buffer with 0 and exit
+ int samples_load_extra;
+ int samples_loaded_retry = -1;
+
+ for ( int k = 0; (k < 10000 && samples_loaded_retry && samples_loaded < sample_load_request); k++ )
+ {
+ // how many more samples do we need to satisfy load request
+ samples_load_extra = sample_load_request - samples_loaded;
+ samples_loaded_retry = GetOutputData( (void**)&pData, samples_load_extra, copyBuf );
+
+ // copy loaded samples from pData into pCopy
+ if ( samples_loaded_retry )
+ {
+ if ( ( samples_loaded_retry * samplesize ) != 0 && !pData )
+ {
+ Warning( "CAudioMixerWave::LoadMixBuffer: samples_loaded_retry * samplesize = %i but pData == NULL\n", ( samples_loaded_retry * samplesize ) );
+ *pSamplesLoaded = 0;
+ return NULL;
+ }
+
+ pCopy += AppendToBuffer( pCopy, pData, samples_loaded_retry * samplesize, pCopyBufferEnd );
+ samples_loaded += samples_loaded_retry;
+ }
+ }
+ }
+
+ // if we still couldn't load the requested samples, fill rest of copy buffer with 0
+ if ( samples_loaded < sample_load_request )
+ {
+ // should always be able to get as many samples as we request from looping sound sources
+ AssertOnce ( IsX360() || !m_pData->Source().IsLooped() );
+
+ // these samples are filled with 0, not loaded.
+ // non-looping source hit end of data, fill rest of g_temppaintbuffer with 0
+ int samples_zero_fill = sample_load_request - samples_loaded;
+
+ int nAvail = pCopyBufferEnd - pCopy;
+ int nFill = samples_zero_fill * samplesize;
+ nFill = MIN( nAvail, nFill );
+ Q_memset( pCopy, 0, nFill );
+ pCopy += nFill;
+ samples_loaded += samples_zero_fill;
+ }
+
+ if ( samples_loaded >= 2 )
+ {
+ // always save last 2 samples from copy buffer to channel
+ // (we'll need 0,1 or 2 samples as start of next buffer for interpolation)
+ Assert( sizeof( pChannel->sample_prev ) >= samplesize*2 );
+ pSample = pCopy - samplesize*2;
+ Q_memcpy( &(pChannel->sample_prev[0]), pSample, samplesize*2 );
+ }
+
+ // this routine must always return as many samples loaded (or zeros) as requested.
+ Assert( samples_loaded == sample_load_request );
+
+ *pSamplesLoaded = samples_loaded;
+
+ return (char *)g_temppaintbuffer;
+}
+
+// Helper routine to round (rate * samples) down to fixed point precision
+
+double RoundToFixedPoint( double rate, int samples, bool bInterpolated_pitch )
+{
+ fixedint fixp_rate;
+ int64 d64_newSamps; // need to use double precision int to avoid overflow
+
+ double newSamps;
+
+ // get rate, in fixed point, determine new samples at rate
+
+ if ( bInterpolated_pitch )
+ fixp_rate = FIX_FLOAT14(rate); // 14 bit iterator
+ else
+ fixp_rate = FIX_FLOAT(rate); // 28 bit iterator
+
+ // get number of new samples, convert back to float
+
+ d64_newSamps = (int64)fixp_rate * (int64)samples;
+
+ if ( bInterpolated_pitch )
+ newSamps = FIX_14TODOUBLE(d64_newSamps);
+ else
+ newSamps = FIX_TODOUBLE(d64_newSamps);
+
+ return newSamps;
+}
+
+extern double MIX_GetMaxRate( double rate, int sampleCount );
+
+// Helper routine for MixDataToDevice:
+// Compute number of new samples to load at 'rate' so we can
+// output 'sampleCount' samples, from m_fsample_index to fsample_index_end (inclusive)
+// rate: sample rate
+// sampleCountOut: number of samples calling routine needs to output
+// bInterpolated_pitch: true if mixers use interpolating pitch shifters
+int CAudioMixerWave::GetSampleLoadRequest( double rate, int sampleCountOut, bool bInterpolated_pitch )
+{
+ double fsample_index_end; // index of last sample we'll need
+ int sample_index_high; // rounded up last sample index
+ int sample_load_request; // number of samples to load
+
+ // NOTE: we must use fixed point math here, identical to math in mixers, to make sure
+ // we predict iteration results exactly.
+ // get floating point sample index of last sample we'll need
+ fsample_index_end = m_fsample_index + RoundToFixedPoint( rate, sampleCountOut-1, bInterpolated_pitch );
+
+ // always round up to ensure we'll have that n+1 sample for interpolation
+ sample_index_high = (int)( ceil( fsample_index_end ) );
+
+ // make sure we always round the floating point index up by at least 1 sample,
+ // ie: make sure integer sample_index_high is greater than floating point sample index
+ if ( (double)sample_index_high <= fsample_index_end )
+ {
+ sample_index_high++;
+ }
+ Assert ( sample_index_high > fsample_index_end );
+
+ // attempt to load enough samples so we can reach sample_index_high sample.
+ sample_load_request = sample_index_high - m_sample_loaded_index;
+ Assert( sample_index_high >= m_sample_loaded_index );
+
+ // NOTE: we can actually return 0 samples to load if rate < 1.0
+ // and sampleCountOut == 1. In this case, the output sample
+ // is computed from the previously saved buffer data.
+ return sample_load_request;
+}
+
+int CAudioMixerWave::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset )
+{
+ return MixDataToDevice_(pDevice, pChannel, sampleCount, outputRate, outputOffset, false );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: The device calls this to request data. The mixer must provide the
+// full amount of samples or have silence in its output stream.
+// Mix channel to all active paintbuffers.
+// NOTE: cannot be called consecutively to mix into multiple paintbuffers!
+// Input : *pDevice - requesting device
+// sampleCount - number of samples at the output rate - should never be more than size of paintbuffer.
+// outputRate - sampling rate of the request
+// outputOffset - starting offset to mix to in paintbuffer
+// bskipallmixing - true if we just want to skip ahead in source data
+
+// Output : Returns true to keep mixing, false to delete this mixer
+
+// NOTE: DO NOT MODIFY THIS ROUTINE (KELLYB)
+
+//-----------------------------------------------------------------------------
+int CAudioMixerWave::MixDataToDevice_( IAudioDevice *pDevice, channel_t *pChannel, int sampleCount, int outputRate, int outputOffset, bool bSkipAllMixing )
+{
+ // shouldn't be playing this if finished, but return if we are
+ if ( m_finished )
+ return 0;
+
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // save this to compute total output
+ int startingOffset = outputOffset;
+
+ double inputRate = (pChannel->pitch * m_pData->Source().SampleRate());
+ double rate_max = inputRate / outputRate;
+
+ // If we are terminating this wave prematurely, then make sure we detect the limit
+ if ( m_forcedEndSample )
+ {
+ // How many total input samples will we need?
+ int samplesRequired = (int)(sampleCount * rate_max);
+ // will this hit the end?
+ if ( m_fsample_index + samplesRequired >= m_forcedEndSample )
+ {
+ // yes, mark finished and truncate the sample request
+ m_finished = true;
+ sampleCount = (int)( (m_forcedEndSample - m_fsample_index) / rate_max );
+ }
+ }
+
+ /*
+ Example:
+ rate = 1.2, sampleCount = 3 (ie: # of samples to return )
+
+ ______load 4 samples_____ ________load 4 samples____ ___load 3 samples__
+
+ 0 1 2 3 4 5 6 7 8 9 10 sample_index (whole samples)
+
+ ^ ^ ^ ^ ^ ^ ^ ^ ^
+ | | | | | | | | |
+ 0 1.2 2.4 3.6 4.8 6.0 7.2 8.4 9.6 m_fsample_index (rate*sample)
+ _______return 3_______ _______return 3_______ _______return 3__________
+ ^ ^
+ | |
+ m_sample_loaded_index----- | (after first load 4 samples, this is where pointers are)
+ m_fsample_index---------
+ */
+ while ( sampleCount > 0 )
+ {
+ bool advanceSample = true;
+ int samples_loaded, outputSampleCount;
+ char *pData = NULL;
+ double fsample_index_prev = m_fsample_index; // save so we can modify in LoadMixBuffer
+ bool bInterpolated_pitch = FUseHighQualityPitch( pChannel );
+ double rate;
+
+ VPROF_( bInterpolated_pitch ? "CAudioMixerWave::MixData innerloop interpolated" : "CAudioMixerWave::MixData innerloop not interpolated", 2, VPROF_BUDGETGROUP_OTHER_SOUND, false, BUDGETFLAG_OTHER );
+
+ // process samples in paintbuffer-sized batches
+ int sampleCountOut = min( sampleCount, PAINTBUFFER_SIZE );
+
+ // cap rate so that we never overflow the input copy buffer.
+ rate = MIX_GetMaxRate( rate_max, sampleCountOut );
+
+ if ( m_delaySamples > 0 )
+ {
+ // If we are preceding sample playback with a delay,
+ // just fill data buffer with 0 value samples.
+ // Because there is no pitch shift applied, outputSampleCount == sampleCountOut.
+ int num_zero_samples = min( m_delaySamples, sampleCountOut );
+
+ // Decrement delay counter
+ m_delaySamples -= num_zero_samples;
+
+ int sampleSize = GetMixSampleSize();
+ int readBytes = sampleSize * num_zero_samples;
+
+ // make sure we don't overflow temp copy buffer (g_temppaintbuffer)
+ Assert ( (TEMP_COPY_BUFFER_SIZE * sizeof(portable_samplepair_t)) > readBytes );
+ pData = (char *)g_temppaintbuffer;
+
+ // Now copy in some zeroes
+ memset( pData, 0, readBytes );
+
+ // we don't pitch shift these samples, so outputSampleCount == samples_loaded
+ samples_loaded = num_zero_samples;
+ outputSampleCount = num_zero_samples;
+
+ advanceSample = false;
+
+ // the zero samples are at the output rate, so set the input/output ratio to 1.0
+ rate = 1.0f;
+ }
+ else
+ {
+ // ask the source for the data...
+ // temp buffer req'd by some data loaders
+ char copyBuf[AUDIOSOURCE_COPYBUF_SIZE];
+
+ // compute number of new samples to load at 'rate' so we can
+ // output 'sampleCount' samples, from m_fsample_index to fsample_index_end (inclusive)
+ int sample_load_request = GetSampleLoadRequest( rate, sampleCountOut, bInterpolated_pitch );
+
+ // return pointer to a new copy buffer (g_temppaintbuffer) loaded with sample_load_request samples +
+ // first sample(s), which are always the last sample(s) from the previous load.
+ // Always returns sample_load_request samples. Updates m_sample_max_loaded, m_sample_loaded_index.
+ pData = LoadMixBuffer( pChannel, sample_load_request, &samples_loaded, copyBuf );
+
+ // LoadMixBuffer should always return requested samples.
+ Assert ( !pData || ( samples_loaded == sample_load_request ) );
+
+ outputSampleCount = sampleCountOut;
+ }
+
+ // no samples available
+ if ( !pData )
+ {
+ break;
+ }
+
+ // get sample fraction from 0th sample in copy buffer
+ double sampleFraction = m_fsample_index - floor( m_fsample_index );
+
+ // if just skipping samples in source, don't mix, just keep reading
+ if ( !bSkipAllMixing )
+ {
+ // mix this data to all active paintbuffers
+ // Verify that we won't get a buffer overrun.
+ Assert( floor( sampleFraction + RoundToFixedPoint(rate, (outputSampleCount-1), bInterpolated_pitch) ) <= samples_loaded );
+
+ int saveIndex = MIX_GetCurrentPaintbufferIndex();
+ for ( int i = 0 ; i < g_paintBuffers.Count(); i++ )
+ {
+ if ( g_paintBuffers[i].factive )
+ {
+ // mix channel into all active paintbuffers
+ MIX_SetCurrentPaintbuffer( i );
+
+ Mix(
+ pDevice, // Device.
+ pChannel, // Channel.
+ pData, // Input buffer.
+ outputOffset, // Output position.
+ FIX_FLOAT( sampleFraction ), // Iterators.
+ FIX_FLOAT( rate ),
+ outputSampleCount,
+ 0 );
+ }
+ }
+ MIX_SetCurrentPaintbuffer( saveIndex );
+ }
+
+ if ( advanceSample )
+ {
+ // update sample index to point to the next sample to output
+ // if we're not delaying
+ // Use fixed point math to make sure we exactly match results of mix
+ // iterators.
+ m_fsample_index = fsample_index_prev + RoundToFixedPoint( rate, outputSampleCount, bInterpolated_pitch );
+ }
+
+ outputOffset += outputSampleCount;
+ sampleCount -= outputSampleCount;
+ }
+
+ // Did we run out of samples? if so, mark finished
+ if ( sampleCount > 0 )
+ {
+ m_finished = true;
+ }
+
+ // total number of samples mixed !!! at the output clock rate !!!
+ return outputOffset - startingOffset;
+}
+
+
+bool CAudioMixerWave::ShouldContinueMixing( void )
+{
+ return !m_finished;
+}
+
+float CAudioMixerWave::ModifyPitch( float pitch )
+{
+ return pitch;
+}
+
+float CAudioMixerWave::GetVolumeScale( void )
+{
+ return 1.0f;
+}
+