Convolution with preconvolved IRs


#1

Hi guys,
I´m working on a plugin that implements an EQ by convolution (using Juce::dsp::Convolution) with an IR library of a hardware EQ.
The EQ has several stages (Hi, Mid, Low, Filters etc) and I´m currently working on implementing these consecutive stages.
My first approach was to select the appropriate IR per filter stage according to the GUI values, then convolve the IRs with each other to get my master IR. Then, in the main proccesing function I convolve the audio buffer with the master IR.

This almost works (I have it working perfectly for a single filter stage, and for multiple stages with some inconsistency in switching IRs), but due to the thread safe design of the Convolution class, the IRs are not updated immediately in the filter stages, which leads to problems.
When I turn a knob in the GUI, I load the corresponding IR, and then call my own recalculateIR function that contains code like this:
AudioBlock<float> blockIR;
LF->getIRasAudioBlock(&blockIR);
prepareIRconvObj->loadImpulseResponse(HPF->getIRStreamData(), HPF->getIRSourceDataSize(), false, false, IRlength);
prepareIRconvObj->process(dsp::ProcessContextReplacing<float>(blockIR));

But calling the process function directly after loading the IR does not guarantee that the IR has already been updated accordingly. And then this IR update function is only called the next time when I touch the next parameter.

Could anyone please help me with an idea for a control flow, for first convolving several IRs with each other and then convolving the result with audio?


#2

Could I somehow wait for the thread to update the IRs, before I call process?


#3

There is no waiting in the audio thread.

What you can do is, prepare everything in the message thread from the GUI, and when all is ready, you set a flag, so the audio thread knows, it is safe to use the new one:

bool readyToSwap = false;
ScopedPointer<dsp::Convolution> activeConvolution;
ScopedPointer<dsp::Convolution> preparingConvolution;

// in buttonClicked:
preparingConvolution = new dsp::Convolution();
preparingConvolution->loadImpulseResponse(HPF->getIRStreamData(), HPF->getIRSourceDataSize(), false, false, IRlength);
readyToSwap = true;

// in processBlock:
if (readyToSwap) {
    activeConvolution = preparingConvolution.release();
    readyToSwap = false;
}
if (activeConvolution) {
    activeConvolution->process(dsp::ProcessContextReplacing<float>(blockIR));
}

Hope that helps…


#4

I don’t really get what the problem is.

The design of the loadImpulseResponse function when you give it a float array guarantees that the IR content is copied inside the convolution engine so it can be used in a thread-safe way. And every time there is new content to load, a thread is used to perform all the calculation, and then the process function performs some linear interpolation between the result using the previous IR and the result using the new IR, so there is no artefact at IR change and no interruption of processing.

So basically, you should be able to change the IR with a function call every time you move a knob. I don’t think you really want the audio thread to do instantaneously the whole update every time something has changed, since such a call would be very expensive, hence the thread use !

However, I see a flaw in the way you calculate the new IR. I think you should use a messaging system like I did in my Convolution class. What would happen if you move several times in a row your knobs, all trying to update the IR more or less at the same time ? What happens if you do so at a time the update is already happening ? The right way imha would be that messaging system, allowing you to merge all the “IR update call messages” which are identical, and to keep only the most recent ones when they are not. And then your update code might be either in a thread or called in the audio thread if it is fast enough and not allocating any new memory. You might take some inspiration from the class code.

But seriously, this kind of task sounds easier at first that it really is, this class is one of the most convoluted (…) one I ever written in terms of concurrency handling. You can also try to ignore some of the concurrency flaws but you might have some performance and stability issues.


#5

Hi guys,
thank you so much for taking the time to reply to my issue. This is very much appreciated!
To address your specific feedback:

@daniel: thanks for writing down this idea. this is not really what I plan to do, but it gives me the chance to explain my idea in more detail. In the buttonClicked part I want to create the final IR by convolving several IRs with each other. This is to save consecutive convolutions for each filter stage in the main audio processing thread.
Mathematical approach:

consecutive filter stages in main audio processing thread: y = (((i * IR1)*IR2)*...) )
prepare IR, to only have one convolution in process: y = i * (IR1*IR2*...) )
mathematically the same, but I could prepare the second version in advance

So, in the buttonClicked part, I gather all IR´s from the filter stages, load them consecutively into a second convolution engine and convolve them with each other.

> loadImpulseResponse
> process
> loadImpulseResponse
> process
> loadImpulseResponse
> process
> etc

This is where the problem arises, because I call process directly after load, and this doesn´t guarantee that the IR has already really been loaded. If I could wait for the convolution engine to really load the IR, I guess the waiting would then occur in the GUI thread and not the audio thread because it would happen in the buttonClicked method, no?

@IvanC: I can already change single IRs on rotating a slider. This works great with no clicks or anything! The problem is when I convolute multiple IRs in the buttonClicked function to use the output for my main convolution as I wrote above. Maybe the messaging system would be a solution, but so far I think that was not the problem. My sliders are discrete (value distance 1) and I already have the problems on a single click, that the IR hasn´t finished loading/preprocessing before the next processing (of the prepareIRconvObj) is called.

Please let me know if I can be clearer in certain points, I hope my goal is understandable.

Thanks!


#6

After having another nights sleep, I now think my question boils down to:
can I somehow make sure that the IR has really been loaded by the pimpl thread, before I call process on the convolution object?

Thanks!


#7

Hi guys,
in case someone was following this, I finally got it to work.
To preconvolve the IRs (from the GUI thread) I now use a very simple convolution code from this page: http://toto-share.com/2011/11/cc-convolution-source-code/
Then in the main audio thread I use the Juce Convolution class to convolve the audio with the preconvolved IRs. This finally works fine and I´m really happy with the solution now.
Thanks for your input anyway!
Best,
Christian