Before I make a post on Parks-McClellan filter design, I want to talk about a paper I found awhile back when I was looking around on the internet for existence of a 2-dimensional or N-dimensional filter design equivalent to Parks-McClellan. I found a paper called Equiripple FIR Filter Design by FFT Algorithm (microsoft academic link , too lazy to find the paper), and it promises Parks-McClellan like filter results but using a much simpler method that simply relies on FFTs. This is the first time I read a not-already-proven research paper (say, the original OFDM paper or the “Gap” paper by Forney), and it was too good to be true. In fact, it actually sucks, and it’s easily apparent if you think about the process, but the paper make it sound so legit. I couldn’t even recreate the results with both my code and an implementation from their university that I found, so this is complete bullshit.
The algorithm can be summarized as follows:
0. Decide on a ripple , a large N number of frequency points, and an L<N length of an FIR filter you'd like, L being an odd number so we can have a linear phase or zero phase FIR filter.
1. initialize your ideal filter, using a large number of discrete frequency points, say N points. Of course, for a low pass filter, that simply means make a rect function around the 0 index.
2. IFFT your filter, and window it to the number of samples you want your zero-phase FIR filter to be. Say, if you want a filter of L samples such that L is odd, then we want samples N-floor(L/2):N-1 and 0:floor(L/2), basically around the 0 index still. Thanks to our IFFT, the filter should be symmetric and zero-phase. At this point, if you looped back here already, check for convergence against the previous windowed result. If converged, break, your filter is done.
3. FFT the filter, and in frequency, subtract the filter magnitude (it should all be real since we're doing zero-phase) from the ideal filter we had at the beginning and wherever the error is greater than ripple, force it to be , and wherever the error is less than ripple, force it to be .
4. Go back to step 2, and check for convergence.
This system basically does windowing filter design AND frequency sampling in an alternating fashion to come up with a filter. Combining 2 shitty filter design methods does NOT get you a result that is even remotely as good as Parks-McClellan. Just the windowing alone results in getting ridiculous Gibbs phenom, and let's not even forget we have no idea what effects are resulting from the frequency constrained design part. This method does converge, but the result is garbage.
I have a version of this “equiripple 1D filter design” function in MATLAB, and I included it below. I made it significantly fancier than the research paper suggested, but nothing really fixed what it did wrong. I did get better results by using non-boxcar filters in frequency with a linear transition from stopband to passband, so that’s worth a try, though you might as well use Parks-McClellan still.
function [filter_coefficients, within_constraint, loops] = equiripple_1D( filter_order, freq_constraint, amp_constraint, mse_max, type_number )
%% equiripple_1D - FIR filter design using equiripple FFT algorithm.
% equiripple_1D( filter_order, filter_constraints ) creates an
% equiripple filter using the given
% filter_constraints, and filter order.
% Inputs:
% filter_order - number of coefficients of designed filter. Will
% be forced into an odd number if even number is
% given.
% freq_constraint - a n x 1 vector of numbers from 0 to 1 in
% normalized angular/radian frequency that
% determine with the amplitude constraint the ideal
% filter, and whether or not the filter comes
% within the constraints wanted. Basically, this
% determines the ripple allowed; put the maximum
% ripple you want for stop bands and 1-ripple for
% pass bands.
% amp_constraint - a n x 1 vector corresponding to freq constraints
% that gives amplitude minimums (in pass band) and
% maximums (in stop band). Values should be between
% 0 and 1 (as opposed to 0 to pi).
% mse_max - maximum value of mean square error acceptable
% when designing filter. If left empty, defaults
% to 1/10^order of your filter.
% type_number - type of window you want to design the filter with
% in the iterative process. Expecting a number
% between 1 and 5. Defaults to 0.
% 0 - hamming window
% 1 - rectangular window
% 2 - triangular window
% 3 - blackman window
% 4 - gaussian window
% Outputs:
% filter_coefficients - coefficients of the FIR filter that the
% algorithm designed. Should be symmetrical and
% linear phase.
% within_constraint - checks if filter is within constraints set
% initially and if not, returns a 0. Otherwise,
% returns a 1 if successful. Used if iterative
% designs with different parameters are
% required.
%% input checking
% make sure these are Nx1
freq_constraint = freq_constraint(:);
amp_constraint = amp_constraint(:);
% freq and amp constraints are the same size
assert( size( freq_constraint, 1 ) == size( amp_constraint, 1 ) );
% freq constraint is more than 0 or less than 1
assert( all( freq_constraint >= 0 ) && all( freq_constraint <= 1 ) );
% freq constraint also should be in ascending order
freq_constraint = sort( freq_constraint );
% amp constraint is more than 0 or less than 1
%assert( all( amp_constraint >= 0 ) && all( amp_constraint <= 1 ) );
% make sure filter order is >0, and odd for now
if isempty( filter_order ) || filter_order < 3
filter_order = find_ideal_order( freq_constraint, amp_constraint );
end
if rem( round(filter_order), 2 ) ~= 1
filter_order = round(filter_order) + 1;
end
% make sure MSE maximum is actually set to something
if ~exist( 'mse_max', 'var' ) || isempty( mse_max ) || mse_max < 0 || mse_max > .5
mse_max = 10^-filter_order;
end
% maks sure window is set
if ~exist( 'type_number', 'var' ) || type_number > 4 || type_number < 0
type_number = 0;
end
%% actual design process
% create the ideal filter using constraints
ideal_filter = create_ideal_filter( filter_order, ...
freq_constraint, ...
amp_constraint );
% iterative part, set up initial large error and previous filter
mean_square_error = 9999;
ideal_filter_coefs = real( ifft( ideal_filter ) );
greater_part = (filter_order+1 )/2;
lesser_part = (filter_order-1)/2;
ideal_filter_coefs( (greater_part + 1):(end - lesser_part) ) = 0;
work_filter = real( fft( ideal_filter_coefs ) );
previous_filter = zeros( filter_order, 1 );
loops = 0;
window_coef = make_window( filter_order, type_number );
window_coef = [ window_coef(greater_part:end) ; ...
zeros( numel( ideal_filter_coefs ) - numel(window_coef), 1) ; ...
window_coef(1:lesser_part) ];
while mean_square_error > mse_max
loops = loops + 1;
% shaping in frequency domain
current_design = force_fft_shape( work_filter, ...
ideal_filter, ...
freq_constraint, ...
amp_constraint );
% zero out coefficients outside of our desired filter order
%nz_current = current_design;
current_design( (greater_part + 1):(end - lesser_part) ) = 0;
%current_design = current_design .* window_coef;
for_error_checking = [current_design(1:greater_part) ; current_design(end-lesser_part+1:end) ];
% check for mean square error, and then save current design as previous
mean_square_error = find_mse( previous_filter, for_error_checking );
previous_filter = for_error_checking;
%mean_square_error = find_mse( nz_current, current_design );
work_filter = real( fft( current_design ) );
if loops > 1e6
break;
end
end
%check if within constraints, may be implemented later
if loops > 1000
within_constraint = 0;
else
within_constraint = 1;
end
current_design = current_design .* window_coef;
filter_coefficients = [current_design(1:greater_part) ; current_design(end-lesser_part+1:end) ];
end
%% subfunction for creating ideal filter
function coefficients = create_ideal_filter( filter_order, freq_constraint, amp_constraint )
% make constraints have 0 and 1 frequency, and idealized amplitudes
[freq_constraint, amp_constraint] = fix_constraints( freq_constraint, ...
amp_constraint );
% for ideal filter, turn amplitudes into 0s and 1s
amp_constraint = round(amp_constraint);
% find order of filter that can give us exact values we want.
% Inverse of greatest common divisor of all the freq_constraints
% will give us enough coefficients to hit exact freq_constraint
% locations
% design_order = 1;
% while (design_order <= filter_order) || any( rem( freq_constraint, 1/design_order ) > 0 )
% design_order = design_order * 10;
% end
% design_order = design_order+1;
design_order = ( ( filter_order - 1 )/2 ) * 10 + 1;
% make ideal filter
coordinates = round( design_order .* freq_constraint );
coordinates(1) = 1;
coefficients = round( interp1( coordinates, amp_constraint, (1:design_order)') );
coefficients = [ coefficients; coefficients(end:-1:2) ];
end
%% subfunction for forcing FFT into constraints
function new_coefficients = force_fft_shape( work_filter, ...
ideal_filter, ...
freq_constraint, ...
amp_constraint )
% get freq constraint with 0 and 1 appended
[freq_constraint, amp_constraint] = fix_constraints( freq_constraint, amp_constraint );
% for ideal filter, turn amplitudes into 0s and 1s
ideal_amp_constraint = round(amp_constraint);
% get coordinates to work with
coordinates = round( ( ( numel( ideal_filter )+1 )/2 ) .* freq_constraint );
coordinates(1) = 1;
% get a negative -1 where in stopband, 1 in passband
% pos_or_neg_ripple = 2*ideal_amp_constraint - 1;
min_ripple = min( abs( ideal_amp_constraint - amp_constraint ) );
%find where amp constraint remains in stop or remains in pass
no_change = find( diff( ideal_amp_constraint ) == 0 );
for index = no_change(:)'
workpiece = work_filter( coordinates(index):coordinates(index+1) );
work_ideal = ideal_filter( coordinates(index):coordinates(index+1) );
try
workpiece( workpiece > (work_ideal + min_ripple) ) = work_ideal(workpiece > (work_ideal + min_ripple)) + min_ripple;
catch
end
try
workpiece( workpiece < (work_ideal - min_ripple) ) = work_ideal(workpiece < (work_ideal - min_ripple)) - min_ripple;
catch
end
work_filter( coordinates(index):coordinates(index+1) ) = workpiece;
end
% %need to force out all other ripples, using largest ripple factor found
% min_ripple = min( abs( ideal_amp_constraint - amp_constraint ) );
% top_bound = ideal_filter + min_ripple;
% low_bound = ideal_filter - min_ripple;
% work_filter( work_filter > (ideal_filter + min_ripple) ) = top_bound( work_filter > (ideal_filter + min_ripple) );
% work_filter( work_filter < (ideal_filter - min_ripple) ) = low_bound( work_filter < (ideal_filter - min_ripple) );
% now constraints only worked on the first half, need to take it and
% flip it
greater_part = (numel(work_filter)+1)/2;
work_filter = [ work_filter(1:greater_part); work_filter(greater_part:-1:2) ];
new_coefficients = real( ifft( work_filter ) );
end
%% subfunction for error checking
function error = find_mse( previous_filter, current_design )
inside_part = (previous_filter - current_design).^2;
error = mean( inside_part );
end
%% subfunction for making constraints have ends
function [freq_constraint, amp_constraint] = fix_constraints( freq_constraint, amp_constraint )
% check if freq 0 and freq 1 are specified, and if not, stick them
% on the ends and copy amp constraints from closest point
if freq_constraint(1) ~= 0
freq_constraint = [0; freq_constraint];
amp_constraint = [amp_constraint(1); amp_constraint];
end
if freq_constraint(end) ~= 1
freq_constraint = [freq_constraint; 1];
amp_constraint = [amp_constraint; amp_constraint(end)];
end
end
function coefficients = make_window( filter_order, type_number )
% make a vector from 1 to filter_order, and find hamming window as long as
% the filter created.
n = 1:filter_order;
n = n - 1;
switch type_number
case 0
coefficients = .54 - .46 * cos( 2*pi*n./(filter_order - 1) );
case 1
coefficients = ones(filter_order, 1);
case 2
half_of_triangle = linspace( 0, 1, (filter_order-1)/2 )';
coefficients = [half_of_triangle(1:end); half_of_triangle(end:-1:2)];
case 3
case 4
end
coefficients = coefficients(:);
end
function filter_order = find_ideal_order( freq_constraint, amp_constraint )
%find smallest ripple constraint
ideal_amp_constraint = round( amp_constraint );
min_ripple = min( abs( ideal_amp_constraint - amp_constraint ) );
%find smallest passband constraint
differences = diff(ideal_amp_constraint);
transition = find( differences ~= 0 );
transition_bw = min( freq_constraint( transition + 1 ) - freq_constraint( transition ) );
%calculate N; doing top part of fraction, bottom part, then ceiling because
%we need an integer N.
N = -20 * log10( min_ripple ) - 13;
N = N / (14.6*( transition_bw ) / (2*pi) );
filter_order = ceil( N );
end