2011
08.06

Let’s explore a setting where we want to filter a noise source with three parallel resonators.

To start, we can create a resonator function and a test bed:

Noise()
{
        rng = z-1('0.5d Audio:Clock(rng * 3.9999d * (1d - rng)))
        Noise = Coerce(Float rng) - 0.5
}

Reson(x0 freq reson)
{
        x1 = z-1('0 x0)
        x2 = z-1('0 x1)
        y1 = z-1('0 y0)
        y2 = z-1('0 y1)
        r = Crt:pow(reson 0.125)

        y0 = x0 - x2 + y1 * 2 * r * Crt:cos(freq) - y2 * r * r
        Reson = y0 * 0.5 * (1 - r * r)
}

Filtered-Noise()
{
	freq = OSC:In("/1/freq" '0.125)
	res = OSC:In("/1/res" '0)
	Filtered-Noise = Reson(Noise() freq res)
}

If you listen to ‘Filtered-Noise()’, you should get a resonant band of noise controllable by OSC methods ‘/1/freq’ and ‘/1/res’.

We can co-opt some of the higher order functions we learned in part 5 to easily make a bunch of them. You may notice that if we feed the same input to all resonators, the only difference between them is the OSC method they listen to.

Let’s make a helper function that produces a resonator with proper OSC control paths when given the number of the band in question;

Import String

Filter-Band(sig number)
{
	band-string = String:Concat("/" Coerce(String number) "/")
	freq = OSC:In(String:Concat(band-string "freq") '0.125)
	res = OSC:In(String:Concat(band-string "res") '0.4)

	Filter-Band = Reson(sig freq res)
}

We import ‘String.k’ to provide us with the concatenation method, ‘String:Concat’. Using it, we construct OSC methods based on the band number. Passing ‘#1′ to Filter-Band would yield “/1/freq” and “/1/res” and so on.

What remains is to construct a bank of filters;

Import Algorithm

Filter-Bank3()
{
	Use Algorithm

	sig = Noise()
	Filter-Bank3 = Reduce(Add Map('Filter-Band(sig arg) #1 #2 #3))
}

This routine uses Map to turn a list of numbers ‘(#1 #2 #3)’ into three filters using the ‘Filter-Band’ function we defined. Note that we actually construct an anonymous function on the go; it passes ‘sig’ to ‘Filter-Band’ as the first argument, and its own argument (#1, #2 or #3 in this case) as the second. This is actually a closure; the anonymous function is bound to ‘sig’ which is outside its scope.

The outputs of these filters are then summed using ‘Reduce’. The ‘Use Algorithm’ directive inside the function merely indicates that functions can be found inside the ‘Algorithm’ package. You may recall that we had to use ‘Algorithm:Map’ and ‘Algorithm:Reduce’ before.

Not clever enough for you yet?

Filter-Bank(N)
{
	Use Algorithm

	sig = Noise()
	Filter-Bank = Reduce(Add Map('Filter-Band(sig arg) Expand(N Increment #1)))
}

This filter bank function accepts a parameter specifying the number of bands you wish to create. The desired amount of OSC methods ranging from ‘/1/…’ to ‘/N/…’ will be made, and all the filters will be summed as in the three-filter example. Note that you must pass the number as a directive, such as ‘#5′. The reasons for this should become clear in part 11.

If this is still not enough for you, notice that you can address all ‘res’ methods with the OSC method ‘/*/res’. You can address the frequency of bands 3-7 with ‘/[3-7]/freq’. See the OSC specification for full address pattern matching reference.

To wrap up this tutorial, let me estimate the performance hit caused by using all this high level abstraction: 0.1

 

  1. DISCLAIMER: Compilation time may increase with growing abstract complexity. []

No Comment.

Add Your Comment


− 1 = three