ResamplingAudioSource problems

Hi all, 

Sorry, long post coming up. I'm implementing some up and down resampling in a VST plugin, and I've noticed an issue with my results using ResamplingAudioSource. I can't be sure as to whether to put it down as an incorrect use of the class, or an inherent problem in the resampling algorithm. I'll try to describe what's happening below and hopefully somebody can shed some light; alternatively if anyone has code to share for a working implementation of ResamplingAudioSource, that would be amazing.

The problem is a 1/frequency magnitude decay. In this test i'm taking white noise, upsampling at a factor of 1/4, downsampling at a factor of 4, and recording the output. See below for a before/after FFT spectra:

Otherwise there's no audible discontiuities or distortions, just this low pass filtering effect.

So here's how I set the resamplers up (it's a multitrack plugin, but i'm just testing on 1 track (2 channels)):

// Set resampling ratios     
for (int trk = 0; trk < _numTracks; ++trk) 
{        
    _resamplers[trk*2] ->setResamplingRatio(1.0f/_resamplingRatio); // upsampling     
    _resamplers[trk*2+1]->setResamplingRatio(_resamplingRatio); // downsampling 

    _resamplers[trk*2]->prepareToPlay(_numSamples, _sampleRate); 
    _resamplers[trk*2+1]->prepareToPlay(_upNumSamples, _upSampleRate);     
}     

// Resampling buffer output info     
_upSampledBufferInfo.buffer = &_upSampledOriginalBuffer;     
_upSampledBufferInfo.startSample = 0;     
_upSampledBufferInfo.numSamples = _upNumSamples;     

_downSampledBufferInfo.buffer = &_currentTrackBuffer;     
_downSampledBufferInfo.startSample = 0;     
_downSampledBufferInfo.numSamples = _numSamples;

I wrote a VST audio source to act as the input to the resamplers:

//----------------------------------------------------------------------------- //
void  VSTAudioSource::SetDataPtrs(float ** audioData, int numChannels) 
{ 
    _data.allocate(numChannels,false); 
    for (int i = 0; i < numChannels; ++i)
    { 
        _data[i] = audioData[i]; 
    } 
    _startSample = 0; 
} 

//----------------------------------------------------------------------------- // 
void  VSTAudioSource::prepareToPlay(int samplesPerBlockExpected, double sampleRate) 
{ 
    _numSamples = samplesPerBlockExpected; 
} 

//----------------------------------------------------------------------------- // 
void  VSTAudioSource::releaseResources() 
{ 
    _numSamples = 0; _startSample = 0; 
} 

//----------------------------------------------------------------------------- // 
void  VSTAudioSource::getNextAudioBlock(const AudioSourceChannelInfo& bufferToFill) 
{ 
    // Fill with data from VST buffer 
    for (int sample = 0; sample < bufferToFill.numSamples; ++sample)
    { 
        for (int chnl = 0; chnl < bufferToFill.buffer->getNumChannels(); ++chnl)
        { 
           *bufferToFill.buffer->getSampleData (chnl,bufferToFill.startSample + sample) = _data[chnl][_startSample]; 
        } 
        _startSample = (_startSample + 1) % _numSamples; 
    } 
}

Then in processBlock():

// UPSAMPLING 
_vstAudioSourceUp.SetDataPtrs(_origDataPtr,2);                 
_resamplers[trk*2]->getNextAudioBlock(_upSampledBufferInfo);
                
// DOWNSAMPLING                 
_vstAudioSourceDown.SetDataPtrs(_upSampledSummedBuffer.getArrayOfChannels(),2);               
_resamplers[trk*2+1]->getNextAudioBlock(_downSampledBufferInfo); 

Think that's it, all thoughts appreciated. Many thanks!

Stuart

 

Looking at the documentation of the ResamplingAudioSource versus the code, I'm a little bit confused as to how it is supposed to be used.

void ResamplingAudioSource::prepareToPlay ( int  samplesPerBlockExpected,

double  sampleRate 
)

samplesPerBlockExpected the number of samples that the source will be expected to supply each time its getNextAudioBlock() method is called. This number may vary slightly, because it will be dependent on audio hardware callbacks, and these aren't guaranteed to always use a constant block size, so the source should be able to cope with small variations.
sampleRate the sample rate that the output will be used at - this is needed by sources such as tone generators.

From this, if using the RAS to upsample, I would call prepareToPlay with samplesPerBlock * upsamplingFactor (upsamplingFactor > 1) as the number of samplesPerBlockExpected. Yet, I don't see why call input->prepareToPlay with the same parameters, since the input source should supply samplesPerBlock samples in this case.


void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected,
                                           double sampleRate)
{
                  ...

 input->prepareToPlay (samplesPerBlockExpected, sampleRate);
    buffer.setSize (numChannels, roundToInt (samplesPerBlockExpected * ratio) + 32);
                  ...
}

If the samplesPerBlockExpected is in fact the input block size, then I'm not sure I understand why the buffer is resized using samplesPerBLockExpected * ratio. Here ratio is 1/upsamplingFactor for upsampling (and = downsamplingFactor for downsampling). So a 1024 samples input block would size the interpolator buffer to 1024 / upsamplingFactor, making the buffer smaller in case of upsampling and bigger in case of downsampling.

 

I dunno how this relates to the issue you are observing with the white noise test, but maybe someone with a bit of experience with the ResamplingAudioSource could provide some insight in its intended use.

Thanks sgelinas, yeah I was confused by prepareToPlay too - in fact generally I've had a tough time understanding the fine details and so the problem is hard to diagnose. Considering I can't see this reported elsewhere I'm sure it's down to my implementation...

stoo - the ResamplingAudioSource uses a low-pass filter to reduce aliasing... If you'd just taken a look at the code I'm sure that would have been obvious to you - it's only 200 lines and is pretty easy to see what's going on in there.

sgelinas - I don't really understand what you're asking, but the prepareToPlay method is a base-class method, and has no special meaning for this class. Every AudioProcessor has that method, and it's just a hint to allow the processor to know how what kind of data size will be sent to it when running.

because of the nyqusit-shannon-theorem you will of couse remove higher frequecy content, when up and downsampling white noise,

that just math.

How steep the low-pass-filter effect will be, its just a question of how the resampler is implmented.

jules/chkn - thanks both. Sorry I should have been clearer, the anti-aliasing low pass filter at fs/2 is no suprise, but the significant reduction of magnitude in the audible frequency range is. I have a very clearly audible and increasing-with-frequency magnitude cut after resampling - not just with white noise but with real audio too - such that it is unusable in it's current state. 

Check out this excellent option:

https://code.google.com/p/r8brain-free-src/

jules - My point was, if you are upsampling a 1024 samples block by 4 for example, the source should provide 1024 samples, and the resampler should resize its buffer to 1024 X 4, while if you downsample by 4 instead, the resampler should resize its buffer to 1024/4.

Given that ratio = decimationFactor (4) and 1/interpolationFactor (1/4), this would resize the buffer to 1024/4 for interpolation and 1024*4 for decimation. 

I understand your point that this method is not that important in the behavior of the source and it's just providing it a starting point, the real magic happenning in getNextAudioBlock method (which is resizing properly).

Thanks for your help!

Seb

 

 

Thanks Peter, looks great, I'll give it a try.