LPF with a cutoff that goes straight down

I’ve been wondering if it is possible to cutoff a band of frequencies with a straight and vertical line. I mean that if you’d set the cutoff frequency to 200, then even the frequency 201 would be completely cut off. Im asking because i’d like to for example only hear the low pitched aliasing artifacts of a LFSaw oscillator and completely filter out all higher frequencies that are not mirrored back. I’ve tried something like this to make the cutoff steeper. {LPF.ar(LPF.ar(LPF.ar(LPF.ar(LFSaw.ar(MouseY.kr(2600,2900)),1045),1045),1045),1045)!2;}.play;
Should i just continue nest LPF’s until the cutoff is completely vertical or are there better ways to do this?

This is more of a job for FFT / PV_BrickWall. LPFs will never give you a vertical cutoff - you’d need an infinite number for an infinite slope.

In signal processing lingo, this is known as an ideal filter or sinc filter. Such a filter has an impulse response shaped like the function sin(x)/x, which extends infinitely in both directions. Ideal filters present practical issues in a digital context. The big ones are potentially unlimited CPU usage and infinite latency in real-time.

The field of filter design, as found in telecommunications and electrical engineering, is largely about how to design filters that are as ideal as possible while still being practically implementable. For example, you can truncate the impulse response of the ideal filter to produce an FIR filter, then convolve that with the input signal. The longer the impulse response, the better the frequency response, but at the cost of latency and CPU usage. Converting the filter to minimum phase reduces the latency, but at the cost of phase distortion. Such tradeoffs are typical in this field.

SuperCollider does not have many near-ideal filters, mostly because they are quite hard to implement. It would be cool to have higher-order Butterworth, Chebyshev, or elliptic filters, which all allow close approaches to ideal filters. They look like this (image from Wikipedia), and they get sharper as you increase the filter order:

500px-Filters_order5.svg

There are many other ways to design near-ideal filters. A practical challenge is that most standard filter design methods are only built for fixed cutoff filters, and making them respond well to cutoff modulation is highly nontrivial. Like I said, actual implementation is hard, so the open source community doesn’t offer a lot of options.

Using FFT (e.g. PV_BrickWall) is not common in the field due to 1. its relative CPU usage, and 2. a Gibbs phenomenon as a result of the FFT window. Basically there are little “peaks” as you approach the cutoff of the brick wall filter, which are heard as resonances. It still might sound fine though, try it.

To address your specific problem: maybe try subtracting an LFSaw from an antialiased saw UGen, like Saw or SawDPW? Make sure they are perfectly matched in amplitude and phase.

4 Likes

@Aueh, the SignalBox quark includes a rectangular windowed sinc design method. The returned Signal can be further windowed to smooth out the lowpass roll off. (One of my favorites is the Kaiser Window.)

To filter a sound, load this filter kernel into a Buffer, and supply to Convolution2.

Also of interest, SignalBox’s *gaussianBank will return a collection of matched linear phase kernels that can be used to filter a signal into selected frequency bands. Supplying a single frequency will return a matched low/highpass pair. An Array with more will give a collection of bandbass filters along with low and highpass to complete the decomposition. (Fun!)

As @nathan states, the cost is a much higher CPU hit… but sometimes you need to pay the price for a super selective filter.

2 Likes

You can try miSCellaneous_lib’s PV_BinRange. It’s a wrapper based on PV_BrickWall, where you pass low and high bin, calculation from frequencies is straight, see helpfile example (have in mind the inherent flaws mentioned by Nathan). Also, there’s the BandSplitter quark.

To illustrate:

(
a = {
	// using a 512-point fft buffer, bins are spaced...
	var n = 512;
	var hzPerBin = SampleRate.ir / n;
	// the nearest bin to 440 Hz is...
	var freq = 440;
	var bin440 = round(freq / hzPerBin);
	var nearestFreq = bin440 * hzPerBin;
	// modulate frequency within a P5 around that
	// upper frequencies should be cut out completely
	// lower frequencies basically pass through
	// just near the brickwall, massive harmonic distortion
	var sig = SinOsc.ar(
		nearestFreq * LFTri.kr(0.2).exprange(-7.midiratio, 7.midiratio)
	);
	var fft = FFT(LocalBuf(n, 1), sig);
	fft = PV_BrickWall(fft, (bin440 / n) * 2 - 1);
	(IFFT(fft) * 0.2).dup
}.play;
)

“BrickWall” is something of a misleading name. It suggests that you get full-strength content very, very, very close to the cutoff, and suddenly 0 amplitude past that point. The actual sound is nothing like that. (I’ve deliberately used a small FFT frame to exaggerate the effect. With a larger frame, the effect would be compressed into a narrower frequency range, but the artifacts will never go away.)

For the original question, given an LFSaw frequency and a sample rate, it should be possible to calculate the aliased frequencies and their amplitudes, and synthesize them additively.

hjh

So many suggestions and ideas; again i’ve learned alot as a beginner. Thanks all!

I cant seem to perfectly match them. This is as close as i can get.

{[LFSaw.ar(-200,1), Saw.ar(200,2)]}.plot(0.5);

But when i subtract them there is still alot of sound.

So just a question, so i can try to build the artifacts myself. An aliasing ugen will mirror back anything over 22khz(judging from the freqscope window)? And they will be mapped back like this: 22001—>21999, 22002 —> 21998 etc. I dont know how the amplitudes are mapped. To be honest i dont know anything about how aliasing is done in supercollider. Is there any place where i can read up on this?

Cool i was also looking for something like that!

… mirror back anything over half the sampling rate. At 44.1 kHz, this is 22050.

You can use .fold(0, s.sampleRate * 0.5) to get from a calculated too-high frequency to its aliased equivalent.

hjh