Morphing Filters

Hi All,

just starting out with SuperCollider, so thought I’d start out trying to recreate an (analogue-style) drum/percussion synthesis voice modelled on the DM2 iOS app.

It’s going reasonably well, but I have some questions that are perhaps more conceptual than SC-specific, that I wondered if someone here might have some advice.

I want to implement a morphing filter, that goes from low, to band, to high-pass configurations. I thought of attempting this with a combination of a low and high pass filter in series, but then wondered if a simple crossfade of the outputs of the three filter types might work just as well.

Has anyone tried to implement a filter that does exactly this, and if so, how did you achieve it?

One of the things that worried me about either approach is the potential problem of multiple resonance peaks. I’d really like to have resonance (or at least a peak around the nominal cut-off frequency), but again, I’m unclear about the best way to achieve that.

Any tips greatly appreciated!

You could look at SelectX:

{

var sig, freq, which;

sig = WhiteNoise.ar(0.1).dup;

freq = SinOsc.kr(0.1).range(100, 10000);

which = MouseX.kr(0, 2);

SelectX.ar(which,

	[

		LPF.ar(sig, freq),

		BPF.ar(sig, freq),

		HPF.ar(sig, freq)

]);

}.play

which will crossfade between inputs.

Sam

For serial filtering check the BEQ suite in main SC. Here’s a rough example for a 3-band EQ:

(
SynthDef(\threeBandEQ, { |out = 0, in, amp = 1, mix = 1, freqs = #[200, 1000, 3000],
	rq = 1, dbs = #[0, 0, 0]|
	var inSig = In.ar(in, 2), sig;

	sig = BLowShelf.ar(inSig, freqs[0], 1, dbs[0]);
	sig = BPeakEQ.ar(sig, freqs[1], rq, dbs[1]);
	sig = BHiShelf.ar(sig, freqs[2], 1, dbs[2], amp);

	XOut.ar(out, mix, sig);
}).add;
)

s.freqscope

// start silently
y = Synth(\threeBandEQ)

x = { WhiteNoise.ar(0.1 ! 2) }.play

y.seti(\dbs, 1, -30)

y.seti(\dbs, 1, 20)

y.set(\rq, 0.1)

y.seti(\freqs, 1, 1200)


y.seti(\dbs, 0, 30)

y.seti(\dbs, 2, 10)

y.seti(\dbs, 2, -30)


(
x.release;
y.free
)

With more peak filters you can control every single resonance with a different rq (and accordingly the shelf rs, which I haven’t done here).
SelectX, as Sam suggested, is also fine. Then everything is running in parallel but as filters are relatively CPU-cheap this shouldn’t be a problem.

Thank you both for the super-quick responses!

I will try both, and see which I prefer…

Thanks again.

I went with aspects of both your solutions, with variations, in the end:

(
    {
        var sig, freq, which, lp, bp, hp;

        sig   = WhiteNoise.ar(0.1).dup;
        freq  = MouseX.kr(50, 1000, 1);
        which = MouseY.kr(0, 2);

        lp = BLowPass4.ar(sig, freq, rq: 0.3);
        bp = BHiPass4.ar(lp, freq: freq, rq: 1);
        hp = BHiPass4.ar(sig, freq: freq, rq: 0.3);
	
        SelectX.ar(which, [lp, bp, hp]);

    }.play;
)

I was finding that the bandpass filter wasn’t giving the slope I wanted, so I opted for applying highpass to the lowpass-filtered signal, and using 4-pole filters.

I turned off resonance (I think) when creating the BP signal, because the LP had already created a resonant peak around the cutoff, and I didn’t want to make the peak bigger by adding more resonance from the HP filter, on top.

Thanks again guys!

I have a followup question:

If I wanted to control the width of the passband for the BP filter in my setup, and keep the width perceptually similar, as the cutoff frequency changes, how might I go about that?

I’d obviously need to shift the high-pass filter cutoff down to widen the band, but I guess it would sound weird if the HP cutoff remained the same distance from the LP cutoff across the whole range, given the exponential nature of pitch.

Sorry if this is really basic stuff. I’ve been working with synthesisers for a long time, but am relatively new to DSP, so I know more-or-less what I want, but don’t always (often) know how to get there…

Working nicely!

s.freqscope

// Morphing filter
// Low > Band > High-pass
// https://scsynth.org/t/morphing-filters/1790/6

(
{
// Declare vars
var sig, freq, fMorph, lp, bp, hp, fMorphCurve;

// Filter morph delta curve envelope
fMorphCurve = Env(
   levels:[0, 0.49, 0.51, 1],
   times: [0.4, 0.2, 0.4],
   curve: [-2, 0, 2]
);
// Plot envelope
//fMorphCurve.plot;

// Generate morph position using curve defined in envelope
fMorph = IEnvGen.kr(fMorphCurve, MouseY.kr(0, fMorphCurve.times.sum));

// Filter cutoff
freq = MouseX.kr(50, 1000, 1);

// Render noise signal
sig = WhiteNoise.ar(0.1).dup;

// Render Low/Band/High-pass signals
lp = BLowPass4.ar(sig, freq, rq: 0.25);
bp = BHiPass4.ar(lp, freq: freq, rq: 1);
hp = BHiPass4.ar(sig, freq: freq, rq: 0.3);

// Mix through signals
SelectX.ar((fMorph * 2), [lp, bp, hp]);

}.play

)
2 Likes