Avoiding clicks while switching impulse responses

I am working on a tiny VST which allows to switch between different impulse responses, ideally as fast as possible while still avoiding clicks.

This is a study project and my first time working with JUCE. As such, my current implementation to achieve this is very straightforward brute-force convolution in the time domain that I perform on whole buffers, where the convolution ‘tail’ is added to the start of the next buffer.
I’ve read that, with an approach like this, switching the impulse response at the start of a new buffer should not produce any (or at least not that many) clicks, yet they are still there.

I would appreciate some advice on what other approaches could be taken to resolve this. At the moment, I am trying to put some crossfading in place, which helps, but the clicks are still audible. I imagine this would be easier to handle if I was working on a sample-per-sample basis, but that would require reworking my custom filters, which I would really like to avoid.

Edit 1: The impulse response is changed from the GUI. I have experimented both on changing it whenever the UI is interacted with, and delaying the use of new IR until a new block processing starts.

Thank you for any advice in advance.

It’s not clear from your post what is changing the impulse response. Does the change come from the GUI or is the audio thread changing it itself? If it’s the GUI making the changes, you have to ensure it is thread safe.

Thank you for your response, I have edited the original question - the change is indeed made from the GUI and I suspected that may be causing issues. I would most appreciate more guidance on this.

The dsp::convolution class does crossfading, you might want to check it out. Is also uses fast convolution in frequency domain, and also partitioned convolution -> quite efficient

Thank you for the input. I have to stick to my own implementations for the time being, but that’s certainly something to explore in the future.

A few years ago, parts of my bachelor thesis dealt with seamless switching between impulse responses. If your native language is by chance german I could send you a copy of the relevant chapter, describing various approaches. A short summary:

  • Hard switching of impulse responses will create artifacts, no matter if computed in time or frequency domain
  • A first smoothing approach in the time domain is to not change all IR coefficients at once but exchanging them coefficient by coefficient on a per-sample basis
  • An even better sounding smoothing approach is to compute e.g. 128 intermediate IRs that are interpolated between the old and new one and use each of these for only one sample
  • For convolution in the frequency domain, a straightforward approach is to compute two convolutions in parallel, one with the old and one with the new input signal and fade between their outputs in the time domain
  • A fancy trick to avoid two IFFTs in the approach above is to perform a “time domain-style convolution” of both spectra with a 3-element impulse response, representing the spectrum of a cos^2 / sin^2 function to perform the crossfading in the frequency domain before performing the inverse transform. This is a bit difficult to explain in a few lines, as it involves some math, but if you are interested I could look for my original resources describing this efficient approach

Last point is very interesting! Any chance to read your thesis?

It’s written in german - so if you understand it just DM me. But it was not my idea, I just found my original source where I found the solution back then – it’s written in englisch by the way :wink: : http://publications.rwth-aachen.de/record/466561/files/466561.pdf. The author describes it in detail from page 97 on.

1 Like

Thank you so much for your response!
I had a feeling just the IRs could be interpolated without the need of crossfading two different convolutions running in parallel, but did not quite know how to go about it. Computing the intermediate IRs seems to be the way to go as it works really well (jumped into implementing it as soon as I saw your post), at least given the current convolution implementation I am using now.
I don’t understand much German, sadly, but I’ll go through the resources you’ve provided, it all seems very interesting.