//SLUGens released under the GNU GPL as extensions for SuperCollider 3, by Nick Collins, http://composerprogrammer.com/index.html

LTI Linear Time Invariant General Filter Equation

LTI.ar(input, bufnuma, bufnumb, mul, add)

Represents the general LTI filter difference equation in the time domain:

y(n) = b0x(n) + b1x(n-1) + ... + b(Nb)x(n-Nb) + a1y(n-1) + ... + a(Na)y(n-Na)

This is not a pole/zero view, so you'd need to calculate time domain coefficients yourself if you want to work from z-plane backwards. A corollary is, stability is not guaranteed. This is part of the fun?

You need to pass in the coefficients via two buffers, of arbitrary size.

input- What do you want to filter?

bufnuma- Feedback filter coefficients, from previous outputs

bufnumb- Feedforward filter coefficients, from previous inputs

(

a=[0.02,-0.01]; //feedback coefficients

b=[1,0.7,0,0,0,0,-0.8,0,0,0,0,0.9,0,0,0,-0.5,0,0,0,0,0,0,0.25,0.1,0.25]; //feedforward coefficients

c=Buffer.sendCollection(s, a, 1);

d=Buffer.sendCollection(s, b, 1);

)

{LTI.ar(AudioIn.ar,c.bufnum, d.bufnum)}.play

//Note- you cannot update buffers during playback unless you stay within the initially allocated sizes

(

a=Array.fill(100,{0.0}); //feedback coefficients

b=Array.rand(100,-0.5,0.5); //feedforward coefficients

b[0]=1;

c=Buffer.sendCollection(s, a, 1);

d=Buffer.sendCollection(s, b, 1);

)

{LTI.ar(AudioIn.ar,c.bufnum, d.bufnum)}.play

(

b=Array.rand(100,-0.5,0.5); //feedforward coefficients

b[0]=1;

d.sendCollection(b);

)

//may explode...

(

10.do({arg i; a[100.rand]=rrand(-0.1,0.1)}); //feedforward coefficients

c.sendCollection(a);

)

//from a routine

(

e={inf.do {

b=Array.rand(100,-0.5,0.5); //feedforward coefficients

b[0]=1;

d.sendCollection(b);

0.1.wait; }}.fork

)

e.stop;

//Code for testing and trying coefficients:

//given two arrays of filter coefficients, calculate an impulse response over 1024 samples, then the fft gives approximate frequency gain and phase response

(

var size = 1024, real, imag, cosTable, complex;

var a,b;

var lastn,lastindex,num;

var y, max;

a=[0.02,0.05,0,0,0.01]; //feedback coefficients

b=[1,1,-0.5,0,0,0,-0.6,0.7]; //feedforward coefficients

//check poles of a are inside the unit circle by factorising the complex polynomial?

//this procedure uses only a finite impulse response so may give fallacious results of stability

num=a.size;

lastn=Array.fill(num,{0});

lastindex=0;

real = Signal.fill(size, {arg i;

y=if(i<(b.size),{b[i]},{0});

y= y+((a.collect({arg val,j; val*(lastn.wrapAt(lastindex+num-1-j));})).sum);

lastn[lastindex]=y;

lastindex=(lastindex+1)%num;

y

});

imag = Signal.newClear(size);

cosTable = Signal.fftCosTable(size);

complex = fft(real, imag, cosTable);

a=complex.postln;

[real, (complex.magnitude), (complex.phase) ].flop.flat

.plot("fft", Rect(0,0, 1024 + 8, 500), numChannels: 3);

max=0;

y=complex.magnitude;

y.do{arg val; if(val>max,{max=val;})};

max

)

//how to create the arbitrary filter from its difference equation coefficients? Need a new UGen (LTI)- or use Csound

(

a=[0.02,0.05,0,0,0.01]; //feedback coefficients

b=[1,1,-0.5,0,0,0,-0.6,0.7]; //feedforward coefficients

c=Buffer.sendCollection(s, a, 1);

d=Buffer.sendCollection(s, b, 1);

)

{Impulse.ar(1)}.play

{LTI.ar(Impulse.ar(1), c.bufnum, d.bufnum)}.play

{LTI.ar(AudioIn.ar(1), c.bufnum, d.bufnum)}.play

(

a=[0.01,-0.01]; //Array.fill(10,{rrand(0.001,0.01)}); //feedback coefficients

b=[1]++Array.fill(100,{exprand(0.1,1)}); //feedforward coefficients

c=Buffer.sendCollection(s, a, 1);

d=Buffer.sendCollection(s, b, 1);

)

//piercing, careful!

{Saw.ar(LFNoise0.kr(10,4000,5000))}.play

{LTI.ar(Saw.ar(LFNoise0.kr(10,4000,5000)), c.bufnum, d.bufnum,0.1)}.play

//Also see [Convolution]