BLowPass and addition creates unexpected distortions

Hi all!

I have encountered a strange issue that I will try to describe as clearly and succinctly as possible with the hope that one of you might have an idea of what is going on.

In short: When filtering any audio signal using a BLowPass UGen with audio rate arguments and summing the output of the UGen with the signal used as input, strange distortions occur.

I have reduced the code to reproduce the issue to the following:

SynthDef.new(\fails, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = DC.ar(1);

	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, filtered + input);
}).add();

On 3.13.0-dev on Ubuntu and MacOS and 3.12.2 on MacOS this synth outputs a kind of pulse train modulated with the input signal. As a comparison, the same code but with the BLowPass swapped for an RLPF works fine:

SynthDef.new(\works1, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = DC.ar(1);

	filtered = RLPF.ar(input, freq, rq);
	Out.ar(0, filtered + input);
}).add();

I have some additional versions of the original SynthDef that output the expected result, but I cannot understand why they work and the first one doesn’t, but hopefully they can help in narrowing down the problem.

Changing the frequency or the rq argument to BLowPass to a literal number makes everything work as expected.

SynthDef.new(\works2a, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = 200;
	rq = DC.ar(1);

	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, filtered + input);
}).add();

SynthDef.new(\works2b, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = 1;

	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, filtered + input);
}).add();

Not adding the filtered output with the input, or outputting them separately also work.

SynthDef.new(\works3a, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = DC.ar(1);

	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, filtered);
}).add();

SynthDef.new(\works3b, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = DC.ar(1);

	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, input);
}).add();

SynthDef.new(\works3c, {
	var input, filtered, freq, rq;

	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = DC.ar(1);

	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, filtered);
	Out.ar(0, input);
}).add();

So to trigger the problem we need a combination of:

  • Using a BEQSuite filter (N.B. I haven’t tried all of them yet).
  • Have all the filter arguments be audio rate signals.
  • Adding the input to the filter with its output inside the SynthDef.

I really don’t know where to go from here. If anyone has any idea of what is going on, what I could try or how I could maybe even solve the issue I would be very grateful.

Thanks,
Ludvig

Sounds like it’s aliasing and I’m almost positive it’s because you have the frequency and rq arguments as audio rate. If you change them to control rate, you’ll get a totally different result.

Better still, is there a particular reason you’re using audio rate DC offset when you could just feed the values into the BLowPass Ugen directly?

1 Like

Yes, in the actual use case, I am using a modular patching environment where I modulate many things at audio rate. The use of DC.ar is to make a portable simple example that reproduces the problem without the whole system.

However, audio rate arguments work, as long as I don’t sum input and filtered in the SynthDef.

I think making changes to the filter coefficients is a reasonable thing to do (e.g. changing the cutoff frequency), and as I demonstrated above, it is indeed working fine in many cases (see the \works2 and \works3 SynthDefs in the original post).

I don’t think there is anything inherently bad with using pseudo UGens.

BLowPass is not a pseudo-ugen - it is compiled C++ code:

BLowPass : BEQSuite {
*ar { arg in, freq = 1200.0, rq = 1.0, mul = 1.0, add = 0.0;
^this.multiNew(‘audio’, in, freq, rq).madd(mul, add);
}

the *sc method is a method to give you coefs for a given frequency (and reflects the math in the c++ code). I think what you are doing should work, so I’ll need to look into it closer to figure out why. Can you please post a bug on the GitHub project?

Thank you for looking into this. I will file a bug on the GitHub page as you suggest.

1 Like

No problem! I wrote the UGens and am familiar with the code. Hopefully I can find the problem.

Josh

1 Like

Maybe the UGen side effects the input signal? The problem disappears when using a copy of the input instead:

SynthDef.new(\fails, {
	var input, copyOfInput, filtered, freq, rq;
	
	input = Saw.ar(50, 0.01);
	freq = DC.ar(200);
	rq = DC.ar(1);
	
	copyOfInput = Sanitize.ar(input);
	filtered = BLowPass.ar(input, freq, rq);
	Out.ar(0, filtered + copyOfInput);
}).add();
1 Like

Good find! But what about the need for addition? Side effects should be there when outputting the filtered signal alone right?

I will add this example to the bug report on GitHub.

If you don’t add to the filtered output, like in your works3b, I think the filtering is optimised away, because it’s output is not used.

Yes, that is a good point. But what about it working when you output the filtered and input separately, as in \works3c? Then both are in use and the both sound as they should. I think that is the strangest case somehow.

I guess that the Out.ar(0, input) runs before the filtering.

1 Like

Ah wow, yeah that would actually explain it.

I think I must have been looking at the wrong file… Thanks for the correction and sorry for my bad information!