macOS audio dies if LPF cutoff = 0

Hi all,

Noticed something odd today. A student submitted an assignment with a Synth that includes LPF with an uninitialized cutoff freq. When the Synth is instantiated, it seems like my computer’s entire audio system goes silent while the Synth is alive. When freed, my computer’s output sound returns. I’ve boiled it down to a simple version here. This is potentially dangerous code — it doesn’t blow up on my system, but I don’t know what might happen with other operating systems, so please be careful.

s.boot;

//....e.g. youtube video playing in background or other system sound...

x = {arg cf; LPF.ar(Silent.ar!2, cf)}.play; //everything goes silent

x.free; //system sound returns to normal

I switched from built-in output to my HDMI audio output, same problem. Obviously, it’s not a great idea to leave a cutoff parameter unspecified, but still, this seems weird/interesting. I suspect it’s CoreAudio-related — can any other macOS users out there reproduce this? Or is it just me? Curious to know why this happens/what’s going on under the hood…

Eli

1 Like

Not an expert on this, so somebody correct me if I’m wrong, but this looks like you are dividing by zero.

void LPF_next(LPF* unit, int inNumSamples){
    ...
    // I think the argument would be this 'freq' variable below...
    double pfreq = freq * unit->mRate->mRadiansPerSample * 0.5;

    // meaning this line would be divide by 0, so C is inf.
    double C = 1.f / tan(pfreq); 
....
}
taken from github 
https://github.com/supercollider/supercollider/blob/18c4aad363c49f29e866f884f5ac5bd35969d828/server/plugins/FilterUGens.cpp#L2069

When you divide a float by a zero I think you get an ‘inf.’ in C. It is still a float type but not a number (floats are complicated IEEE 754 - Wikipedia ). Now I think if you try to do maths with this it produces a ‘nan’ float (not a number), which I guess the audio server can’t deal with. I tried it on linux with jack and got the same result. The fact that it mutes everything is probably to try and protect the speakers and is considered a polite thing to do.

J

1 Like

I can reproduce this, though I’m also on macOS so no surprise here.
If you poll the output of LPF, it’s reported as nan. I’m guessing that when nan enters the audio stream and is being passed down to CoreAudio, it messes up mixing downstream (I’m guessing that x + nan = nan so essentially the mix becomes invalid).

There is this warning in the LPF help file:

WARNING: due to the nature of its implementation frequency values close to 0 may cause glitches and/or extremely loud audio artifacts!

And it seems an uninitialized control is set to 0 by default. So at least it’s documented, and maybe the safety-limit feature we got recently has made the experience slightly less ear-shattering.

I think sclang could automatically create a Sanitize + Clip instance as the last nodes in the Server tree to keep the output in a sane range. If someone really needs those CPU cycles, there could be an option in Server to disable it.

Yes! No lack of latency is worth potential hearing loss.

1 Like

This was pretty much my guess too. Just to be clear, my surprise is not that LPF behaves unstably if its cutoff frequency is zero. My surprise is that SC has the ability to “poison the well” so to speak — that it can influence the mix downstream, in Core Audio land. I’m just surprised I haven’t encountered this before.

1 Like

inf + anything = inf

nan + anything = nan

I’d be surprised if CoreAudio could do anything to override floating point math rules. If SC outputs NaN, and CoreAudio tries to mix NaN with anything else, it’s still going to be NaN going out to the soundcard.

BTW you can clip inf – inf.clip(1, 2) is 2.0 – but you can’t clip NaN: (0/0).clip(1, 2) is -nan. So I ended up handling it with Select (see ddwMixerChannel/MixerChannelDef.sc at master · jamshark70/ddwMixerChannel · GitHub ) – but I wrote this before Sanitize…

hjh

1 Like

Actually there are SIMD instructions on at least Intel, and I would guess ARM, that do exactly this. It’s always been a little baffling to me that Apple didn’t fix this.

FWIW, JACK doesn’t trap bad numbers either. I just tested by playing a video of Rzewski performing “The People United” (VLC → PulseAudio → JACK) and then, in SC:

a = { (DC.ar(0) / 0).dup }.play;

… click, silence.

Basically, the question is whether a/ a multiclient audio-server framework (CoreAudio, JACK, etc) should check every output sample from every client app individually for bad values, or b/ is it each client’s responsibility not to output the bad values in the first place?

hjh

1 Like

These concerns about how supercollider handles nan and inf are very important, but in the context of this issue cutoff should be controled with sc_max in order to avoid 0 frequency. (Althought I am not sure which should be the minimun allowed freq)

For me, this is a satisfying answer to my original question — it’s a matter of design, and I suppose there’s a case to be made for both options. I feel like I now understand why the entire audio system goes down.

A different question of how to deal with “bad” values exclusively within SC is something I’m less concerned with. I know it’s “bad” to set filters’ cutoff frequencies to zero, and I’m aware of various tools (SafetyLimiter, StageLimiter, Sanitize, CheckBadValues) and other strategies for avoiding these problems.

Thanks all for the thoughts.