ZPlane a Z-plane object for SC with graphical interface


Inherits from: Object


A class for graphing poles and zeros on a Z-plane. Filters of arbitrary order can be designed by specifying the location of the desired poles and zeros on the Z-plane and feeding these polar coordinates to FilterCoef.


ZPlane can also calculate the frequency and phase response of the desired filter. A number of convenience methods are also implemented for easier use, like automatic detection of identical pole/zero pairs and automatic conjugate adding when specifying a pole/zero (thus resulting in real-valued filter coefficients). 



Creation / Class Methods


*new(poles, zeros, function, real)

Creates a new instance of ZPlane. Multiple instances are allowed to co-exist peacefully.

poles - An Array containing a number of poles of type Polar.

zeros - An Array containing a number of zeros of type Polar.

function - A Function evaluated each time the mouse is moved in the gui. Can be used to update specific settings when a pole or zero is dragged to another location on the Z-plane. 

real - A Boolean indicating whether the filter should be real or complex. When set to true, the conjugates of any specified poles and zeros are added automatically.


Instance Methods

addPole(index, location)

Add a pole to the Z-plane. Index must denote a valid integer location into the poles array. Alternatively, index can be specified as nil, which means the pole will be added to the tail of the poles array. Poles can also be added by calling .gui on an instance of Z-plane, clicking on the desired location of the graphical representation of the Z-plane and pressing 'ctrl- p'.

index - An instance of Integer or nil

location - An instance of Polar


addZero(index, location)

Add a zero to the Z-plane. Index must denote a valid integer location into the zeros array. Alternatively, index can be specified as nil, which means the zero will be added to the tail of the zeros array. Zeros can also be added by calling .gui on an instance of Z-plane, clicking on the desired location of the graphical representation of the Z-plane and pressing 'ctrl- z'.

index - An instance of Integer or nil

location - An instance of Polar


removePole(index)

Removes the pole specified by index of type Integer. If there is a conjugate, this is removed too. This method returns the (possibly modified) index in response to a call. Internally, this is used by setPole.

removeZero(index)

Removes the zero specified by index of type Integer. If there is a conjugate, this is removed too. This method returns the (possibly modified) index in response to a call. Internally, this is used by setZero.


removeAllPoles

Removes all poles associated with this instance of ZPlane. (Poles might be added at 0, 0 however, to keep the nr. of poles and zeros equal.)

removeAllZeros

Removes all poles associated with this instance of ZPlane. (Poles might be added at 0, 0 however, to keep the nr. of poles and zeros equal.)

setPole(index, location)

Change the location of a particular pole specified by index. If there is a conjugate this will be moved too.

index - An instance of Integer or nil

location - An instance of Polar

setZero(index, location)

Change the location of a particular zero specified by index. If there is a conjugate this will be moved too.

index - An instance of Integer or nil

location - An instance of Polar

calcAmpResp(res, norm)

Calculates the frequency response of the filter and returns an Array of Integer length res (default is 512), holding the result. Norm is a  Boolean indicating wether the response should be normalized to 1.0 or not. The default for norm is 'true'.

calcPhaseResp(res, unwrap, derivative)

Calculates the phase response of the filter and returns an Array of Integer length res (default is 512), holding the result. Unwrap is a Boolean indicating wether the phase of the filter should be unwrapped or not. The default for unwrap is ' true'. Derivative is a Boolean indicating wether the negative valued derivative (group delay) of the phase of the filter should be calculated too. The default for derivative is ' true'.

calcAmpAndPhaseResp(res, norm, unwrap, derivative)

Calculates the frequency and phase response of the filter and returns an Array of arrays, both of Integer length res (default is 512), holding the results. Norm is a  Boolean indicating wether the response should be normalized to 1.0 or not, and unwrap is a Boolean indicating wether the phase of the filter should be unwrapped or not. Derivative is a Boolean indicating wether the negative valued derivative (group delay)  of the phase of the filter should be calculated too. The default for all three is ' true'. 

If one desires the frequency and phase response simultaneously, this method is slightly more efficient than calling calcAmpResp and calcPhaseResp separately.

gui(bounds)

Makes a Window with a graphical interface of the Z-plane. The user can drag the poles and zeros to different location across the graph. By clicking inside the graph for the Z-plane and pressing ctrl- p, a new pole will be created at that location. Similarly, by pressing ctr- z a new zero will be created. To delete an item, click on a pole or zero and press ctrl- d. By dragging the bottom right corner, the view can be magnified or de-magnified. Bounds is a Rect indicating the dimensions of the graphical interface.

close

If a Z-plane graph gui is open, close it.

Examples




// Creating a new instance of ZPlane.

