[Solved] Help with CatmullRomInterpolator?


#1

So, I’m trying to oversample the audio input for a plugin in order to run it through a filter. I found some information about the CatmullRomInterpolator, now I want to figure out how to use it.

I read they are stateful, so each channel needs to be processed through an individual instance, and they need to be set outside of processBlock(). Did that, added a private CatmullRomInterpolator variable to the AudioProcessor.h file.

Going by the documentation/information I could find (incl. Class Reference), it seems to me that I should be doing this in the processBlock():

  • get a writePointer for the input stream
  • set up a stream for the oversampled signal and get a writePointer
  • upsample from input to oversampled stream
  • run oversampled stream through the filter
  • downsample from oversampled to input (well, output) writePointer

I figure the (important part of the) code in processBlock should look like this:

/* sample block size */
const int numSamples = buffer.getNumSamples();

/* if at least one channel is present */
if(totalNumInputChannels>0)
{
    /* get input stream */
    float *inputLeft = buffer.getWritePointer (0);
    
    /* set up temporary stream for oversampled data */
    AudioSampleBuffer leftOversampled;
    leftOversampled.setSize(1,numSamples*2); /* Oversampling by 2, therefore twice numSamples */
    
    /* fetch write pointer for oversampling stream */
    float *oversampledLeft = leftOversampled.getWritePointer(0);
    
    /* upsample from input to oversampled stream by factor 2
         resampler1 is a variable of type CatmullRomInterpolator */
    resampler1.process(2, inputLeft, oversampledLeft, numSamples*2);
    
    /* set up the filter
         hostSampleRate: variable set in prepareToPlay()
         filterCutoffFreq: variable set/updated from slider (20-20k, default 1k)
         cutoff1: variable of type IIRFilter */
    IIRCoefficients coefficients=IIRCoefficients::makeHighPass(hostSampleRate*2,filterCutoffFreq);
    cutoff1.setCoefficients(coefficients);
    
    /* run oversampled stream through filter */
    cutoff1.processSamples (oversampledLeft, numSamples*2);
    
    /* downsample fom oversampled stream back to i/o write pointer by factor 1/2 */
    resampler1.process(0.5, oversampledLeft, inputLeft, numSamples); 
}

The thing creates output and filters alright, but there are constant clicks and farts, which leads me to assume I’m doing something wrong with the buffering, some samples are dropped instead of processed or something like that.

I tried resetting the CatmullRomInterpolator (resampler1) and/or AudioSampleBuffer before each processing step, but that didn’t help.

What am I doing wrong? Can anyone point me in the right direction?
Many thanks in advance!

Cheers,
Rob


#2

Hello ! Two remarks :

  • Never ever create an AudioSampleBuffer in your processing function. Do it once only in the prepareToPlay, give it the right size. By doing so, you’ll improve significantly the performance of your algorithm, since allocating and deleting space for an array is very consuming…

  • You need to use two different resampler objects, one for the upsampling, and one for the downsampling, that’s probably the reason why you’re getting artefacts :wink:


#3

Thank you for your reply. :slight_smile:
I followed your advice, but I’m still getting clicks and buzzing artfacts.

By commenting sections out, I’ve narrowed it down to this:

  • when I only upsample and downsample without the filter in between, there are no clicks and everything sounds as expected.
  • when I only use the filter without upsampling before and downsampling after, there are no clicks and everything sounds as expected.
  • when I use upsampling -> filter -> downsampling, there are clicks and buzzing artefacts.

These are the private parts (pun not intended) of my .h file:

IIRCoefficients filterCoefficients;
IIRFilter cutoff1, cutoff2;

AudioSampleBuffer leftOversampled, rightOversampled;

CatmullRomInterpolator  upSampler1, downSampler1,
                        upSampler2, downSampler2;

double upsamplingFactor=2.0;
double downsamplingFactor=0.5;

double hostSampleRate;
double filterSampleRate;
double filterCutoffFreq=1000.0;

In my .cpp file, this is the prepareToPlay() part:

hostSampleRate=sampleRate; /* sampleRate passed by function */
filterSampleRate=hostSampleRate*upsamplingFactor;

/* each of these has only 1 channel and needs room for 2.0 x sample blocks */
leftOversampled.setSize(1,samplesPerBlock*upsamplingFactor);
rightOversampled.setSize(1,samplesPerBlock*upsamplingFactor);

This is the interesting part of processBlock() when only up-/downsampling, no clicks or anything, just a slight high-cut very far up, guessing that’s from the downsampling:

float *inputLeft = buffer.getWritePointer (0);
float *oversampledLeft = leftOversampled.getWritePointer(0);

upSampler1.process(upsamplingFactor, inputLeft, oversampledLeft, int(numSamples*upsamplingFactor));
downSampler1.process(downsamplingFactor, oversampledLeft, inputLeft, numSamples);

This is the interesting part of processBlock() when only filtering, no clicks or anything, but the filter works fine:

float *inputLeft = buffer.getWritePointer (0);

