Oversampling = random amplitude spikes?

Hello! I am running into an issue where using oversampling seems to introduce random spikes in amplitude for single samples (this does not happen when not using oversampling). I tried upgrading to Juce 7 and have created a simple test file to demonstrate. I am guessing that I am using the oversampler incorrectly?

I would upload the example file, but I am not allowed to attach yet in the forum. Thanks!

Example output:

0.00244384
0.00182456
0.0012077
0.000595403
-1.46199e-05
-0.000629048
-0.00125011
-0.00187474

In processor.h private:

juce::dsp::Oversampling<**float**> oversamplingModule;

At the top of processor.cpp:

, // Oversampling
oversamplingModule(2, 1, juce::dsp::Oversampling<**float**>::FilterType::filterHalfBandPolyphaseIIR)

In prepare to play:

    juce::dsp::ProcessSpec spec;
    spec.sampleRate = sampleRate;
    spec.maximumBlockSize = samplesPerBlock;
    spec.numChannels = 2;
    
    oversamplingModule.reset();
    oversamplingModule.initProcessing (static_cast<size_t> (samplesPerBlock));

Here’s the entire process block:

    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

    // Blocks for oversampling
    juce::dsp::AudioBlock<float> block(buffer);
    juce::dsp::AudioBlock<float> upSampledBlock(block);
    upSampledBlock = oversamplingModule.processSamplesUp(block);
    
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        for (int sample = 0; sample < upSampledBlock.getNumSamples(); ++sample)
        {
        if (channel == 1) {
           DBG(upSampledBlock.getSample(1, sample));
        }
        }

    }

I just got access to upload, so here’s the test file!
ClipTestDummy.zip (65.7 KB)

You’re using the same oversampling object for both channels. You need one object per channel.

That’s not required with the oversampler, it can deal with stereo and more channels internally. But it of course needs to be initialized to use the needed number of channels.

From the docs :

A processor that performs multi-channel oversampling.

Is this just a case of ISP’s being generated?

I thought that was the case but was willing to give reFX’s answer a shot!

Sorry - I don’t know what ISP is!

Intersample peak

Well these are values coming from the input so I don’t know how that would be happening?

FYI - I did just try making separate L and R instances of the oversampler and the result is the same.

That’s what I use on all of our products, without suffering these spikes.

In PluginProcessor.h, we have the oversampler as a private member:
std::unique_ptr<dsp::Oversampling> oversampling;

Then, PluginProcessor.cpp:

In the constructor:
oversampling.reset (new dsp::Oversampling (2, log2(2), dsp::Oversampling::filterHalfBandPolyphaseIIR, false)); //change log2(2) with your oversampling factor

In prepareToPlay:
oversampling->initProcessing(static_cast<size_t> (samplesPerBlock));

In process(dsp::ProcessContextReplacing context):

ScopedNoDenormals noDenormals;
    
    auto&& inBlock  = context.getInputBlock();
    auto&& outBlock = context.getOutputBlock();
    
    jassert (inBlock.getNumChannels() == outBlock.getNumChannels());
    jassert (inBlock.getNumSamples() == outBlock.getNumSamples());
    
    auto numSamples  = inBlock.getNumSamples();
    auto numChannels = inBlock.getNumChannels();
        
    // Upsampling
    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;
    
    
    auto&& plugInBlock  = pluginContext.getInputBlock();
    auto&& plugOutBlock = pluginContext.getOutputBlock();
    
    jassert (plugInBlock.getNumChannels() == plugOutBlock.getNumChannels());
    jassert (plugInBlock.getNumSamples() == plugOutBlock.getNumSamples());
    
    numSamples  = plugInBlock.getNumSamples();
    numChannels = plugInBlock.getNumChannels();
    
    const bool isMono = numChannels == 1;
    const bool isStereo = numChannels > 1;
    
    //Plugin Processing
    for (size_t chan = 0; chan < numChannels; ++chan)
    {
        for (size_t i = 0; i < numSamples; ++i)
        {
            const float nrDry = plugInBlock.getChannelPointer(chan)[i];
            //my dsp process goes here
            plugOutBlock.getChannelPointer(chan)[i] = blabla; //my dsp process output goes in the out block here
        }
    }
    
    // Downsampling
    if (audioCurrentlyOversampled)
        oversampling->processSamplesDown (context.getOutputBlock());
        
}

Hope this helps.

Luca

1 Like

Thank you, I will try this :slight_smile:

Just out of the blue, the signal after oversampling, especially after using IIR filters (which also move the phase) might look ripply, but could be correct. In the values you provided in the first post I can’t see a spike (did not check the file which you have provided)

The expected range is -1 to 1, with oversampling enabled I was getting single samples out of range like the -1.46199 in the example.

The e-05 means you have to multiply the value by 10^-5, the value actually is -0.0000146199

Thank you so much for that - I totally missed that bit. That fixes it.

Thanks again!