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; }
//----------------------------------------------------------------------------- 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; // 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 < CPAINTBUFFERS; 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; }
//----------------------------------------------------------------------------- // Purpose: The device calls this to request data. The mixer must provide the // full amount of samples or have silence in its output stream. // Input : *pDevice - requesting device // sampleCount - number of samples at the output rate // outputRate - sampling rate of the request // Output : Returns true to keep mixing, false to delete this mixer //----------------------------------------------------------------------------- bool CAudioMixerWave::MixDataToDevice( IAudioDevice *pDevice, channel_t *pChannel, int startSample, int sampleCount, int outputRate, bool forward /*= true*/ ) { int offset = 0; int inputSampleRate = (int)(pChannel->pitch * m_pData->Source().SampleRate()); float rate = (float)inputSampleRate / outputRate; fixedint fracstep = FIX_FLOAT( rate ); sampleCount = min( sampleCount, PAINTBUFFER_SIZE ); int startpos = startSample; if ( !forward ) { int requestedstart = startSample - (int)( sampleCount * rate ); if ( requestedstart < 0 ) return false; startpos = max( 0, requestedstart ); SetSamplePosition( startpos ); } while ( sampleCount > 0 ) { int availableSamples; int inputSampleCount; char *pData = NULL; int outputSampleCount = sampleCount; if ( outputRate != inputSampleRate ) { inputSampleCount = (int)(sampleCount * rate); int availableSamples = GetOutputData( (void **)&pData, startpos, inputSampleCount, forward ); if ( !availableSamples ) break; if ( availableSamples < inputSampleCount ) { outputSampleCount = (int)(availableSamples / rate); inputSampleCount = availableSamples; } Mix( pDevice, pChannel, pData, offset, m_fracOffset, fracstep, outputSampleCount, 0, forward ); // compute new fraction part of sample index float offset = (m_fracOffset / FIX_SCALE) + (rate * outputSampleCount); offset = offset - (float)((int)offset); m_fracOffset = FIX_FLOAT(offset); } else { availableSamples = GetOutputData( (void **)&pData, startpos, sampleCount, forward ); if ( !availableSamples ) break; outputSampleCount = availableSamples; inputSampleCount = availableSamples; Mix( pDevice, pChannel, pData, offset, m_fracOffset, FIX(1), outputSampleCount, 0, forward ); } offset += outputSampleCount; sampleCount -= outputSampleCount; if ( forward ) { m_sample += inputSampleCount; m_absoluteSample += inputSampleCount; } if ( m_loop != 0 && m_sample >= m_loop ) { SetSamplePosition( m_startpos ); } } if ( sampleCount > 0 ) return false; return true; }