filterCoefficients=IIRCoefficients::makeHighPass(hostSampleRate,filterCutoffFreq);
cutoff1.setCoefficients(filterCoefficients);
cutoff1.processSamples (inputLeft, numSamples);

When I combine the two, this is what the processBlock() looks like, there are clicks and pops and buzzing artefacts:

float *inputLeft = buffer.getWritePointer (0);
float *oversampledLeft = leftOversampled.getWritePointer(0);

upSampler1.process(upsamplingFactor, inputLeft, oversampledLeft, int(numSamples*upsamplingFactor));

/* filterSampleRate = hostSampleRate x upsamplingFactor */
filterCoefficients=IIRCoefficients::makeHighPass(filterSampleRate,filterCutoffFreq);
cutoff1.setCoefficients(filterCoefficients);
cutoff1.processSamples (oversampledLeft, numSamples*upsamplingFactor);

downSampler1.process(downsamplingFactor, oversampledLeft, inputLeft, numSamples);

The only thing that really changes when having oversampling+filter is:

  • the filter uses the upsampled stream instead of the input
  • the filter coefficients are calculated with a higher sample rate
  • the numSamples for processing gets multiplied by the upsamplingFactor

The upsampled stream seems to be intact, otherwise (I guess) there would be artefacts in the “up-/downsampling only” audio signal. So I’ll rule that out as the culprit for now.

When I upsample a stream by, for example, the factor 2, then a 44.1 kHz sample rate will become an 88.2 kHz sample rate. So it makes sense to let the filter know (by setting it in the coeffs) that it’s calculating at 88.2 kHz instead of 44.1 kHz, right? That’s what I’m doing by setting filterSampleRate = hostSampleRate * upsamplingFactor. So I’ll also rule this out as the culprit for now.

Part of the concept of oversampling is that there will be more samples to calculate with than there are initially. So if a buffer has, say, a numSamples of 256 and I upsample it by a factor of 2, then it would make sense that the upsampled stream should have 512 samples, that’s numSamples * upsamplingFactor. Right? Which is what I’m doing.

I’m telling the upSampler1 to calculate (numSamples * upsamplingFactor) output samples, I’m telling the cutoff1 to process the filter with (numSamples * upsamplingFactor) samples. And then the downSampler1 gets the full oversamples stream and reduces it to a numSamples long output stream again.

So as I understand it, I can also rule out the different numSamples as the culprit.

Which leaves me with ideas: 0
What’s the thing I’m doing or understanding wrong? :confused:


#4

I can’t see the reason why that doesn’t work for now, that’s very strange…

Can you just add all the reset functions for your filter and interpolator classes in the prepareToPlay ? And replace the CatmulRomInterpolator with the LagrangeInterpolator to see if something changes ?


#5

Hm, thanks for the hint, just tried that. Unfortunately, to no success.

prepareToPlay() now does this: (apart from setting sample rate etc.)

/* reset resamplers */
upSampler1.reset();
upSampler2.reset();
downSampler1.reset();
downSampler2.reset();

/* create temporary stream for oversampled data */
leftOversampled.setSize(1,samplesPerBlock*upsamplingFactor);
rightOversampled.setSize(1,samplesPerBlock*upsamplingFactor);

/* clear filters */
leftOversampled.clear();
rightOversampled.clear();

Tried clearing the AudioSampleBuffers before and after setting their size, tried making the CatmullRomInterpolator a LagrangeInterpolator as per your suggestion… nothing. Still buzzing and clicking.

:confused:


#6

You don’t need to clear the buffers :wink:

However I wonder if the ratio should be 0.5 for upsampling and 2.0 for downsampling instead. I have never used the interpolator classes so… I think also that some lowpass filtering should be used too before the downsampling process. The content of the ResamplingAudioSource might give more hints… And have a look to the output of your resampling functions, they return the number of input samples actually being used, that information might help

I’ll try to run your code when I have some time if you are not able to solve the issue.


#7

Ugh… so obvious… sigh I knew it was something silly.
It was really the incorrect up-/downsampling speed values.

I don’t remember exactly what it was, but when reading the CatmullRomInterpolator documentation, something made me look at the ResamplingAudioSource documentation as well. Reading all those different things must’ve confused me. Trees and forest and so on…

The CatmullRomInterpolator page is pretty straight-forward: “speedRatio - the number of input samples to use for each output sample”. 0.5 go in, 1 comes out, higher sample rate. Obvious.

The ResamplingAudioSource page also mentions a pretty understandably named parameter “samplesInPerOutputSample”. 2 samples in, 1 sample out, lower sample rate. Obious.

I guess what must have thrown me off track was the further explanation of that parameter: “higher values will speed it up; lower values will slow it down”.

To my understanding, a higher sample rate puts through the same audio at a higher speed, e.g. what is 16 samples long in 44.1 kHz is 32 samples long in 88.2 kHz but “takes the same time”. That seems like speeding up to me.

Meaning that a value higher than 1.0 should raise the sample rate = upsample, and a value between 0 and 1 should lower sample rate = downsample…

It would never have occured to me to look at the speed parameter in a 100 years, I was convinced it had something to do with the filter and the buffers…

Thank you so much for thinking of that!