Just in case anyone else is having trouble with this, here are my thoughts. I've been playing with this FilterDesign toolbox for the last few days, and I had a rather limited grasp on this whole concept when I started. Hopefully this clarifies a few points (and I'm not just spouting incorrect information)
Lets say you have a signal sampled at 1000Hz with 10 muV of noise and a 500 muV 100Hz Sine wave. We can design a filter for the 100Hz component of this signal by creating a transfer function, but we first need to look at the Z plane.
In order to make sense of this space, all of the frequencies must be normalized to the sampling rate. A normalized frequency of 0 Hz is (0/1000) = 0.0, and a normalized frequency of 1000Hz is (1000/1000) = 1.0. According to the
nyquist criterion, frequencies above half of the sampling rate (500Hz) will show artifacts from
aliasing, so we are only interested in frequencies up to 500Hz in this signal (500/1000) = 0.5.
Frequencies map to locations in the Z plane by using this transformation: z = exp( i * 2Pi * f ) This means that a frequency of 0Hz - 0.0 (DC) exists at z = exp( i*2*Pi*0 ) = exp( 0 + 0i ) = (1.0 + i0.0), and a frequency of 500Hz - 0.5 (nyquist) exists at z = exp( i * 2Pi * 0.5 ) = exp( i * Pi ) = (-1.0 + 0.0i). It just so happens that if you interpolate this number from 0Hz to the nyquist frequency (500Hz in this case), you'll find that this traces along the top half of the unit circle from (1.0,0.0) to (-1.0,0.0).
The transfer function created by FilterDesign is a fraction of complex polynomials. Everywhere the numerator of this fraction is equal to zero, the resulting evaluation of this transfer function is zero. As such, these locations are called zeros. Everywhere the denominator of this fraction is equal to zero, the resulting evaluation of this transfer function is infinite (divide by zero). These locations are called poles. If you were to imagine a large sheet of tent fabric on this plane, propped up by tent-poles at the pole locations, and nailed to the ground at the zero locations, you would have a nice visual representation of your transfer function in the z-plane. Where this "tent-cloth" intersects the top half of the unit circle, you will see the frequency response of the filter you've designed. You can plot it in a figure called a
bode plot and see what frequencies are suppressed (stopped) and which are passed.
These filters do not necessarily maintain the amplitude of the original signal, however. If you were to band-pass filter our example signal from 90-110 Hz, you would not see 500muV of output 100Hz activity. About all we can do is attempt to normalize the gain, but even this will not accurately output the original amplitude of this signal.
In the case of a low-pass filter, we want to normalize the gain such that the gain on the lowest frequency within the pass band (DC) is unity or close to it. As such, we want to Evaluate() our transfer function at (1.0 + 0.0i), or simply 1.0. We divide our unity gain by the result of this evaluation such that when we multiply by the gain later, we get a unity gain at this frequency.
For a high-pass filter, we want to apply the same idea, but we have to move our evaluation point to somewhere in the pass band. The nyquist frequency should be in the pass band of a high pass filter, so we can evaluate it there (-1.0 + 0.0i).
A Band-stop filter is interesting, in that it should pass both low and high frequencies but stop some frequencies in between. In this case, you could likely evaluate the transfer function at either -1.0 or 1.0.
A band-pass filter, however, will stop frequencies at both -1.0 and 1.0 (depending on your design parameters), so you will want to evaluate the gain at the peak of your pass-band, which should be the center frequency. In this instance, to isolate the 100Hz component, we will want a band-pass filter with a cuton at 90Hz and a cutoff of 110Hz. This puts our center frequency, f_c = 90+((110-90)/2.0) = 100Hz = 0.1. We can then evaluate the transfer function at exp( complex( 0, 2*pi*f_c ) ) and divide by the magnitude of the complex result. It just so happens that abs() is overloaded to deal with complex numbers in just this way.
In short, here is some helpful source code for dealing with FilterDesign.
Code: Select all
if( type == "Butterworth" || type == "Chebyshev" )
{
FilterDesign::Butterworth* filt = NULL;
if( type == "Butterworth" )
filt = new FilterDesign::Butterworth();
else
{
FilterDesign::Chebyshev* chebyFilt = new FilterDesign::Chebyshev();
chebyFilt->Ripple_dB( ripple );
filt = chebyFilt;
}
filt->Order( order );
float cuton_freq = ( float )cuton / ( float )input.SamplingRate();
float cutoff_freq = ( float )cutoff / ( float )input.SamplingRate();
FilterDesign::Complex z( 0, 0 );
if( character == "Lowpass" )
{
filt->Lowpass( cutoff_freq );
z = 1.0f;
}
else if( character == "Highpass" )
{
filt->Highpass( cutoff_freq );
z = -1.0f;
}
else if( character == "Bandpass" )
{
filt->Bandpass( cuton_freq, cutoff_freq );
double f_c = cuton_freq + ( ( cutoff_freq - cuton_freq ) / 2.0f );
z = exp( FilterDesign::Complex( 0, 2 * M_PI * f_c ) );
}
else if( character == "Bandstop" )
{
filt->Bandstop( cuton_freq, cutoff_freq );
z = 1.0f;
}
else
bcierr << "Unknown filter characteristic \"" << character
<< "\" on row " << row << " of " << matrix->Name() << endl;
transferFunction *= filt->TransferFunction();
FilterDesign::Complex complexGain = filt->TransferFunction().Evaluate( z );
gain /= abs( complexGain );
}
You guys probably knew most of that, but I'm still trying to understand it myself and the pure mathematical descriptions didn't really make as much sense to me. I find it easier to understand what's going on intuitively by thinking about it this way.
Hope that helps,
-Griff