Running SC 3.13 on Apple Silicon, I’m experiencing the following behaviour: after sending a signal into FreeVerb, then waiting around 30 seconds, I get a stream of denormals until I send another signal into the Bus or free the synth.
b = Bus.audio(s,2);
SynthDef(\verb,{
var sig = In.ar(\in.kr(),2);
sig = FreeVerb.ar(sig,1,0.9,0.99);
Out.ar(\out.kr(),sig)
}).add;
x = Synth(\verb,[\in, b]);
(
p = Pbind(
\dur,0.1,
\legato,Pseq([0.2],20),
\out,b
).play
)
// now wait 30 seconds or so...
...
CheckBadValues: normal found in Synth 1044, ID 0 (previous 355 values were denormal)
CheckBadValues: denormal found in Synth 1044, ID 0 (previous 1 values were normal)
CheckBadValues: normal found in Synth 1044, ID 0 (previous 92 values were denormal)
CheckBadValues: denormal found in Synth 1044, ID 0 (previous 1 values were normal)
...
In this example Synth 1044 was a synth from the SafetyNet quark; after using CheckBadValues before and after FreeVerb it seems to be the source of the junk. I’m going to posit this is a bug and not a feature? can anyone else reproduce this behaviour?
Have you tried adding a very small noise signal to the input of the reverb? Something like PinkNoise.ar(0.0000001) or the like? Reverbs get nervous with “pure silence”.
That seemed to do the trick - weird that I’ve never experienced that before…encountered this behaviour with code that I’ve been running for several years now! But thanks for the fix!
Pure Silence isn’t the exact problem. It is Pure Silence AFTER something has already been fed into the system, and you start getting the tail levels of a reverb as they approach zero. Theoretically - a reverb should never reach zero ONCE a signal has been fed into it. So - silence after an input is where we get the denormals. Zero in to a reverb is fine as long as it stays zeroes. But then - that’s a worthless reverb
I don’t think so… the point, to avoid denormals, is to add a very small non-zero signal. Silent.ar renders as DC.ar(0) – therefore sig + Silent.ar(1) will be equivalent to sig and the reverb result would be unchanged, denormals and all.
In any case… FreeVerb seems not to do anything about denormals.
At the server level, there’s sc_SetDenormalFlags() (“// Set denormal FTZ [‘flush-to-zero’] mode on CPUs that need/support it”). On my machine (Intel CPU), I don’t get denormals – so this is definitely working in some/many environments. Perhaps Apple Silicon needs different flags…?
Bumping this thread as I’ve noticed more Ugens spitting out denormals…This snippet spits out a bunch of denormals upon instantiation and then settles down after a second or so:
Removing the second .cubed method seems to fix it… which would suggest some more close-to-zero numbers being the issue, wouldn’t it? Perhaps someone with better knowledge of the server can help me out here?
I understand the solution is to add a little noise… but why does SC reverb get nervous from a little silence? Even an audacity reverb would not be that nervous…
Anyways, also got recommended Sanitize.ar. Have you tried this Mike?
I checked it now, I still get denormals from the .dup.cubed.cubed! Maybe I should actually file a bug report…
I haven’t used Sanitize.ar, but I often use CheckBadValues.ar with SelectX.ar to replace nans, nils, infs…this way I get a printout when bad things happen and I can try to fix them. But Sanitize looks really good!
I believe my comment from 2.5 years ago still holds.
When scsynth boots, it calls a function sc_SetDenormalFlags(). I’m not an expert on the details here, but it looks to me like various processors can set a mode per process (or per thread?) where the FPU will automatically replace denormals with zeros.
If that setting is taking effect, there would be no need for SC code to handle denormals explicitly, as the FPU would block denormals before they reach any user code.
And I still confirm that on my Intel CPU, I can’t reproduce the denormals that others are seeing. I think this is because this bit…
The only other part of this function is an #elif defined(__VFP_FP__), which a comment identifies as being for “the Cortex A8, along with the SIMD Neon unit” = basically, Bela. This was added 4 years ago.
The Intel bits of this function are 13 years old.
Missing from the function is any mention of Apple Silicon chips. So it’s quite possible that we just aren’t doing anything about denormals on recent Macs (unless the SIMD directive applies to Silicon chips too…? I’m well outside my expertise here but I’d guess that the X86 in the identifier rules that out). Hence, I think at minimum this function needs to be reviewed for applicability to Silicon chips.
For reference and completeness, the most common strategies are:
1 FTZ/DAZ
A simple flag to the C compiler will allow you to enable or disable flushing denormal floating-point numbers from your program. For example, with gcc and clang, the compiler flag allows this feature.
You can test compiling this program with and without the flag:
There are other alternatives, this one is particularly ingenious, and great for dsp because it is branchless:
#include <limits>
#include <cmath>
state *= fabs(state) > std::numeric_limits<float>::min();
Why is the branchless alternative particularly well-suited for real-time audio code?
Well, technical reasons:
a) The code becomes more predictable, and CPUs can struggle with unpredictable branches
b) No conditional jumps
c) SIMD-friendly: Can be vectorized more easily in C++
d) Consistent timing
Consequence: when operating with many filter states to check, you will prefer to avoid branches in your inner loop
3 SIMD-specific situations,
In this case, vector operations already have built-in denormal handling.
NOTE Sometimes a program can choose different alternatives.
Since each situation is different, if you are serious about it and optimization, you may see yourself using more than one option:
a) FTZ/DAZ is fast (the first alternative), but affects the entire thread, not just your dsp function (it may not be the best option)
b) DC offset is super simple, but remember that it will add coloration to your filter (listen if you like it or not)
c) Noise injection: this is not bad for filters, can be used, and even makes it sound a bit better hehe
d) State clamping has precise control, but with lots of conditional checks (opposite of the branchless alternative)
e) There are also a few algorithms, which may bring some benefits, but will also require maintenance compared to the other options
EDIT: The fact that we are still discussing this in 2025 means it will likely remain a topic for some time.
We can summarize the points made here. This problem is:
Architecture-dependent
Algorithm-dependent (reverbs with high feedback are particularly vulnerable)
Time-dependent (takes time for decay to reach denormal range)
Still a pain in the ass
I have a particular preference for noise insertion.
Why? Well, it
Decorrelate stereo channels slightly
Mask quantization artifacts
Prevent DC buildup
I imagine that those are positive qualities in audio dsp filters
@jamshark70 You’re correct - sc_SetDenormalFlags() is missing Apple Silicon support.
Your link is good. I also checked others. While Apple provides FE_DFL_DISABLE_DENORMS_ENV in their fenv.h, this constant is Apple-specific and doesn’t exist on Linux ARM64 or other platforms. So, I believe it would not be the ideal solution.