Hello everybody!
I was doing some research work about reverberation algorithms lately, and I looked into the venerable good ol’ juce::Reverb
algorithm to see how it was designed. I remember the LinearSmoothedValue
class was originally there as a private class, before being publicly available in the modules, so I thought I could still learn one thing or two by looking at it again in its very last version.
It appears it is inspired from the Freeverb “iteration 1” algorithm that is open source since forever, and that Jules added it in JUCE around 2014 with more or less the same implementation and “tuning” values for the delay line sizes than in the original. It is a simple “Schroeder’s” structure reverb with parallel recursive comb filters and serial delay based all-pass filters, without anything fancy, which explains why the algorithm sounds a bit metallic and raw. So, the class uses internally those basic blocks thanks to internal private classes, called CombFilter
and AllPassFilter
, doing supposedly what their names suggest. Have a look at the CombFilter
process function:
float process (const float input, const float damp, const float feedbackLevel) noexcept
{
const float output = buffer[bufferIndex];
last = (output * (1.0f - damp)) + (last * damp);
JUCE_UNDENORMALISE (last);
float temp = input + (last * feedbackLevel);
JUCE_UNDENORMALISE (temp);
buffer[bufferIndex] = temp;
bufferIndex = (bufferIndex + 1) % bufferSize;
return output;
}
As you can see, it is not the vanilla comb filter, since it has also an additional parameter called “damping” which can be used to low-pass a bit the output using a simple additional state variable, which is an idea introduced by Moorer if I’m not wrong. But then, here is the process function for the AllPassFilter
class:
float process (const float input) noexcept
{
const float bufferedValue = buffer [bufferIndex];
float temp = input + (bufferedValue * 0.5f);
JUCE_UNDENORMALISE (temp);
buffer [bufferIndex] = temp;
bufferIndex = (bufferIndex + 1) % bufferSize;
return bufferedValue - input;
}
This is not an all-pass filter at all!! In fact, it’s a comb filter again with additional gain!
When I discover this, I was wondering why, and then I found out that the original Freeverb1 algorithm was doing this too, and that this issue was already quite well documented:
https://www.dsprelated.com/freebooks/pasp/Artificial_Reverberation.html
Funny isn’t it? So I was wondering if it could be possible to documentate that a bit more in the juce::Reverb class too, since it can be misleading for JUCE SDK users to come upon a class called “All-Pass Filter” which is actually not an all-pass filter at all
And if you are wondering what happens when you replace the “Freeverb” APF comb filter with a true APF, here is what you get:
Original
True APF
Interesting right?