OK let’s talk about denormals for once and for all
What’s that beast ? When some DSP code uses any IIR filter in the large sense, you have to deal with things like that (for the sample number n) :
output[n] = gain1 * input [n] + gain2 * input[n-1] + … + gainA * output[n-1] + gainB * output[n-2] + …
Examples : the JUCE classes IIRFilter/dsp::IIR/dsp::StateVariableFIlter, any envelope follower (compressors, synths), anything where the current output is a function of any of the last output values. That means that a FIR filter or a delay line don’t have the problem. Add a feedback to your delay line and you get it.
What problem ? Imagine you have any DSP code processing some input samples != 0. Then you stop the transport bar in you DAW, and the DSP code gets only input samples = 0. You expect the output to become zero then.
Since you have feedback in the signal path, and given time constants in your filter, the output is a function of the last output values, and it is moving slowly from a given not null value to zero. At some point, that output is then going to be very close to zero (around say 1e-12 for a float). That’s where the accuracy of the float type is not enough to deal with a calculus using such a value. So what should be done here ?
By default, the Intel processor enter in a calculus mode which is a lot slower than the normal mode, to deal with these very low values called denormals. This way, there is no loss of accuracy, and the processing code can still calculates the output value with the right accuracy. And this way, the output is very precise, and goes down to 1e-20, 1e-30, and 1e-40, and 1e-50, and… wait ! That’s not what we want. Below a given threshold, what we want in audio is zero, not -600 dB ! What could we do from values like that ?
Now here is the second problem. If you have a plug-in with some processing having a feedback in the signal path, not handling the first problem means that when you click on “stop” on the DAW transport bar, the CPU is going to enter in “denormals mode” at some point to return these nice but useless outputs at -600 dB, and the CPU load is going to increase a lot at that moment ! That’s not good at all. And the duration of the CPU load increase can be very high if the time constants of the processing are long.
So how do we deal with that ? Two solutions :
- the SnapToZero method, which is basically a detection on some state variables of the denormals, which replaces values very close to zero with zero. It’s like saying “I don’t care about signals below -300 dB in amplitude, if I see one, I’ll replace it with zero”. That’s what I use most of the time in my DSP code.
- the SSE flags set to the right value to disable handling of denormals. It should be done once at the beginning of the main processing code, every time the process function is called, and everything that has been changed should be put to its initial value after the processing code. The new class that Fabian has added does exactly that !
So now, why have I used the first solution in the DSP module code ? The reason is simple : that’s because it’s compatible with everything (32 bits without SSE, ARM etc.). And the snapToZero functions are called once in every processing block, not once for each sample processed ! So the overhead of the method is not significant at all, and so no one can say that method is slower than the other one…
And I’d like to say that using systematically the second method is also a very good way to deal with denormals without understanding what they are, which is not a good practice for DSP engineers in my opinion. I like to use the first method because it forces me to look for the locations in my code where there is feedback, and put there the snapToZero calls. If i forget one, I get the slow down when the input is zero…