You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
788 lines
28 KiB
788 lines
28 KiB
//========= 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; |
|
} |
|
|
|
|