(

// Create a new instance of ZPlane with a pole at 0.25, 0.25pi and a zero at 0.25, 0.75pi.

a = ZPlane.new([Polar.new(0.25, 0.25pi)], [Polar.new(0.25, 0.75pi)]);

// Notice that ZPlane automatically accounts for the complex conjugates.

a.poles.postln;

a.zeros.postln;


// Add a second pole at 0.75, 0.0pi

a.addPole(nil, Polar.new(0.75, 0.0pi));

// Notice that ZPlane automatically added a zero at 0.0, 0.0pi to keep the nr. of poles and zeros

// equal.

a.poles.postln;

a.zeros.postln;

)



// Controlling things graphically. Try to move poles and zeros around or to create new poles and  

// zeros by clicking inside the view and pressing ctrl- p or ctrl- z respectively. Delete poles or  

// zeros by clicking on an item and pressing ctrl- d.

(

// Create a new instance of ZPlane with a pole at 0.25, 0.25pi and a zero at 0.25, 0.75pi.

a = ZPlane.new([Polar.new(0.25, 0.25pi)], [Polar.new(0.25, 0.75pi)], { |me| [me.poles, me.zeros].postln });


a.gui;

)



// Plotting the amplitude and phase response of a filter

(

// Create a new instance of ZPlane with a pole at 0.8, 0.25pi and a zero at 1.0/0.8, 0.25pi. An 

// example of an allpass filter.

a = ZPlane.new([Polar.new(0.8, 0.25pi)], [Polar.new(0.8.reciprocal, 0.25pi)]);


b = a.calcAmpAndPhaseResp;

b[0].plot("Amplitude Response", Rect(50, 300, 400, 200), minval: 0.0, maxval: 1.2);

b[1].plot("Phase Response", Rect(460, 300, 400, 200));

b[2].plot("Group Delay", Rect(870, 300, 400, 200));

)



// Using ZPlane together with FilterCoef to generate filter coefficients.

(

// Create a new instance of ZPlane with a zero at 0.8, 0.75pi. A lowpass filter.

a = ZPlane.new(zeros: [Polar.new(0.8, 0.75pi)]);

b = a.calcAmpAndPhaseResp;

b[0].plot("Amplitude Response", Rect(50, 300, 400, 200));

b[1].plot("Phase Response", Rect(460, 300, 400, 200));

b[2].plot("Group Delay", Rect(870, 300, 400, 200));


// Calculate the (normalized) time domain filter coefficients

FilterCoef.calc(a.poles, a.zeros);

)



// Sound examples:

Server.default = Server.internal;

s = Server.default;

s.boot;


// Using ZPlane together with FilterCoef and LTI.

(

// Make a new instance of ZPlane with 7 poles evenly distributed from 0 to pi

a = ZPlane.new([Polar.new(0.997, 0.0pi), Polar.new(0.997, (1/6)*pi), Polar.new(0.997, (1/3)*pi), Polar.new(0.997, (1/2)*pi), Polar.new(0.997, (2/3)*pi), Polar.new(0.997, (5/6)*pi), Polar.new(0.997, pi)]);


a.gui; // Watch the Z-plane graph

b = a.calcAmpAndPhaseResp; // Calculate amplitude and phase response

b[0].plot("Amplitude Response", Rect(50, 90, 400, 200));

b[1].plot("Phase Response", Rect(460, 90, 400, 200));

b[2].plot("Group Delay", Rect(870, 90, 400, 200));

)


(

// Calculate time-domain coefficients and fill the buffers needed by LTI.

c = FilterCoef.calc(a.poles, a.zeros);

d = Buffer.sendCollection(s, c[0].real, 1);

e = Buffer.sendCollection(s, c[1][1..c[1].lastIndex].real, 1);

)


SCFreqScopeWindow.new(400, 200, 0);


x = { LTI.ar(WhiteNoise.ar(0.4).dup(2), e.bufnum, d.bufnum) }.play;

x.free



// CAUTION: The following example is a bit dangerous, since we are updating the coefficients in 

// real-time. As long as we keep the poles inside the unit circle and don't add or delete any poles 

// or zeros we should be fine.

(

var func;


Server.internal.boot.scope;

func = { |me| var coef = FilterCoef.calc(me.poles, me.zeros);

x.set(\a0, coef[0][0].real, \a1, coef[0][1].real, \a2, coef[0][2].real, \b1, coef[1][1].real, \b2, coef[1][2].real)

};

a = ZPlane.new([Polar.new(0.25, 0.25pi)], [Polar.new(0.5, 0.75pi)], func);

a.gui;


x = { arg a0 = 0.36224217105666, a1 = 0.2561438955859, a2 = 0.090560542764165, 

b1 = 0.35355339059327, b2 = -0.0625; 

SOS.ar(WhiteNoise.ar(0.4).dup, a0, a1, a2, b1.neg, b2.neg) 

}.play(Server.internal);

)


s.makeBundle(nil, { x.free });