dsp::Convolution and resampling

Hi @reuk @matkatmusic assisted me in adding r8brain resampler to my convolution project. We have found that the juce Convolution engine impulse loading process will automatically resample an impulse response if the internal member variable that tracks the currently spec’d samplerate differs from the sample rate of the IR being loaded.

 void setImpulseResponse (BufferWithSampleRate&& buf,
                             Convolution::Stereo stereo,
                             Convolution::Trim trim,
                             Convolution::Normalise normalise)
    {
        const std::lock_guard<std::mutex> lock (mutex);
        wantsNormalise = normalise;
        originalSampleRate = buf.sampleRate;

        impulseResponse = [&]
        {
            auto corrected = fixNumChannels (buf.buffer, stereo);
            return trim == Convolution::Trim::yes ? trimImpulseResponse (corrected) : corrected;
        }();

        engine.set (makeEngine());
    }

This function sets originalSampleRate which is a member variable.
after we call this, we call prepare(spec) to force this impulse response to be loaded.
prepare updates the processSpec member variable used in makeEngine()

We have to pass in a dummy buffer with the desired sample rate into setImpulseResponse , then call prepare() in order to make originalSampleRate and processSpec.sampleRate have the same value.

Once those 2 variables have the same value, we can call setImpulseResponse with the actual Impulse Response and call prepare afterwards to force that IR to be loaded.

In summary:

  • internal resampler was being used even though we were passing in the updated sample rate and had already resampled our IR.
  • we got around it by loading a dummer buffer and prepare -ing to force the IR load which would update the 2 member variables that control if the IR being loaded is internally resampled or not.

Is there a better approach?

Kind regards,
Mitch

Just wanted to add that this dummy buffer prepare() step is what sets the internal processSpec to have the correct sample rate of the impulse response we’re about to load.

Basically this:

juce::AudioBuffer<float> dummyBuffer;
dummyBuffer.setSize(1, 1);
dummyBuffer.setSample(0, 0, 1.f); //impulse response

conv.loadImpulseResponse(dummyBuffer, targetSampleRate, ... );

spec.sampleRate = targetSampleRate;
conv.prepare(spec);

conv.loadImpulseResponse(actualImpulseResponse, targetSampleRate, etc...);
conv.prepare(spec);

If we have already resampled the actualImpulseResponse to the targetSampleRate, this approach prevents the convolver’s internal resampler from being used.

I don’t really understand the goal. Are you trying to avoid calling the resampler altogether, or just avoid resampled IRs being used during processing?

In the latter case, it looks to me like calling loadImpulseResponse will cache the provided impulse response as-is, along with its sample rate. Then, the next call to prepare will call makeEngine passing the new processing sample rate. At that point, if the processing sample rate matches the cached impulse response sample rate, then no resampling will be performed.

If you’re trying to avoid running the resampler altogether, then your current approach sounds reasonable.

We are trying to avoid running the internal resampler because we have already resampled externally, using r8brains.

An idea that might be difficult to implement but would make the convolver nicer to use is to allow using a 3rd party resampler.
I think this could be accomplished by providing a virtual ResamplerBase class interface for us to implement that the convolver makes use of.
The convolver would default to using its own derived class that simply wraps the JUCE resampler.
This would allow people using the convolution class the ability to use 3rd party resamplers, without having to do tricks like what was described above (calling prepare() twice). they would just need to derive from ResamplerBase and wrap their preferred resampler’s interface in this class, and pass this derived class to the Convolution class somehow.
maybe during construction?
Maybe at runtime if you wanted to be able to compare the resamplers at runtime?