Realtime Resampling audio to 48khz and back?

Hi all, I am trying an experiment here, to resample incoming buffers to 48khz, do something, and back… but I cannot manage to make it sound correct… What am I doing wrong?

I am using

    juce::LagrangeInterpolator up;
    juce::LagrangeInterpolator down;
    juce::AudioBuffer<float>   scratch;

So when I prepare to play, I check sampleRate, if it’s not 48000, then I will resample in the processblock, my code:

        const int numIn  = buffer.getNumSamples();

        // RESAMPLE TO 48K IF IT WASNT
        numUp = up.process (upRatio,
                            buffer.getReadPointer(0),
                            scratch.getWritePointer(0),
                            numIn);

//DO SOMETHING TO SCRATCHBUFFER WHICH IS NOW AT 48KHZ

//RESAMPLE BACK TO ORIGINAL SAMPLERATE
        down.process (downRatio,
                      scratch.getReadPointer(0),
                      buffer.getWritePointer(0),
                      numUp);

The up and down ratios are set on preptoplay

        upRatio = targetRate / sampleRate;
        downRatio = 1.0 / upRatio;

It’s driving me mad and would love to know if someone can shed a light :slight_smile:

I am not familiar with juce::LagrangeInterpolator but I would guess you need a low pass at 24kHz before interpolation.

The problem seems to be much bigger - doing the above method results in cracks and pops and the signal being completely ruined… So I was wondering if it’s even possible to achieve …

Do you use separate interpolator instances per (stereo) channel?

Im just processing a mono buffer…hence the read/write pointers being directly aimed to channel 0

Just an idea. If the something is compatible with both 48kHz and 44.1kHz, you may downsample to 48Khz/44.1kHz and upsample to the original samplerate. It is much simpler and should be more efficient (you can look at juce::dsp::OverSampling).

1 Like

Thank you for the idea - however, I have to stick to 48khz, since I am trying to implement RNNOISE in my project, and RNNOISE is hardcoded to work at 48khz :confused: :frowning:

I’d appreciate if someone can shed an extra light on how to resample to and back from 48khz in real time! :slight_smile:

Looking into the interpolators and where you are giving the process() method the number of samples you want processed, you should give the number of samples you expect after processing. The function returns what is actually then produced.

The int value to give is listed as numOutputSamplesToProduce not the number of input samples.
I hope this helps.

Thank you for the heads up, so I adjusted the code as you suggested, still no luck, though:


        upRatio = targetRate / sampleRate;
        downRatio = 1.0 / upRatio;

        const int numIn = buffer.getNumSamples();               // my original numSamples
        const int needUp  = (int) std::ceil (numIn * upRatio);    //numSamples needed after resampling, ceiling up in case of decimals.

//here i can resize the scratchbuffer to have size needUp if needed.

//i need needUp samples
        up.process (upRatio,
                    buffer.getReadPointer  (0),
                    scratch.getWritePointer (0),
                    needUp);

//DO SOMETHING

//i need numIn samples to fit back in my orig. buffer.
        down.process (downRatio,
                      scratch.getReadPointer  (0),
                      buffer.getWritePointer (0),
                      numIn);




It still sounds bad, unfortunately…

I presume you don’t reset or clear, ‘up’ or ‘down’ between frames? The interpolators are stateful.

I’ve only used the Lagrange on complete samples, and it works well, but that’s not in real time.
I can imagine there also being problems with fractional sizes, and how many output samples you actually need for any incoming blocks?

You should check if the conversion back to the original rate matches the expected number of samples, with generic resamplers this is not guaranteed.
One way to deal with this is to write to a ring buffer and read with a bit delay to make sure there are always enough output samples. This can run for some time, but unless the ratio is a power of two, there is no way to exactly represent the ratio and it’s reciprocal, so in the long run your interpolators will run out of sync. There are ways to make synchronized up/downsamplers, no idea if they exist in juce.

I added this to the NeuralAmpModeler project using @baconpaul’s LanczosResampler code available here, which you might find interesting. Usage in iPlug2… here

1 Like

Welp! You might have saved the day! THANK YOU! This seems to be it.