This is something I did with my own JUCE based framework, so don't be put off by weird data types or calls. The concept is still migratable to non JUCE environments.
- Code: Select all
void Stuff (AudioSampleBuffer* source, AudioSampleBuffer* target, unsigned int ratio)
/* examine the dimensions of the source buffer */
const unsigned int numChannels = source->getNumChannels();
const unsigned int numSamples = source->getNumSamples();
/* create working copy in case source==target */
/* go through every channel */
for(unsigned int channel=0;channel<numChannels;++channel)
/* getting access to the streams' float array with the samples for this channel */
const float* sourceData=source->getReadPointer(channel);
/* for every sample in the source stream */
/* calculate an offset; this is used to determine where in the oversampled
stream to put the original samples. will be 0 for first one, so all good. */
const int offset=sample*ratio;
/* for every ratio step */
/* add one repetition of this sample to the new stream */
/* copy working copy to target stream */
Immediately after doing this, you'll have to apply a VERY steep low pass filter at the SOURCE's Nyquist, so 1/2 * SOURCE sample rate. If the original sample rate is 44100, then put the LP at ~22000. When you're done processing, just reverse the above. VERY steep low pass filter, remove the excess samples.
My OCD would recommend *not* to stuff/up-sample the original stream/AudioSampleBuffer/float array, but to create a working copy that is already sized to the correct amount of samples, then you don't have to resize one stream constantly.
For 4x oversampling, create a stream/float array that's (original float array size * 4) samples long, use above process to copy over 1 sample from the original into 4 samples of the oversampled one, finally apply a hard filter.
Then you do your filtering.
At the end, you reverse above process by applying a VERY hard LP filter at Nyquist to the oversampled stream, then you loop through the original stream (for int sample=0;sample<original.size;++sample), use (int sample * oversampling factor) as an offset and just copy over original[sample]=oversampled[sample*offset].
That should do the trick.
Note that my snippet stuffs the original sample into the up-sampled stream multiple times. I tried doing this with 1 original sample and (oversampling factor - 1) zeroes, this is called zero stuffing, but somehow the signal I got out in the end was -3 dBfs quieter than what I sent in. Reusing the original sample gave me identical volume after just up- and then downsampling.
In the end, I just went back to using JUCE's internal functionality for resampling. I couldn't get any filter to work as steep as theirs without being hugely CPU inefficient.