dsp::Oversampling class performance

Hi guys,
I don’t know if I’m doing something wrong, but looks like the dsp::Oversampling is not performing good as expected.

I have an upcoming distortion plugin that, as obvious, shows a lot of aliasing if not oversampled.
I’m using the dsp::Oversampling class, but to get close to the performance of a competitor’s 4x oversampler, I have to set mine at 16x

RED: target, hardware unit | BLUE: My plugin, 16x oversampling
Notice the aliasing still there

RED: target, hardware unit | BLUE: Competitor’s plugin, 4x oversampling
Very minimal aliasing.

What I’m doing is:
create the oversampler in MyPlugin::MyPlugin() constructor
oversampling.reset (new dsp::Oversampling<float> (2, log2(OSAMPFACTOR), dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, true));

in prepareToPlay i have: oversampling->initProcessing(static_cast<size_t> (samplesPerBlock));

in process I have:

dsp::AudioBlock<float> oversampledBlock;

setLatencySamples (audioCurrentlyOversampled ? roundToInt (oversampling->getLatencyInSamples()) : 0);

// Upsampling
if (audioCurrentlyOversampled)
{
    oversampledBlock = oversampling->processSamplesUp (context.getInputBlock());
}

auto pluginContext = audioCurrentlyOversampled ? dsp::ProcessContextReplacing<float> (oversampledBlock)
: context;

//My DSP code here

if (audioCurrentlyOversampled)
{
    oversampling->processSamplesDown (context.getOutputBlock());
}

Is there anything wrong I’m missing or the dsp::Oversampling class is underperforming?

Thanks!

The quality I think depends on not only the oversampling ratio, but also on the quality of the downsampling filter. That might explain the disparity.

2 Likes

Look at the constructor of the juce Oversampling class.

        auto gaindBStartUp    = (isMaximumQuality ? -90.0f : -70.0f);
        auto gaindBStartDown  = (isMaximumQuality ? -75.0f : -60.0f);
        auto gaindBFactorUp   = (isMaximumQuality ? 10.0f  : 8.0f);
        auto gaindBFactorDown = (isMaximumQuality ? 10.0f  : 8.0f);

Even at max quality the filters are created with -90/-75 dB stopband. It used to be -75dB for both. When I complained about that a few years ago, “Up” got bumped to -90dB, but IMHO this is not enough. As I wrote back then, I’d expect oversampling to use the same quality as a top-end audio interface as it should be the weak link in the audio-quality chain.

I ended up patching the Juce Oversampling to have another constructor that takes the stopband db values to apply and use -110dB for up&down stopband attenuation.

Ivan suggested me to try using a different oversampler constructor and, then, add the stages:

oversampling.reset (new dsp::Oversampling<float> (2));
oversampling->addOversamplingStage(dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, 0.05, -90.0, 0.05, -90.0);
oversampling->addOversamplingStage(dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, 0.05, -90.0, 0.05, -90.0);

This helps a lot, since I now got the 16x performance using a 4x oversampler, but it doesn’t go better than that even with a transition band of 0.01 and -200dB of attenuation.
I got close to the competitor’s performance stacking stages up to 32x, but it’s overkill on the CPU.

What is your competitor’s product? It might do more than just oversampling to reduce aliasing.

it’s an analog simulation of a distortion pedal. we get pretty much the same spectra (including aliasing) with no oversampling. It also offers FIR and IIR as oversampling options and I compared both. Later I’ll try without the Juce oversampler, using another class with a custom FIR and, as further test, with HIIR.

if you search for the steepest transition band and you can live with a maximally linear phase response and some ripples around the transition band, i had great results downsampling with a bessel filter.

1 Like

It’s possible that -100 dB is the maximum achievable stopband using floats and the JUCE oversampler class. You could try running the oversampler using doubles. Of course that’s even slower, but maybe it allows you to go lower on the stopband. If I remember correctly the JUCE oversampler uses SampleType for the recursive coefficients (v1Up and v1Down)… which limits the achievable precision. Maybe this has improved in Juce6, but I haven’t checked yet.

I checked and it’s still the same:

   AudioBuffer<SampleType> v1Up, v1Down;

So the only way around it without patching (which I did) is to feed it doubles… Would be nice if there were two template parameters.

also, are you processing your filter with doubles? i had quality problems in the past with coefficients and intermediate calculations done with floats

The JUCE oversampling class always uses the SampleType for all filter calculations, so it’s likely all float.

it’s really only the audio buffers that should be floats, any manipulation of them that is not addition or subtraction should be done in double precision space then converting back to float before writing into the audio buffers the altered signal

3 Likes

Yes. I’ve had this discussion before here. The issue was performance from using double operations and compatibility with the juce SIMD types for multi-channel operation. So nothing got changed and I made a patched version for my own usage that uses double operations as well as doing the two parallel structures at the same time using SIMD.